Следует ли избегать LINQ, потому что он медленный?

Мне сказали, что, поскольку .net linq очень медленный, мы не должны его использовать, и мне было интересно, что кто-то другой пришел к такому же выводу, и пример:

Взял 1443мс, чтобы сделать 1000000000 сравнений не LINQ.
Взял 4944мс, чтобы сделать 1000000000 сравнений с LINQ.
(На 243% медленнее)

код не LINQ:

for (int i = 0; i < 10000; i++)
{
    foreach (MyLinqTestClass1 item in lst1) //100000 items in the list
    {
        if (item.Name == "9999")
        {
            isInGroup = true;
            break;
        }
    }
}

Взял 1443мс, чтобы сделать 1000000000 сравнений не LINQ.

LINQ код:

for (int i = 0; i < 10000; i++)  
    isInGroup = lst1.Cast<MyLinqTestClass1>().Any(item => item.Name == "9999");  

Взял 4944мс, чтобы сделать 1000000000 сравнений с LINQ.

Я предполагаю, что можно оптимизировать код LINQ, но мысль заключалась в том, что легко получить действительно медленный код LINQ и, учитывая, что его не следует использовать. Учитывая, что LINQ медленный, из этого следует также, что PLINQ медленный, а NHibernate LINQ будет медленным, поэтому не следует использовать любой тип оператора LINQ.

Кто-нибудь еще обнаружил, что LINQ настолько медленный, что ему хотелось бы, чтобы он никогда не использовал его, или я делаю слишком общий вывод, основываясь на таких тестах?

Ответ 1

Следует ли избегать Linq, потому что его медленный?

Нет. Его следует избегать, если он недостаточно быстрый. Медленные и недостаточно быстрые не совсем то же самое!

Slow не имеет отношения к вашим клиентам, вашему руководству и вашим заинтересованным сторонам. Не достаточно быстро, очень важно. Никогда не измеряйте, как быстро что-то происходит; что ничего не говорит о том, что вы можете использовать для обоснования бизнес-решения. Измерьте, насколько близок к тому, чтобы быть приемлемым для клиента. Если это приемлемо, прекратите тратить деньги на его ускорение; это уже достаточно хорошо.

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

Вы нашли искусственную, нереалистичную ситуацию, когда код LINQ медленнее, чем какой-либо другой способ написания кода. Я заверяю вас, что ваши клиенты не заботятся о скорости вашего нереалистичного теста. Им все равно, если программа, которую вы им отправляете, слишком медленна для них. И я заверяю вас, что ваш менеджмент не заботится об этом (если они компетентны); они заботятся о том, сколько денег вы тратите бесполезно, чтобы сделать вещи, которые достаточно быстро незаметно быстрее, и сделать код более дорогим для чтения, понимания и поддержки в процессе.

Ответ 2

Почему вы используете Cast<T>()? Вы не дали нам достаточного кода, чтобы действительно оценить результат теста.

Да, вы можете использовать LINQ для записи медленного кода. Угадай, что? Вы также можете записать медленный код не LINQ.

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

Если кто-то сказал мне, чтобы я не использовал LINQ (особенно LINQ to Objects) по воспринимаемым причинам скорости, я бы смеялся им в лицо. Если бы они столкнулись с определенным узким местом и сказали: "Мы можем сделать это быстрее, не используя LINQ в этой ситуации, а здесь доказательства", тогда это совсем другое дело.

Ответ 3

Возможно, я что-то пропустил, но я уверен, что ваши тесты не работают.

Я тестировал следующие методы:

  • Метод расширения Any ( "LINQ" )
  • Простой цикл foreach (ваш "оптимизированный" метод)
  • Использование метода ICollection.Contains
  • Метод расширения Any с использованием оптимизированной структуры данных (HashSet<T>)

Вот тестовый код:

class Program
{
    static void Main(string[] args)
    {
        var names = Enumerable.Range(1, 10000).Select(i => i.ToString()).ToList();
        var namesHash = new HashSet<string>(names);
        string testName = "9999";
        for (int i = 0; i < 10; i++)
        {
            Profiler.ReportRunningTimes(new Dictionary<string, Action>() 
            {
                { "Enumerable.Any", () => ExecuteContains(names, testName, ContainsAny) },
                { "ICollection.Contains", () => ExecuteContains(names, testName, ContainsCollection) },
                { "Foreach Loop", () => ExecuteContains(names, testName, ContainsLoop) },
                { "HashSet", () => ExecuteContains(namesHash, testName, ContainsCollection) }
            },
            (s, ts) => Console.WriteLine("{0, 20}: {1}", s, ts), 10000);
            Console.WriteLine();
        }
        Console.ReadLine();
    }

    static bool ContainsAny(ICollection<string> names, string name)
    {
        return names.Any(s => s == name);
    }

    static bool ContainsCollection(ICollection<string> names, string name)
    {
        return names.Contains(name);
    }

    static bool ContainsLoop(ICollection<string> names, string name)
    {
        foreach (var currentName in names)
        {
            if (currentName == name)
                return true;
        }
        return false;
    }

    static void ExecuteContains(ICollection<string> names, string name,
        Func<ICollection<string>, string, bool> containsFunc)
    {
        if (containsFunc(names, name))
            Trace.WriteLine("Found element in list.");
    }
}

Не беспокойтесь о внутренних элементах класса Profiler. Он просто запускает Action в цикле и использует Stopwatch для его времени. Он также обязательно вызовет GC.Collect() перед каждым тестом, чтобы устранить как можно больше шума.

Вот результаты:

      Enumerable.Any: 00:00:03.4228475
ICollection.Contains: 00:00:01.5884240
        Foreach Loop: 00:00:03.0360391
             HashSet: 00:00:00.0016518

      Enumerable.Any: 00:00:03.4037930
ICollection.Contains: 00:00:01.5918984
        Foreach Loop: 00:00:03.0306881
             HashSet: 00:00:00.0010133

      Enumerable.Any: 00:00:03.4148203
ICollection.Contains: 00:00:01.5855388
        Foreach Loop: 00:00:03.0279685
             HashSet: 00:00:00.0010481

      Enumerable.Any: 00:00:03.4101247
ICollection.Contains: 00:00:01.5842384
        Foreach Loop: 00:00:03.0234608
             HashSet: 00:00:00.0010258

      Enumerable.Any: 00:00:03.4018359
ICollection.Contains: 00:00:01.5902487
        Foreach Loop: 00:00:03.0312421
             HashSet: 00:00:00.0010222

Данные очень согласованы и рассказывают следующую историю:

  • Наивное использование метода расширения Any примерно на 9% медленнее, чем наивно, используя цикл foreach.

  • Использование наиболее подходящего метода (ICollection<string>.Contains) с неоптимизированной структурой данных (List<string>) примерно на 50% быстрее, чем наивно, используя цикл foreach.

  • Использование оптимизированной структуры данных (HashSet<string>) полностью удаляет любой из других методов из воды с точки зрения производительности.

Я понятия не имею, откуда у вас 243%. Я предполагаю, что это имеет какое-то отношение ко всему этому кастингу. Если вы используете ArrayList, вы не только используете неоптимизированную структуру данных, вы используете сильно устаревшую структуру данных.

Я могу предсказать, что будет дальше. "Да, я знаю, что вы можете оптимизировать его лучше, но это был всего лишь пример для сравнения производительности LINQ и non-LINQ".

Да, но если вы не могли бы быть в вашем примере даже тщательнее, как вы можете рассчитывать на то, что это будет полным в производственном коде?

В нижней строке указано следующее:

Как вы архивируете и разрабатываете свое программное обеспечение экспоненциально более важно, чем конкретные инструменты, которые вы используете и когда.

Если вы столкнетесь с узкими местами производительности, которые каждый бит, вероятно, произойдет с LINQ и без них, тогда решите их. Эрик предлагает автоматизированные тесты производительности, является отличным; это поможет вам выявить проблемы на раннем этапе, чтобы вы могли решить их правильно - не избегая потрясающего инструмента, который делает вас на 80% более продуктивным, но, 10%, но фактически исследует проблему и придумывает реальное решение, которое может повысить вашу производительность в 2, 10 или 100 или более.

Создание высокопроизводительных приложений - это не использование правильных библиотек. Это о профилировании, выборе хорошего дизайна и написании хорошего кода.

Ответ 4

Является ли LINQ "узким местом" в реальном мире (как для общей, так и для воспринимаемой производительности приложения)?

Будет ли ваша заявка выполнять эту операцию на 1,000,000,000+ записей в реальном мире? Если это так - тогда вы, возможно, захотите рассмотреть альтернативы - если не тогда, то это говорит о том, что "мы не можем купить этот семейный седан, потому что он плохо управляется при 180+ MPH".

Если это "просто медленно", то это не очень хорошая причина... по тому рассуждению вы должны писать все в asm/C/С++, а С# должен быть за столом "слишком медленным".

Ответ 5

В то время как преждевременная пессимизация (imho) так же плоха, как преждевременная оптимизация, вы не должны исключать всю технологию, основанную на абсолютной скорости, без учета контекста использования. Да, если вы делаете очень тяжелый хруст с номером , и это узкое место, LINQ может быть проблематичным - профилируйте его.

Аргумент, который вы можете использовать в пользу LINQ, заключается в том, что, хотя вы, вероятно, можете превзойти его рукописным кодом, версия LINQ, вероятно, будет более понятной и простой в обслуживании. Кроме того, есть преимущество PLINQ по сравнению со сложной ручной распараллеливанием.

Ответ 6

Проблема с таким сопоставлением заключается в том, что она не имеет смысла в абстрактной форме.

Можно было бы победить любого из них, если кто-то должен был начать с хэширования объектов MyLinqTestClass1 своим свойством Name. Между ними, если можно сортировать их по имени, а затем выполнять двоичный поиск. Действительно, нам не нужно хранить объекты MyLinqTestClass1 для этого, нам просто нужно сохранить имена.

Объем памяти - проблема? Может быть, сохранить имена в структуре DAWG, объединить их достаточно, а затем использовать для этой проверки?

Есть ли лишние накладные расходы при настройке этих структур данных? Это невозможно сказать.

Еще одна проблема - это другая проблема с понятием LINQ, которое является его именем. Это отлично подходит для маркетинговых целей, чтобы MS могла сказать "здесь куча классных новых вещей, которые работают вместе", но менее хороши, когда дело касается людей, объединяющих вещи вместе, когда они проводят анализ, где они должны раздвигать их, Вы должны обратиться к Any, который в основном реализует шаблон на основе перечня фильтров, распространенный в .NET2.0 дней (и не неизвестный с .NET1.1, хотя было бы более неудобно писать, поскольку оно использовалось только там, где его эффективность эффективности в некоторых случаях действительно имеет значение), у вас есть лямбда-выражения, и у вас есть деревья запросов, которые объединены в одну концепцию. Какой медленный?

Я бы сказал, что ответ здесь - это лямбда, а не использование Any, но я бы не поставил большой суммы (например, успех проекта), я бы протестировал и был уверен. Между тем, способ использования лямбда-выражений с IQueryable может сделать особенно эффективный код, что было бы чрезвычайно сложно писать с эквивалентной эффективностью без использования lambdas.

Неужели мы не можем быть эффективными, когда LINQ хорош в эффективности, потому что у него не получается искусственный бенчмарк? Я так не думаю.

Используйте LINQ, где это имеет смысл.

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

Ответ 7

Возможно, linq медленный, но с linq я могу очень легко распараллелить свой код.

Вот так:

lst1.Cast<MyLinqTestClass1>().AsParallel().Any(item => item.Name == "9999");

Как вы распараллеливаете цикл?

Ответ 8

Для меня это похоже на то, что вы работаете над контрактом, а работодатель либо не понимает LINQ, либо не понимает узких мест в производительности системы. Если вы пишете приложение с графическим интерфейсом, незначительное влияние использования LINQ незначительно. В типичном GUI/веб-приложении вызовы в памяти составляют менее 1% всего времени ожидания. Вы, или, вернее, ваш работодатель, пытаетесь оптимизировать это 1%. Это действительно выгодно?

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

Кстати, актер не нужен. Следующее функционально эквивалентно вашему первому тесту:

       for (int i = 0; i < 10000; i++)
            isInGroup = lst1.Any(item => item.Name == "9999");

Когда я запускал это с помощью тестового списка, содержащего 10 000 объектов MyLinqTestClass1, оригинал выполнялся через 2,79 секунды, пересмотренный за 3,43 секунды. Экономия 30% на операциях, которые, вероятно, занимают менее 1% процента времени процессора, не очень хорошо использует ваше время.

Ответ 9

Здесь интересное замечание, поскольку вы упоминаете, что nHibernate медленнее, поскольку LINQ является медленным. Если вы выполняете LINQ to SQL (или эквивалент nHibernate), то ваш код LINQ преобразуется в запрос EXISTS на SQL-сервере, где, поскольку ваш код цикла должен сначала извлекать все строки, а затем перебирать их. Теперь вы можете легко написать такой тест, чтобы код цикла считывал все данные один раз (один поиск в БД) для всех запусков 10K, но код LINQ фактически выполняет запросы 10K SQL. Вероятно, это будет иметь большое преимущество в скорости для версии цикла, которой в действительности не существует. На самом деле один запрос EXISTS будет превосходить сканирование таблицы и цикл каждый раз - даже если у вас нет индекса в столбце, который запрашивается (что, вероятно, будет, если этот запрос выполняется очень часто).

Я не говорю, что это имеет место с вашим тестом - у нас нет достаточного кода для просмотра, но это может быть так. Также может случиться так, что на LINQ to Objects существует разница в производительности, но это может вообще не переводить LINQ to SQL. Вам нужно знать, что вы измеряете, и насколько оно применимо к вашим реальным потребностям.

Ответ 10

"Мне сказали [кем?], что, поскольку linux linux настолько медленный [для чего?], мы не должны его использовать"

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

Прежде всего, информация поступает от источника, которому вы доверяете? Если нет, вы можете сделать огромную ошибку, доверяя этому (возможно, неизвестному) человеку принимать ваши дизайнерские решения. Во-вторых, эта информация по-прежнему актуальна сегодня? Но в порядке, на основе вашего простого и не очень реалистичного теста, вы пришли к выводу, что LINQ работает медленнее, чем вручную, выполняя ту же операцию. Естественный вопрос, который нужно задать себе, заключается в следующем: критическая ли производительность этого кода? Будет ли производительность этого кода ограничена другими факторами, чем скорость выполнения моего запроса LINQ, - считайте запросы базы данных, ожидая ввода-вывода и т.д.?

Вот как мне нравится работать:

  • Определите проблему, которую необходимо решить, и напишите простейшее полнофункциональное решение с учетом требований и ограничений, которые вы уже знаете о
  • Определите, действительно ли ваша реализация выполняет требования (достаточно ли она достаточно? Является ли потребление ресурсов на приемлемом уровне?).
  • Если это так, вы закончили. Если нет, найдите способы оптимизации и уточнения своего решения, пока оно не пройдет тест на # 2. Здесь вам может потребоваться отказаться от чего-то, потому что он слишком медленный. Может быть. Однако есть вероятность, что узкое место не там, где вы ожидали, что оно вообще будет.

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

Да, может наступить день, когда вы обнаружите, что ваше оригинальное решение больше не сокращает его. Или это не так. Если да, то с этим справитесь. Я предлагаю вам не тратить время на то, чтобы решить гипотетические (будущие) проблемы.

Ответ 11

Да, ты прав. Легко писать медленный код в LINQ. Другие тоже правы: легко писать медленный код в С# без LINQ.

Я написал тот же цикл, что и в C, и он запустил некоторое количество миллисекунд быстрее. Вывод из этого состоит в том, что сам С# медленный.

Как и в случае с расширением LINQ- > loop, в C потребуется более чем в 5 раз больше строк кода, чтобы сделать то же самое, делая его медленнее писать, труднее читать, с большей вероятностью иметь ошибки и более жесткие найти и исправить их, но если сохранить несколько миллисекунд за каждые миллиарды итераций, важно, что часто это нужно.

Ответ 12

Как вы продемонстрировали, можно написать код не LINQ, который работает лучше, чем код LINQ. Но возможно и обратное. Учитывая преимущество обслуживания, которое может предоставить LINQ, вы можете подумать о потере LINQ, так как маловероятно, что вы столкнетесь с узкими местами производительности, которые можно отнести к LINQ.

Тем не менее, есть некоторые сценарии, в которых LINQ просто не будет работать. Например, если вы импортируете тонну данных, вы можете обнаружить, что действие выполнения отдельных вставок медленнее, чем отправка данных на SQL Server партиями XML. В этом примере это не значит, что вставка LINQ быстрее, чем вставка не LINQ, а не для того, чтобы выполнять отдельные вставки SQL для импорта объемных данных.

Ответ 13

Я бы сказал, что вам следует избегать слишком сложного написания наиболее эффективного кода, за исключением того, что это обязательно.

Ответ 14

Учитывая, что LINQ медленный, тогда также будет следовать, что PLINQ медленный, и NHibernate LINQ будет медленным, поэтому любой тип в инструкции LINQ не должен использоваться.

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

Ответ 15

Ваш тестовый пример немного перекошен. Оператор ANY начнет перечисляться через ваши результаты и вернет true в первом случае, если находит и завершает работу. Попробуйте это с помощью простых списков строк, чтобы увидеть результат. Чтобы ответить на ваш вопрос об избежании LINQ, вы должны действительно перейти к использованию LINQ. Это упрощает чтение кода при выполнении сложных запросов в дополнение к проверке времени компиляции. Также вам не нужно использовать оператор Cast в вашем примере.

string compareMe = "Success";
string notEqual = "Not Success";

List<string> headOfList = new List<string>();
List<string> midOfList = new List<string>();
List<string> endOfList = new List<string>();

//Create a list of 999,999 items
List<string> masterList = new List<string>();
masterList.AddRange(Enumerable.Repeat(notEqual, 999999));

//put the true case at the head of the list
headOfList.Add(compareMe);
headOfList.AddRange(masterList);

//insert the true case in the middle of the list
midOfList.AddRange(masterList);
midOfList.Insert(masterList.Count/2, compareMe);

//insert the true case at the tail of the list
endOfList.AddRange(masterList);
endOfList.Add(compareMe);


Stopwatch stopWatch = new Stopwatch();

stopWatch.Start();
headOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
midOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
endOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Stop();

Ответ 16

Тип кастинга, конечно, замедляет ваш код. Если вам это очень нравится, по крайней мере, для сравнения используется сильно типизированный IEnumerable. Я сам стараюсь использовать LINQ везде, где это возможно. Это делает ваш код намного более кратким. Не нужно беспокоиться о императивных деталях вашего кода. LINQ - это функциональная концепция, которая означает, что вы расскажете, что вы хотите, и не беспокойтесь о том, как.

Ответ 17

Есть тысячи причин, чтобы избежать Linq.

Следуя цитате из обсуждения имен Linq, некоторые из них:

QUOTE1

"Например, это работает:

var a = new { x = 1, y = 2 };
a = new { x = 1, y = 3 };

Но это не работает:

var a = new { x = 1, y = 2 };
a = new { x = 1, y = 2147483649 };

Он возвращает: Error 1 Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'

Но это работает:

var a = new { x = 1, y = 2147483648 };
a = new { x = 1, y = 2147483649 };

При компиляции:

var a = new { x = 1, y = 2 };

Тип компонентов x и y произвольно объявляется как 32-разрядное целое число со знаком, и это один из многих целочисленных типов, которые имеет платформа, без каких-либо особых.

Но есть еще. Например, это работает:

double x = 1.0;
x = 1;

Но это не работает:

var a = new { x = 1.0, y = 0 }; 
a = new { x = 1, y = 0 };

Правила цифрового преобразования не применимы к этим типам типов. Как вы можете видеть, элегантность в каждой детали. "

quote2

"Похоже, что" Анонимный тип 1 "и" Анонимный тип № 2 "не являются синонимами - они называют разные типы. А поскольку { x = 1, y = 2 } и { y = 2, x = 1 } являются выражениями этих двух типов, соответственно, только они обозначают различные значения, но также и значения различных типов.

Итак, я был прав, чтобы быть параноидальным. Теперь моя паранойя распространяется еще дальше, и я должен спросить, что LinQ делает из сравнения:

new { x = 1, y = 2 } == new { x = 1, y = 2 }

Результат ложный, потому что это сравнение указателей.

Но результат:

(new { x = 1, y = 2 }).Equals(new { x = 1, y = 2 })

Является истинным.

И результат:

(new { x = 1, y = 2 }).Equals(new { y = 2, x = 1 })

и

(new { x = 1, y = 2 }).Equals(new { a = 1, b = 2 })

Является ложным. "

QUOTE3

"обновления ориентированы на запись: -O

Это, я согласен, проблематично и вытекает из ориентированного на LINQ характера.

Это шоу-стоппер для меня. Если мне все равно нужно использовать SQL для моих обновлений, зачем беспокоиться о LinQ?

Оптимизация в объектах LinQ для объектов несущественна.

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