C # foreach производительность против фрагментации памяти - PullRequest
0 голосов
/ 16 января 2019

Обнаружение проблемы с производительностью (микро, я знаю) Я заканчиваю с этой тестовой программой. Скомпилированный с фреймворком 4.5 и режимом релиза он работает на моей машине около 10 мс.

Что меня беспокоит, если что, если я уберу эту строку

public int[] value1 = new int[80];

раз приближаются к 2 мс. Кажется, что есть некоторая проблема фрагментации памяти, но я не смог объяснить почему. Я протестировал программу с Net Core 2.0 с такими же результатами. Кто-нибудь может объяснить это поведение?

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApp4
{

    public class MyObject
    {
        public int value = 1;
        public int[] value1 = new int[80];
    }


    class Program
    {
        static void Main(string[] args)
        {

            var list = new List<MyObject>();
            for (int i = 0; i < 500000; i++)
            {
                list.Add(new MyObject());
            }

            long total = 0;
            for (int i = 0; i < 200; i++)
            {
                int counter = 0;
                Stopwatch timer = Stopwatch.StartNew();

                foreach (var obj in list)
                {
                    if (obj.value == 1)
                        counter++;
                }

                timer.Stop();
                total += timer.ElapsedMilliseconds;
            }

            Console.WriteLine(total / 200);

            Console.ReadKey();
        }
    }
}

UPDATE:

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

  • без массива

Without array

  • с массивом

With array

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Я не могу воспроизвести большую разницу между ними, и я бы не ожидал этого тоже.Ниже приведены результаты, полученные на .NET Core 2.2.

Экземпляры MyObject будут размещены в куче.В одном случае у вас есть int и ссылка на массив int.В другой у вас есть только Int.В обоих случаях вам нужно выполнить дополнительную работу, следуя ссылке из списка.Это одинаково в обоих случаях, и скомпилированный код показывает это.

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

BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.376 (1803/April2018Update/Redstone4)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.2.200-preview-009648
  [Host]     : .NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), 64bit RyuJIT
  DefaultJob : .NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), 64bit RyuJIT


       Method |   size |     Mean |     Error |    StdDev | Ratio |
------------- |------- |---------:|----------:|----------:|------:|
    WithArray | 500000 | 8.167 ms | 0.0495 ms | 0.0463 ms |  1.00 |
 WithoutArray | 500000 | 8.167 ms | 0.0454 ms | 0.0424 ms |  1.00 |

Для справки:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Generic;

namespace CoreSandbox
{
    [DisassemblyDiagnoser(printAsm: true, printSource: false, printPrologAndEpilog: true, printIL: false, recursiveDepth: 1)]
    //[MemoryDiagnoser]
    public class Test
    {
        private List<MyObject> dataWithArray;
        private List<MyObjectLight> dataWithoutArray;

        [Params(500_000)]
        public int size;

        public class MyObject
        {
            public int value = 1;
            public int[] value1 = new int[80];
        }

        public class MyObjectLight
        {
            public int value = 1;
        }

        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Test>();
        }

        [GlobalSetup]
        public void Setup()
        {
            dataWithArray = new List<MyObject>(size);
            dataWithoutArray = new List<MyObjectLight>(size);

            for (var i = 0; i < size; i++)
            {
                dataWithArray.Add(new MyObject());
                dataWithoutArray.Add(new MyObjectLight());
            }
        }

        [Benchmark(Baseline = true)]
        public int WithArray()
        {
            var counter = 0;

            foreach(var obj in dataWithArray)
            {
                if (obj.value == 1)
                    counter++;
            }

            return counter;
        }

        [Benchmark]
        public int WithoutArray()
        {
            var counter = 0;

            foreach (var obj in dataWithoutArray)
            {
                if (obj.value == 1)
                    counter++;
            }

            return counter;
        }

    }
}
0 голосов
/ 16 января 2019

Есть несколько последствий.

Когда у вас есть строка public int[] value1 = new int[80];, у вас есть одно дополнительное выделение памяти: в куче создается новый массив, который будет содержать 80 целых чисел (320 байт) + накладные расходы класса. Вы делаете 500 000 из этих ассигнований.

Эти выделения составляют более 160 МБ ОЗУ, что может привести к срабатыванию ГХ и проверке наличия свободной памяти.

Кроме того, когда вы выделяете столько памяти, вполне вероятно, что некоторые объекты из списка не сохраняются в кэше ЦП. Когда вы позже перечислите свою коллекцию, ЦП может потребоваться считывать данные из ОЗУ, а не из кэша, что приведет к серьезному снижению производительности.

...