Подсчет элементов в массиве - PullRequest
3 голосов
/ 31 января 2020

У меня есть этот массив: var arr = new int[] { 1, 1, 0, -1, -1 };, где мне нужно посчитать количество положительных, отрицательных и нулевых чисел. Я сделал это с foreach l oop и с Linq, и я попытался сравнить производительность между двумя методами, используя Stopwatch, вот мой код:

        int pos = 0, neg = 0, zeros = 0;
        int p = 0, n = 0, z = 0;


        Stopwatch sw = new Stopwatch();

        sw.Start();

        pos = arr.Sum(e => e > 0 ? 1 : 0);
        neg = arr.Sum(e => e < 0 ? 1 : 0);
        zeros = arr.Sum(e => e == 0 ? 1 : 0);

        sw.Stop();


        Stopwatch sw2 = new Stopwatch();

        sw2.Start();

        foreach (var item in arr)
        {
            if (item > 0) p++;
            else if (item < 0) n++;
            else z++;
        }

        sw2.Stop();

        Console.WriteLine("Elapsed={0}", sw.Elapsed); //Elapsed=00:00:00.0008311
        Console.WriteLine("Elapsed2={0}", sw2.Elapsed); //Elapsed2=00:00:00.0000028

Результаты показали мне, что foreach l oop, где намного лучше (28 мс), чем метод Linq (8311 мс), поэтому мой вопрос, почему все это разница в производительности?

Я даже пытался сделайте три foreach l oop, один для подсчета негативов, один для подсчета позитивов и третий для подсчета нулей, но производительность все равно была лучше, чем метод Linq!

Заранее благодарим за помощь!

Ответы [ 2 ]

3 голосов
/ 31 января 2020

Давайте проведем скачки немного по-другому:

private static string UnderTest(int size) {
  int pos = 0, neg = 0, zeros = 0;
  int p = 0, n = 0, z = 0;

  Random random = new Random(0);
  int[] arr = Enumerable
    .Range(0, size)
    .Select(x => random.Next(-1, 2))
    .ToArray();
  GC.Collect(GC.MaxGeneration);

  Stopwatch sw = new Stopwatch();
  sw.Start();

  // Three Linq loops (expected to be 3 three times slower)
  pos = arr.Sum(e => e > 0 ? 1 : 0);    
  neg = arr.Sum(e => e < 0 ? 1 : 0);
  zeros = arr.Sum(e => e == 0 ? 1 : 0);

  sw.Stop();

  Stopwatch sw2 = new Stopwatch();
  sw2.Start();

  // Just 1 loop 
  foreach (var item in arr) {
    if (item > 0) p++;
    else if (item < 0) n++;
    else z++;
  }

  sw2.Stop();

  return $"{sw.Elapsed} vs. {sw2.Elapsed} ratio: {((double)sw.Elapsed.Ticks) / sw2.Elapsed.Ticks:f3}";
} 

Гонки для другого массива size s:

   int[] loops = new int[] {
    1000,
    10_000,
    100_000,
    1_000_000,
    10_000_000,
    100_000_000,
    1000,         // <- 1000 again
  };

  string report = string.Join(Environment.NewLine, loops
    .Select(loop => $"loops: {loop,10} : {UnderTest(loop)}"));

  Console.Write(report);

Результат:

loops:       1000 : 00:00:00.0006471 vs. 00:00:00.0000114 ratio: 56.763 // <- Warming up
loops:      10000 : 00:00:00.0003195 vs. 00:00:00.0001074 ratio: 2.975
loops:     100000 : 00:00:00.0037131 vs. 00:00:00.0010910 ratio: 3.403
loops:    1000000 : 00:00:00.0351574 vs. 00:00:00.0118858 ratio: 2.958
loops:   10000000 : 00:00:00.3729617 vs. 00:00:00.1198276 ratio: 3.112
loops:  100000000 : 00:00:03.7002508 vs. 00:00:01.1808595 ratio: 3.134
loops:       1000 : 00:00:00.0000322 vs. 00:00:00.0000099 ratio: 3.253 // <- Expected

Что происходит: у нас есть 3 циклы на основе Linq (3 вызовы для Sum), поэтому Linq 3 * В 1021 раз медленнее (не удивительно). Однако мы имеем огромный выброс при самом первом запуске , когда система загружает сборку , компилирует IL-код и c. Итак, у вас есть так называемый эффект прогрева .

1 голос
/ 31 января 2020

Foreach - это причудливое представление для простого for, итерирующего по массиву один раз и подсчитывающего все 3 значения одновременно.

Сумма создает временную переменную (назовем ее суммой) и выполняет итерацию по массив, вызывая функцию, которую вы указали в качестве аргумента, возвращая ее значение и добавляя ее во временную переменную. Это означает, что вы вызываете n функций, где n - длина массива.

Как вы, вероятно, можете догадаться из этого, вызов функции обходится намного дороже (с точки зрения тактов), чем просто добавляя их , Вы также делаете это 3 раза, по одному для каждого arr.sum.

Помимо всего этого, есть некоторые накладные расходы на (ну любые) функции, библиотеки или нет, и так как размер массива не очень большой , что накладные расходы будут иметь значение

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...