Подтвердить что ты не робот

Почему одновременная модификация массивов настолько медленная?

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

const long maxCount = 500000000;
const int numThreads = 4;
const int Multiplier = 1;
static void DoIt()
{
    long[] c = new long[Multiplier * numThreads];
    var threads = new Thread[numThreads];

    // Create the threads
    for (int i = 0; i < numThreads; ++i)
    {
        threads[i] = new Thread((s) =>
            {
                int x = (int)s;
                while (c[x] > 0)
                {
                    --c[x];
                }
            });
    }

    // start threads
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < numThreads; ++i)
    {
        int z = Multiplier * i;
        c[z] = maxCount;
        threads[i].Start(z);
    }
    // Wait for 500 ms and then access the counters.
    // This just proves that the threads are actually updating the counters.
    Thread.Sleep(500);
    for (int i = 0; i < numThreads; ++i)
    {
        Console.WriteLine(c[Multiplier * i]);
    }

    // Wait for threads to stop
    for (int i = 0; i < numThreads; ++i)
    {
        threads[i].Join();
    }
    sw.Stop();
    Console.WriteLine();
    Console.WriteLine("Elapsed time = {0:N0} ms", sw.ElapsedMilliseconds);
}

Я запускаю Visual Studio 2010, программу, скомпилированную в режиме выпуска, целевую среду .NET 4.0, "Любой процессор" и выполняется в 64-битной среде исполнения без прикрепленного отладчика (Ctrl + F5).

Эта программа работает около 1,700 мс в моей системе с одним потоком. С двумя потоками он занимает более 25 секунд. Полагая, что разница в конфликте кеша, я установил Multipler = 8 и снова запустил. Результат составляет 12 секунд, поэтому утверждение было по крайней мере частью проблемы.

Увеличение Multiplier выше 8 не повышает производительность.

Для сравнения, похожая программа, которая не использует массив, занимает около 2,200 мс с двумя потоками, когда переменные смежны. Когда я разделяю переменные, две версии потока выполняются за тот же промежуток времени, что и однопоточная версия.

Если проблема связана с избыточным индексированием массива, вы ожидаете, что она появится в однопоточной версии. Мне кажется, что при изменении массива происходит какое-то взаимное исключение, но я не знаю, что это такое.

Глядя на сгенерированный ИЛ, он не очень просвещает. Также не было разборки. Разборка действительно показывает пару вызовов (я думаю) библиотеки времени выполнения, но я не смог войти в них.

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

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

Может ли кто-нибудь сказать мне, что вызывает это значительное замедление?

4b9b3361

Ответ 1

У вас есть ложное разделение. Я написал статью об этом здесь