NDepend ищет более быстрые возможности сбора

У меня есть огромная устаревшая кодовая база, и я хотел бы ее оптимизировать, сделать быстрее. По этой причине я подумал о поиске возможностей замены списков и массивов наборами HashSet и словарями.

Существует следующий запрос NDepend в разделе Использование .NET Framework/System.collection

// <Name>Caution with List.Contains()</Name>
let containsMethods = ThirdParty.Methods.WithFullNameIn(
   "System.Collections.Generic.List<T>.Contains(T)",
   "System.Collections.Generic.IList<T>.Contains(T)",
   "System.Collections.ArrayList.Contains(Object)")

from m in Application.Methods.UsingAny(containsMethods) 
select m

Этого запроса недостаточно. В нем будет указана одна функция со следующим кодом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ListOptimisation
{
    class Program
    {
        static void Main(string[] args)
        {
            int aLength = 10000;
            List<int> aNumbers2Search = Enumerable.Range(0, aLength).ToList();

            List<int> aTestList = Enumerable.Range(0, aLength).ToList();
            int[] aTestArray = Enumerable.Range(0, aLength).ToArray();

            HashSet<int> aTestHash = new HashSet<int>(Enumerable.Range(0, aLength));
            Dictionary<int, int> aTestDictionary = new Dictionary<int, int>();
            for(int i = 0; i < aLength; ++i)
            {
                aTestDictionary.Add(i, i);
            }

            Search(aTestList, aNumbers2Search);
            SearchIList(aTestList, aNumbers2Search);
            SearchIEnumerable(aTestList, aNumbers2Search);
            Search(aTestArray, aNumbers2Search);
            SearchIList(aTestArray, aNumbers2Search);
            SearchIEnumerable(aTestArray, aNumbers2Search);
            Search(aTestHash, aNumbers2Search);
            SearchIEnumerable(aTestHash, aNumbers2Search);
            Search(aTestDictionary, aNumbers2Search);
        }

        private static void Search(List<int> testList_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testList_in.Contains(x));
        }

        private static void Search(HashSet<int> testHash_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testHash_in.Contains(x));
        }

        private static void Search(Dictionary<int, int> testDictionary_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testDictionary_in.ContainsKey(x));
        }

        private static void Search(int[] testArray_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testArray_in.Contains(x));
        }

        private static void SearchIList(IList<int> testIList_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testIList_in.Contains(x));
        }

        private static void SearchIEnumerable(IEnumerable<int> testIEnumerable_in, List<int> numbers2Search_in)
        {
            numbers2Search_in.ForEach(x => testIEnumerable_in.Contains(x));
        }
    }
}

Лучшим запросом был бы такой:

// <Name>Caution with List style contains</Name>
let containsMethods = ThirdParty.Methods.WithSimpleName("Contains").Except(ThirdParty.Methods.WithFullNameIn("System.Collections.Generic.HashSet<T>.Contains(T)"))

from m in Application.Methods.UsingAny(containsMethods) 
select m

//<Description>
// Alternative to Caution with List.Contains()
//</Description>

В нем будут перечислены 4 функции (List, IList, int[], IEnumerable). Я новичок в CQLinq. Мои вопросы:

  • Кто-нибудь может написать лучший запрос для обнаружения возможного неправильного использования контейнера .NET (не только для содержимого, но и для других возможных операций)?
  • Как бы вы обнаружили плохое использование контейнера?

Последний комментарий: некоторая наша бизнес-логика обрабатывает много данных, поэтому наличие правильных контейнеров, структур данных и алгоритмов имеет значение.


person Laszlo    schedule 22.07.2016    source источник


Ответы (2)


Действительно, попытка заменить вызовы List<T>.Contains() вызовами Hashset<T>.Contains() не является микрооптимизацией и может значительно повысить производительность. На самом деле рефакторинг алгоритма, основанный на поиске по хеш-набору O(1), по моему опыту, является одним из лучших способов повышения производительности.

Написанный вами запрос CQLinq — это первый шаг к выявлению некоторых потенциально медленных точек. Однако, чтобы хорошо начать рефакторинг, вы должны 1) просмотреть код, чтобы оценить размер коллекции во время выполнения, и 2) использовать инструмент профилирования производительности в реальной ситуации, чтобы оценить, влияют ли эти потенциально медленные точки на производительность, а также найти другие медленные точки, не соответствующие запросу.

person Patrick from NDepend team    schedule 26.07.2016
comment
Что ж, у нас есть тесты производительности бизнес-сценария, поэтому я могу проверить свои изменения и сопоставить измененный код с соответствующим тестом, так что это не проблема. :-) Моя самая большая мечта, чтобы кто-то ответил на мой второй вопрос с набором хорошо зарекомендовавших себя запросов CQLinq под названием «Возможные узкие места производительности». :-) - person Laszlo; 26.07.2016
comment
Запрос, который вы написали, правильный, но не идеальный. Если Hashset‹T› создается в методе, а ICollection‹T›.Contains() вызывается из другого метода объекта ICollection‹T›, NDepend не будет достаточно сообразительным, чтобы догадаться, что вызов Contains() правильно, это статический анализатор, а не динамический анализатор ;-) - person Patrick from NDepend team; 27.07.2016
comment
Да, мой запрос будет давать ложные срабатывания, но будет включать больше запахов. И да, NDepends не является динамическим анализатором, возможно, мне следует поставить точку останова трассировщика в .NET List.Contains... - person Laszlo; 28.07.2016
comment
Почему бы не использовать динамический анализатор? - person Patrick from NDepend team; 29.07.2016

Это не лучший подход для оптимизации проблем с производительностью. Эта оптимизация окажет незначительное влияние на вашу систему, если только вы не имеете дело с огромными списками.

Вы получите лучшие результаты, используя программное обеспечение для профилирования производительности. Если вы хотите повысить производительность путем поиска какого-либо шаблона кода, попробуйте выполнить поиск вложенных циклов и дорогостоящего кода, такого как методы, связанные с файлами и базами данных.

person lstern    schedule 22.07.2016
comment
Это не микрооптимизация, и мы смогли добиться огромного прироста производительности, переписав пару обработок List‹›, O(n) .Contains time to HashSet и Dictionary O(1) .Contains time. - person Laszlo; 22.07.2016
comment
Как вы измеряете улучшение производительности? Я знаю, что не отвечаю на ваш вопрос, но вы указали свою проблему (производительность) и описали необычный подход к решению этой проблемы (и ваш запрос уже улавливает все соответствующие элементы, содержащие IMO). Возможно, вам следует отредактировать свой вопрос, предоставив дополнительную информацию о системе. и как вы проводите этот процесс повышения производительности. - person lstern; 22.07.2016
comment
У нас есть тест производительности, который выполняет понятные для бизнеса сценарии и регистрирует время их выполнения. И да, мы нашли пару узких мест с очень долгим и утомительным профилированием, и в конце концов это было плохое использование контейнеров. Мы не можем все профилировать, кодовая база огромна (миллионы строк), для меня это легаси. - person Laszlo; 22.07.2016