Интерпретация вывода Cachegrind

Это часть вывода cachegrind. Эта часть кода была выполнена 1224 раза. elmg1 представляет собой массив unsigned long размером 16 x 20. Размер кеша L1 моей машины составляет 32 КБ, размер строки кеша 64 байт и ассоциативный набор с 8 путями.

  1. для (i = 0; i ‹ 20; i++) 78 336 2 448 2 50 184 0 0 1 224 0 0
  2. {
  3. telm01 = elmg1[i]; 146 880 0 0 73 440 0 0 24 480 0 0
  4. telm31 = (telm01 ‹‹ 3) ^ val1; 97 920 0 0 48 960 0 0 24 480 0 0
  5. telm21 = (telm01 ‹‹ 2) ^ (val1 >> 1); 146 880 1 224 1 48 960 0 0 24 480 0 0
  6. telm11 = (telm01 ‹‹ 1) ^ (val1 >> 2); 146 880 0 0 48 960 0 0 24 480 0 0
  7. }

О. Причина, по которой я поместил это здесь, заключается в том, что в 3-й строке внутри цикла for я вижу несколько промахов I1 (также один промах L2). Это несколько сбивает с толку, и я не мог догадаться, почему?

B. Я пытаюсь оптимизировать (по времени) часть кода. Вышеупомянутое является лишь небольшим фрагментом. Я думаю, что в моей программной памяти доступ обошелся мне очень дорого. Как и в приведенном выше примере, elmg1 представляет собой массив беззнаковых длинных чисел размером 16 x 20. Когда я пытаюсь использовать его в коде, всегда есть какие-то промахи, а в моей программе этих переменных очень много. Какие-либо предложения?

C. Мне нужно выделить и (иногда инициализировать) эти беззнаковые длинные. Можете ли вы предложить, какой из них я должен предпочесть, calloc или объявление массива, а затем явную инициализацию. Кстати, будет ли разница в том, как кеш их обрабатывает?

Спасибо.


person anup    schedule 01.11.2010    source источник


Ответы (1)


Вы пробовали развернуть петлю?

  1. Я бы не беспокоился о промахах L1 прямо сейчас. Также допустим один промах L2 из 1224, процессор должен в какой-то момент загрузить значения в кеш.
  2. Какой процент промахов L2 стоит для этого кода по сравнению с остальной частью программы?
  3. Используйте calloc(), если размер массива всегда один и тот же, и вы используете константы для размера, тогда компилятор может оптимизировать обнуление массива. Кроме того, единственное, что может повлиять на использование строк кэша, — это выравнивание, а не то, как оно было инициализировано.

изменить: число, которое трудно прочитать таким образом, и прочитать его неправильно в первый раз.

давайте удостоверимся, что я правильно читаю числа для строки 5:

Ir    146,880
I1mr  1,224
ILmr  1
Dr    48,960
D1mr  0
DLmr  0
Dw    24,480
D1mw  0
DLmw  0

Кэш L1 разделен на два кэша по 32 КБ: один для кода I1 и один для данных D1. IL и DL — это кеш L2 или L3, который используется как для данных, так и для инструкций.

Большое количество I1mr — это пропуски инструкций, а не пропуски данных, это означает, что код цикла выбрасывается из кэша инструкций I1.

I1 промахивается в строке 1 и 5, всего 3672, что составляет 3 раза по 1224, поэтому каждый раз, когда цикл запускается, вы получаете 3 промаха кеша I1 с 64-байтными строками кеша, что означает, что размер кода цикла составляет от 128 до 192 байтов, чтобы покрыть 3 строки кеша. Таким образом, I1 пропускает строку 5 потому, что код цикла пересекает последнюю строку кэша.

Я бы рекомендовал использовать KCachegrind для просмотра результатов от cachegrind

Изменить: Подробнее о строках кеша.

Этот код цикла не выглядит так, будто он вызывается сам по себе 1224 раза, так что это означает, что есть еще код, который выталкивает этот код из кеша I1.

Ваш 32-килобайтный кэш I1 разделен на 512 строк кэша (по 64 байта каждая). Часть «8-way set associative» означает, что каждый адрес памяти отображается только на 8 из этих 512 строк кэша. Если бы вся программа, которую вы профилируете, представляла собой один непрерывный блок из 32 Кбайт памяти, то вся она поместилась бы в кэш I1, и ни одна из них не была бы извлечена. Это, скорее всего, не так, и для тех же 8 строк кэша будет содержаться более 8 блоков кода по 64 байта. Предположим, что вся ваша программа имеет 1 Мбайт кода (включая библиотеки), тогда каждая группа из 8 строк кэша будет содержать около 32 (1 Мбайт/32 Кбайт) фрагментов кода, содержащихся в тех же 8 строках кэша.

Прочитайте эту статью lwn.net, чтобы узнать все кровавые подробности о кэшах ЦП

Компилятор не всегда может определить, какие функции программы будут активными (вызываются много раз), а какие — кодовыми точками (то есть кодом обработчика ошибок, который почти никогда не запускается). GCC имеет функциональные атрибуты горячий/холодный, которые позволяют чтобы пометить функции как горячие/холодные, это позволит компилятору сгруппировать горячие функции вместе в одном блоке памяти, чтобы лучше использовать кеш (т. е. холодный код не будет выталкивать горячий код из кешей).

В любом случае, эти промахи I1 действительно не стоят того, чтобы беспокоиться о них.

person Neopallium    schedule 01.11.2010
comment
A. Это нормально, но почему в строке 5 есть промахи кеша, а в строке 3, 4 меньше. Нужно ли мне самому указывать выравнивание, я читал, что malloc по умолчанию обеспечивает выравнивание 8/16 байт. - person anup; 01.11.2010
comment
да, malloc должен обеспечить как минимум 8-байтовое выравнивание, но это не то же самое, что 64-байтовое выравнивание кеша. Выравнивание кэша важно только тогда, когда у вас есть массив объектов по 64 байта каждый. Если массив не выровнен по кэшу, то доступ к любому элементу в массиве может привести к двум промахам кэша вместо одного. Но выравнивание кеша в этом случае не проблема. - person Neopallium; 01.11.2010
comment
Спасибо за ваш ответ. Но, одно, что я не понял, какое это имеет отношение к 3 строкам кэша? Должно быть больше строк кэша. - person anup; 01.11.2010