Я написал два класса матриц в Java, чтобы сравнить производительность их матричных умножений. В одном классе (Mat1) хранится член double[][] A
, где строка i
матрицы A[i]
. Другой класс (Mat2) хранит A
и T
, где T
является транспонированием A
.
Скажем, мы имеем квадратную матрицу M и хотим получить произведение M.mult(M)
. Вызовите продукт P
.
Когда M является экземпляром Mat1, используемый алгоритм был простым:
P[i][j] += M.A[i][k] * M.A[k][j]
for k in range(0, M.A.length)
В случае, когда M является Mat2, я использовал:
P[i][j] += M.A[i][k] * M.T[j][k]
который является тем же самым алгоритмом, поскольку T[j][k]==A[k][j]
. На матрицах 1000x1000 второй алгоритм занимает около 1,2 секунды на моей машине, а первый занимает не менее 25 секунд. Я ожидал, что второй будет быстрее, но не этим. Вопрос в том, почему это происходит намного быстрее?
Моя единственная догадка заключается в том, что второй лучше использует кэширование CPU, поскольку данные втягиваются в кеши в кусках более одного слова, а второй алгоритм извлекает выгоду из этого путем перемещения только строк, в то время как первый игнорирует данные, вложенные в кеши, сразу переходя к строке ниже (что составляет ~ 1000 слов в памяти, поскольку массивы хранятся в основном порядке строки), ни один из данных, для которых кешируется.
Я спросил кого-то, и он подумал, что это из-за более дружественных шаблонов доступа к памяти (т.е. что вторая версия приведет к меньшему числу слабых ошибок TLB). Я вообще не думал об этом, но я могу видеть, как это приводит к меньшему количеству ошибок TLB.
Итак, что это? Или есть еще одна причина для разницы в производительности?