в чем проблема с использованием пошагового доступа к потоку для измерения пиковой пропускной способности памяти

В качестве примера возьмем Skylake, его строка кэша составляет 64 байта.

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

Затем я запускаю потоки, которые синхронизируют объявление, запуская код ниже параллельно. Таким образом, когда все потоки завершатся, общая загруженная память составит total * NUM_OF_THREADS * 64B. Затем я делю его на (end_time-start_time).

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

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

Любой комментарий? Спасибо.

   st = start_timing()
        do {
          for (i=0; i< 10; i++) {
            asm volatile("movl 0x0(%[P]),%[sum]\n\t"
                         "movl 0x40(%[P]),%[sum]\n\t"
                         "movl 0x80(%[P]),%[sum]\n\t"
                         "movl 0xc0(%[P]),%[sum]\n\t"
                         "movl 0x100(%[P]),%[sum]\n\t"
                         "movl 0x140(%[P]),%[sum]\n\t"
                         "movl 0x180(%[P]),%[sum]\n\t"
                         "movl 0x1c0(%[P]),%[sum]\n\t"
                         "movl 0x200(%[P]),%[sum]\n\t"
                         "movl 0x240(%[P]),%[sum]\n\t"
                         "movl 0x280(%[P]),%[sum]\n\t"
                         "movl 0x2c0(%[P]),%[sum]\n\t"
                         "movl 0x300(%[P]),%[sum]\n\t"
                         "movl 0x340(%[P]),%[sum]\n\t"
                         "movl 0x380(%[P]),%[sum]\n\t"
                         "movl 0x3c0(%[P]),%[sum]\n\t"
                         "movl 0x400(%[P]),%[sum]\n\t"
                         "movl 0x440(%[P]),%[sum]\n\t"
                         "movl 0x480(%[P]),%[sum]\n\t"
                         "movl 0x4c0(%[P]),%[sum]\n\t"
                             : [P]"+r"(p), [sum]"+r"(sum)
                             : );
          }   
          total += 200;
          p = q+ ((total%1000000)<<6);

        } while (total < 10000000);
    et = end_timing()

    bw = (total * 64)/(et-st)

person yeeha    schedule 18.04.2019    source источник
comment
Какие реальные цифры вы получаете? Что вы видите с perf stat для подсчета промахов кеша?   -  person Peter Cordes    schedule 18.04.2019
comment
Я использую perf, чтобы измерить фактическое потребление пропускной способности памяти, которое составляет около 95 ГБ/с. Но мой расчет, упомянутый в исходном посте, дает более 600 ГБ/с. :)   -  person yeeha    schedule 18.04.2019
comment
Вы можете измерить количество попаданий в кэш на каждом уровне, используя события производительности MEM_LOAD_RETIRED.L1_HIT, MEM_LOAD_RETIRED.L2_HIT и MEM_LOAD_RETIRED.L3_HIT. Я предполагаю, что вы получаете много обращений L1, но трудно сказать, почему, не видя весь код. Что вы имели в виду, говоря, что я использую производительность, чтобы измерить фактическое потребление пропускной способности памяти, которое составляет около 95 ГБ / с? Например как?   -  person Hadi Brais    schedule 19.04.2019


Ответы (1)


Да, загрузка dword из каждой строки кеша — хороший способ оценить пропускную способность кеша/памяти для кешей, отличных от L1d. (Если данные остаются горячими в L1d, вам необходимо измерить узкое место их передачи через блоки выполнения загрузки в регистры; если у вас нет AVX512, для чтения всей строки кэша требуется несколько инструкций.)

Вероятно, вы получаете попадания в кэш L1d или L2. Если вы никогда не записывали память, вся она будет копироваться при записи на одну и ту же физическую нулевую страницу, если она находится в BSS или выделена с помощью malloc.

Или просто у разных ядер есть свои приватные кэши L1d. См. раздел Как кэш может быть таким быстрым? на сайте electronics.SE. Однако, если вы на самом деле используете 10 МБ физической памяти, это больше, чем у четырехъядерного рабочего стола SKL. Если у вас Skylake Xeon с большим объемом кэш-памяти L3, то да, совокупная пропускная способность, конечно, может быть значительно выше, чем у оперативной памяти.

Кроме того, http://blog.stuffedcow.net/2013/01/ivb-cache-replacement/ показывает, что замена L3 не является строго псевдо-LRU; он адаптивен в последних версиях Intel, поэтому он может быть более устойчивым, чем вы ожидаете, к исключению из цикла по ОЗУ. 10 МБ может быть достаточно мало, чтобы получить несколько попаданий L3 с общим объемом L3 8 МБ на четырехъядерном i7.


asm volatile предотвратит его оптимизацию, а ввод "+r"(pointer) должен подойти для просмотра обновлений вашего указателя. Компилятор не «знает», что asm читает указанную память (потому что вы не сказали ему об этом, и нет "memory" clobber), поэтому любые более ранние сохранения в буфер могут быть оптимизированы как мертвые хранилища.

person Peter Cordes    schedule 18.04.2019
comment
Я дважды проверил окончательную сборку, и моя встроенная сборка осталась нетронутой. На самом деле я не инициализирую (или не сохраняю) массив ('q'), поскольку вы можете видеть, что я просто читаю его шаг за шагом. - person yeeha; 18.04.2019
comment
Разве я не думаю, что прикасаюсь к 10M * 64B = 640MB памяти из-за пошагового доступа? - person yeeha; 18.04.2019
comment
@yeeha: о, я недостаточно внимательно прочитал вопрос. Я пропустил, что это было 10M загрузок, а не 10МиБ размера буфера. Но я думаю, что отсутствие инициализации вашего массива объясняет ваши наблюдения. Вы получите промахи TLB, но попадания в кэш данных, если все страницы COW сопоставлены с одними и теми же физическими страницами. - person Peter Cordes; 19.04.2019