Эффективная максимальная пропускная способность основной памяти к процессору в C # - PullRequest
0 голосов
/ 05 мая 2018

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

Я думаю, мы можем быть уверены, что кеш не используется при использовании очень больших массивов. До сих пор, используя несколько потоков и long [], мне никогда не удавалось преодолеть ограничение в 2 ГБ / с, хотя я знаю, что современная пропускная способность ОЗУ по крайней мере больше 10 ГБ / с. (У меня современный компьютер, и я работаю в 64-битном режиме, конечно, без режима отладки).

Можете ли вы предоставить программу на C #, способную приблизиться к максимальной пропускной способности? Если нет, не могли бы вы объяснить, почему программа на C # не может этого сделать?

Например:

  • Подготовка: создайте (несколько?) Большой массив и заполните его случайными числами
  • Основной шаг: сумма (или любая операция с низкой загрузкой процессора) всех элементов в массиве

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Это многопоточная версия, которая следует за (очень хорошим) ответом @ harold.

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

int N = 64;
uint[][] data = new uint[N][];
for (int k = 0; k < N; k++)
{
   data[k] = new uint[1000000 * 32];
}
for (int j = 0; j < 15; j++)
{
    long total = 0;
    var sw = Stopwatch.StartNew();
    Parallel.For(0, N, delegate (int k)
    {
       uint sum = 0;
       uint[] d = data[k];
       //for (uint i = 0; i < d.Length; i += 64)
       //{
       //    sum += d[i] + d[i + 16] + d[i + 32] + d[i + 48];
       //}
       for (uint i = 0; i < d.Length; i++)
       {
          sum += d[i];
       }
       Interlocked.Add(ref total, sum);
     });
     sw.Stop();
     long dataSize = (long)data[0].Length* N * 4;
     Console.WriteLine("{0} {1:0.000} GB/s", total, dataSize / sw.Elapsed.TotalSeconds / (1024 * 1024 * 1024));
}

Для измерения информации на моем ноутбуке:

  • однопоточная полоса пропускания: 13 ГБ / с
  • многопоточная полоса пропускания: 20 ГБ / с
  • многопоточное чтение всех элементов: 17 ГБ / с
0 голосов
/ 05 мая 2018

Предполагая, что вы имеете в виду однопотоковую пропускную способность, это довольно просто, например, вот так:

uint[] data = new uint[10000000 * 32];
for (int j = 0; j < 15; j++)
{
    uint sum = 0;
    var sw = Stopwatch.StartNew();
    for (uint i = 0; i < data.Length; i += 64)
    {
        sum += data[i] + data[i + 16] + data[i + 32] + data[i + 48];
    }
    sw.Stop();
    long dataSize = data.Length * 4;
    Console.WriteLine("{0} {1:0.000} GB/s", sum, dataSize / sw.Elapsed.TotalSeconds / (1024 * 1024 * 1024));
}

На моей машине я получаю около 19,8-20,1 ГБ / с, и я знаю, что однопоточная полоса пропускания должна составлять около 20 ГБ / с, так что это нормально. Многопоточная пропускная способность на моей машине на самом деле выше, около 30 ГБ / с, но для этого потребуется более сложный тест, который координирует как минимум два потока.

Некоторые трюки необходимы в этом тесте. Самое главное, я полагаюсь на размер строки кэша 64 байта, чтобы можно было пропускать любые действия с большей частью данных. Поскольку код затрагивает каждую строку кэша (возможно, минус один или два в начале и конце из-за того, что массив не обязательно выровнен по 64), весь массив будет перенесен из памяти. На всякий случай, если это имело значение (это немного изменило результаты, поэтому я сохранил его), я развернул цикл на 4 и сделал индексную переменную без знака, чтобы избежать бессмысленных movsx инструкций. Сохранение операций, особенно с таким скалярным кодом, важно для того, чтобы не делать этим узким местом, а не пропускную способность памяти.

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

...