Я написал два класса матриц в 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.
Итак, что это? Или есть еще одна причина для разницы в производительности?