По сравнению с Linq - производительность против будущего
Очень короткий вопрос. У меня есть случайный сортированный массив больших строк (100K + entries), где я хочу найти первое заполнение нужной строки. У меня есть два решения.
Из-за того, что я читал, что могу предположить, что "цикл цикла" в настоящее время дает немного лучшую производительность (но этот маржа всегда может измениться), но я также считаю версию linq более читаемой. На балансе, какой метод обычно считается лучшей лучшей практикой кодирования и почему?
string matchString = "dsf897sdf78";
int matchIndex = -1;
for(int i=0; i<array.length; i++)
{
if(array[i]==matchString)
{
matchIndex = i;
break;
}
}
или
int matchIndex = array.Select((r, i) => new { value = r, index = i })
.Where(t => t.value == matchString)
.Select(s => s.index).First();
Ответы
Ответ 1
Лучшая практика зависит от того, что вам нужно:
- Скорость разработки и поддерживаемость: LINQ
- Производительность (в соответствии с инструментами профилирования): ручной код
LINQ действительно замедляет работу со всей косвенностью. Не беспокойтесь об этом, так как 99% вашего кода не влияет на производительность конечного пользователя.
Я начал с С++ и действительно научился оптимизировать кусок кода. LINQ не подходит для максимальной загрузки вашего процессора. Поэтому, если вы измеряете запрос LINQ, чтобы быть проблемой, просто выровняйте его. Но только тогда.
Для вашего образца кода я бы оценил 3-кратное замедление. Распределения (и последующие GC!) И нецелесообразности через лямбды действительно пострадали.
Ответ 2
Чуть лучше производительность? Цикл даст ЗНАЧИТЕЛЬНО лучшую производительность!
Рассмотрим приведенный ниже код. В моей системе для сборки RELEASE (not debug) она дает:
Found via loop at index 999999 in 00:00:00.2782047
Found via linq at index 999999 in 00:00:02.5864703
Loop was 9.29700432810805 times faster than linq.
Код намеренно настроен так, что элемент, который будет найден, находится справа в конце. Если бы это было в самом начале, все было бы совсем по-другому.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Demo
{
public static class Program
{
private static void Main(string[] args)
{
string[] a = new string[1000000];
for (int i = 0; i < a.Length; ++i)
{
a[i] = "Won't be found";
}
string matchString = "Will be found";
a[a.Length - 1] = "Will be found";
const int COUNT = 100;
var sw = Stopwatch.StartNew();
int matchIndex = -1;
for (int outer = 0; outer < COUNT; ++outer)
{
for (int i = 0; i < a.Length; i++)
{
if (a[i] == matchString)
{
matchIndex = i;
break;
}
}
}
sw.Stop();
Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);
double loopTime = sw.Elapsed.TotalSeconds;
sw.Restart();
for (int outer = 0; outer < COUNT; ++outer)
{
matchIndex = a.Select((r, i) => new { value = r, index = i })
.Where(t => t.value == matchString)
.Select(s => s.index).First();
}
sw.Stop();
Console.WriteLine("Found via linq at index " + matchIndex + " in " + sw.Elapsed);
double linqTime = sw.Elapsed.TotalSeconds;
Console.WriteLine("Loop was {0} times faster than linq.", linqTime/loopTime);
}
}
}
Ответ 3
LINQ, согласно декларативной парадигме, выражает логику вычисления без описания его потока управления. Запрос ориентирован на цели, самоопределяется и, таким образом, легко анализируется и понимается. Является также кратким. Более того, используя LINQ, одно зависит от абстракции структуры данных. Это связано с высокой скоростью и возможностью повторного использования.
Итерация aproach адресует императивную парадигму. Он обеспечивает мелкозернистый контроль, благодаря чему легко получить более высокую производительность. Код также проще отлаживать. Иногда хорошо продуманная итерация более читаема, чем запрос.
Ответ 4
Хорошо, вы сами ответили на свой вопрос.
Перейдите с циклом For
, если вы хотите получить лучшую производительность, или выберите Linq
, если хотите читать.
Также возможно иметь в виду возможность использования Parallel.Foreach(), которая выиграет от линейных лямбда-выражений (так, что ближе к Linq), и это гораздо более читаемо, а затем делает паралелизацию "вручную".
Ответ 5
Между производительностью и ремонтопригодностью всегда существует дилемма. И обычно (если нет конкретных требований к производительности), то должна поддерживаться ремонтопригодность. Только если у вас проблемы с производительностью, тогда вам следует профилировать приложение, найти источник проблем и улучшить его производительность (за счет сокращения ремонтопригодности в то же время, да, в мире, в котором мы живем).
О вашем образце. Linq - это не очень хорошее решение, потому что оно не добавляет совместимость соответствия вашему коду. На самом деле для меня проецирование, фильтрация и проецирование снова выглядят еще хуже, чем простой цикл. Здесь вам нужно просто Array.IndexOf, который более удобен в обслуживании, чем цикл, и имеет почти такую же производительность:
Array.IndexOf(array, matchString)
Ответ 6
Я не думаю, что это считается лучшей практикой, которую некоторые предпочитают смотреть на LINQ, а некоторые нет.
Если производительность является проблемой, я бы профилировал оба бита кода для вашего сценария, и если разница незначительна, то переходите к той, которую вы считаете более совместимой, в конце концов, скорее всего, вы будете поддерживать код.
Также вы подумали о том, как использовать PLINQ или сделать цикл параллельным?
Ответ 7
Лучший вариант - использовать метод IndexOf класса Array. Поскольку он специализирован для массивов, он будет работать значительно быстрее, чем Linq и For Loop.
Совершенствование Ответа Мэтта Уотсона.
using System;
using System.Diagnostics;
using System.Linq;
namespace PerformanceConsoleApp
{
public class LinqVsFor
{
private static void Main(string[] args)
{
string[] a = new string[1000000];
for (int i = 0; i < a.Length; ++i)
{
a[i] = "Won't be found";
}
string matchString = "Will be found";
a[a.Length - 1] = "Will be found";
const int COUNT = 100;
var sw = Stopwatch.StartNew();
Loop(a, matchString, COUNT, sw);
First(a, matchString, COUNT, sw);
Where(a, matchString, COUNT, sw);
IndexOf(a, sw, matchString, COUNT);
Console.ReadLine();
}
private static void Loop(string[] a, string matchString, int COUNT, Stopwatch sw)
{
int matchIndex = -1;
for (int outer = 0; outer < COUNT; ++outer)
{
for (int i = 0; i < a.Length; i++)
{
if (a[i] == matchString)
{
matchIndex = i;
break;
}
}
}
sw.Stop();
Console.WriteLine("Found via loop at index " + matchIndex + " in " + sw.Elapsed);
}
private static void IndexOf(string[] a, Stopwatch sw, string matchString, int COUNT)
{
int matchIndex = -1;
sw.Restart();
for (int outer = 0; outer < COUNT; ++outer)
{
matchIndex = Array.IndexOf(a, matchString);
}
sw.Stop();
Console.WriteLine("Found via IndexOf at index " + matchIndex + " in " + sw.Elapsed);
}
private static void First(string[] a, string matchString, int COUNT, Stopwatch sw)
{
sw.Restart();
string str = "";
for (int outer = 0; outer < COUNT; ++outer)
{
str = a.First(t => t == matchString);
}
sw.Stop();
Console.WriteLine("Found via linq First at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);
}
private static void Where(string[] a, string matchString, int COUNT, Stopwatch sw)
{
sw.Restart();
string str = "";
for (int outer = 0; outer < COUNT; ++outer)
{
str = a.Where(t => t == matchString).First();
}
sw.Stop();
Console.WriteLine("Found via linq Where at index " + Array.IndexOf(a, str) + " in " + sw.Elapsed);
}
}
}
Выход:
Found via loop at index 999999 in 00:00:01.1528531
Found via linq First at index 999999 in 00:00:02.0876573
Found via linq Where at index 999999 in 00:00:01.3313111
Found via IndexOf at index 999999 in 00:00:00.7244812
Ответ 8
Немного без ответа, и на самом деле просто расширение для fooobar.com/info/135077/..., но я некоторое время работал над API-совместимой заменой Linq-to-Objects. Он по-прежнему не обеспечивает производительность цикла с ручным кодированием, но он быстрее для многих (большинства?) Сценариев linq. Это создает больше мусора и имеет несколько более высокие первоначальные затраты.
Код доступен https://github.com/manofstick/Cistern.Linq
Доступен пакет Nuget https://www.nuget.org/packages/Cistern.Linq/ (я не могу утверждать, что он закален в бою, используйте на свой страх и риск)
Взяв код из ответа Мэтью Уотсона (fooobar.com/info/135077/...) с двумя небольшими изменениями, мы получаем время, равное "всего лишь", в 3,5 раза хуже, чем цикл, закодированный вручную. На моей машине это занимает около 1/3 времени оригинальной версии System.Linq.
Два изменения для замены:
using System.Linq;
...
matchIndex = a.Select((r, i) => new { value = r, index = i })
.Where(t => t.value == matchString)
.Select(s => s.index).First();
со следующим:
// a complete replacement for System.Linq
using Cistern.Linq;
...
// use a value tuple rather than anonymous type
matchIndex = a.Select((r, i) => (value: r, index: i))
.Where(t => t.value == matchString)
.Select(s => s.index).First();
Итак, сама библиотека находится в стадии разработки. Он не проходит пару крайних случаев из набора тестов corefx System.Linq. Также необходимо преобразовать несколько функций (в настоящее время они имеют реализацию corefx System.Linq, которая совместима с точки зрения API, если не с точки зрения производительности). Но больше, кто хочет помочь, прокомментировать и т.д. Будет оценен....