Верхние четыре строки массива занимают часть кэша:
|*ooooooooooooooooooooooooooooooo|
|*ooooooooooooooooooooooooooooooo|
|*ooooooooooooooooooooooooooooooo|
|*ooooooooooooooooooooooooooooooo|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
|...
Выше приведена схема массива, как прикладной математик записал бы массив на бумаге.Каждый элемент состоит из пары (x, y), точки .
Четыре строки, помеченные o
на диаграмме, содержат 128 точек, достаточно для заполнения1024 байта, что составляет только одну четверть кэша, но смотрите: в вашем коде переменная i
равна
- счетчик основных циклов, а также
- индекс массива строка (как написано на бумаге).
Итак, давайте снова посмотрим на диаграмму.Как ваши вложенные циклы шагают по массиву, как показано на схеме?
Ответ: очевидно, ваши циклы шагают вправо по верхнему ряду, как показано на схеме, с j
(столбец) в качестве вспомогательного счетчика цикла.Однако, как вы заметили, массив хранится по столбцам.Поэтому, когда элемент [j][i] == [0][0]
загружен, с ним загружается вся строка кэша.И что включает в себя эту строку кэша?Это четыре элемента, помеченные на диаграмме *
.
Поэтому, пока ваш внутренний цикл выполняет итерацию по верхнему ряду массива, как показано на схеме, кэш-память пропускается каждый раз, каждый раз выбирая четыре элемента.И затем для следующих трех рядов, это все хиты.
Это не легко думать.Это хорошая проблема, и я не ожидаю, что вы поймете мой ответ мгновенно, но если вы внимательно рассмотрите последовательность нагрузок, как я объяснил, это должно (после небольшого размышления) начать обретать смысл.
При данной вложенности цикла коэффициент попадания действительно равен 3 / 4.
ДАЛЬНЕЙШАЯ ОБСУЖДЕНИЕ
В комментариях вы задали хороший дополнительный вопрос:
Можете ли вы написать элемент (например, array[3][14].x
), который ударил бы?
Могу.array[j][i] == array[10][5]
ударил бы.(Оба .x
и .y
будут попадать.)
Я объясню.array[j][i] == array[10][4]
будет отсутствовать, тогда как array[10][5]
, array[10][6]
и array[10][7]
в конечном итоге попадут.Почему в конце концов? Это важно.Хотя все четыре элемента, которые я назвал, загружаются аппаратным обеспечением кеша одновременно, array[10][5]
не доступен для вашего кода (то есть для процессора) при обращении к array[10][4]
.Скорее всего, после доступа к array[10][4]
программа и ЦП получат доступ к array[11][4]
в следующий раз .
Программа и ЦП получат доступ к array[10][5]
только позже.
И, действительно, если вы подумаете об этом, это имеет смысл, не так ли, потому что это часть того, что делают кэши: они теперь загружают дополнительные данные, тихо, как часть строки кэша, так что процессорпри необходимости может быстро получить доступ к дополнительным данным позже.
ПРИЛОЖЕНИЕ: ЗАКАЗ МАТРИЦ FORTRAN / BLAS / LAPACK
Стандартно в числовых вычислениях хранение матриц по столбцама не рядом.Это называется хранилище столбца .К сожалению, в отличие от более раннего языка программирования на Фортране, язык программирования C изначально не был предназначен для численных вычислений, поэтому в C для хранения массивов по столбцам нужно писать array[column][row] == array[j][i]
, что, конечно, означает, что нотация прикладного математика обращает вспятьего или ее карандаш написал бы это.
Это артефакт языка программирования C.Артефакт не имеет математического значения, но при программировании на C вы должны не забывать набирать [j][i]
.[Если бы вы программировали на ныне в основном устаревшем языке программирования Fortran, вы бы набрали (i, j)
, но это не Fortran.]
Причина, по которой хранилище на основе столбцов является стандартным, связана с последовательностью, в которойЦП выполняет скалярное умножение, умножение и сложение с плавающей запятой, когда в математической / карандашной терминологии матрица [A] действует слева на вектор столбца x.Стандартная библиотека подпрограмм линейной алгебры (BLAS), используемая LAPACK и другими, работает таким образом.Вы и я должны работать таким же образом, не только потому, что нам, скорее всего, понадобится взаимодействовать с BLAS и / или LAPACK, но и потому, что в численном отношении это более плавно.