Я столкнулся со странной проблемой производительности в тесте умножения матриц (matrix_mult в Metis из набора MOSBENCH ). Тест был оптимизирован для разбиения данных таким образом, чтобы активный рабочий набор составлял 12 КБ (3 элемента размером 32x32 дюйма) и помещался в кэш L1. Короче говоря, при замене двух внутренних контуров разница в производительности при определенных размерах входных массивов (4096, 8192) почти в 4 раза, а в остальных - около 30%. Проблема, по сути, сводилась к последовательному доступу к элементам, а не к шагу. Я думаю, что определенные размеры массивов создавали неверный доступ, что приводило к коллизиям строк кэша. Разница в производительности заметно меньше при переходе от 2-сторонней ассоциативной L1 к 8-позиционной ассоциативной L1.
Мой вопрос: почему gcc не оптимизирует упорядочение циклов, чтобы максимизировать последовательный доступ к памяти?
Ниже приведена упрощенная версия проблемы (обратите внимание, что время производительности сильно зависит от конфигурации L1. Указанные ниже цифры приведены для системы AMD с частотой 2,3 ГГц и двухканальной ассоциативной ассоциацией 64K L1, скомпилированной с -O3).
N = ARRAY_SIZE // 1024
int* mat_A = (int*)malloc(N*N*sizeof(int));
int* mat_B = (int*)malloc(N*N*sizeof(int));
int* mat_C = (int*)malloc(N*N*sizeof(int));
// Elements of mat_B are accessed in a stride pattern of length N
// This takes 800 msec
for (int t = 0; t < 1000; t++)
for (int a = 0; a < 32; a++)
for (int b = 0; b < 32; b++)
for (int c = 0; c < 32; c++)
mat_C[N*a+b] += mat_A[N*a+c] * mat_B[N*c+b];
// Inner two loops are swapped
// Elements are now accessed sequentially in inner loop
// This takes 172 msec
for (int t = 0; t < 1000; t++)
for (int a = 0; a < 32; a++)
for (int c = 0; c < 32; c++)
for (int b = 0; b < 32; b++)
mat_C[N*a+b] += mat_A[N*a+c] * mat_B[N*c+b];