Скорость выполнения программы C
У меня есть одна проблема на моем экзамене по предмету Принципала языка программирования. Я долго думал, но я все еще не понимал проблемы
проблема: Ниже приведена программа C, которая выполняется в среде MSVC++ 6.0 на ПК с конфигурацией ~ CPU Intel 1.8 GHz, Ram 512MB
#define M 10000
#define N 5000
int a[M][N];
void main() {
int i, j;
time_t start, stop;
// Part A
start = time(0);
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
a[i][j] = 0;
stop = time(0);
printf("%dn", stop - start);
// Part B
start = time(0);
for (j = 0; j < N; j++)
for (i = 0; i < M; i++)
a[i][j] = 0;
stop = time(0);
printf("%dn", stop - start);
}
объясните, почему часть a выполняется только в 1s, но он принял участие B 8s до конца?
5 ответов
строкам против столбцам.
сначала вспомните, что все многомерные массивы представлены в памяти как continguous блок памяти. Таким образом,многомерный массив A(m, n) может быть представлен в памяти как
a00 a01 a02 ... a0n a10 a11 a12 ... a1n a20 ... АМН
в первом цикле вы последовательно запускаете этот блок памяти. Таким образом, вы проходите через массив, пересекая элементы в следующем заказ
a00 a01 a02 ... a0n a10 a11 a12 ... a1n a20 ... amn
1 2 3 n n+1 n+2 n+3 ... 2n 2n+1 mn
во втором цикле вы пропускаете в памяти и проходите через массив, пересекая элементы в следующем порядке
a00 a10 a20 ... am0 a01 a11 a21 ... am1 a02 ... amn
или, возможно, более четко,
a00 a01 a02 ... a10 a11 a12 ... a20 ... amn
1 m+1 2m+1 2 m+2 2m+2 3 mn
все, что пропуск вокруг действительно вредит вам, потому что вы не получаете преимуществ от кэширования. При последовательном выполнении массива соседние элементы загружаются в кэш. Когда вы пропускаете через массив, вы не получаете этих преимуществ и вместо этого получаю промахи кэша ущерба производительности.
Это связано с тем, как выкладывается память массива и как она загружается в кэш и получает доступ: в версии A при доступе к ячейке массива соседи загружаются с ней в кэш, а затем код немедленно обращается к этим соседям. В версии B доступ к одной ячейке (и ее соседям, загруженным в кэш), но следующий доступ находится далеко, в следующей строке, и поэтому вся строка кэша была загружена, но использовалось только одно значение, а другая строка кэша должна быть заполняется для каждого доступа. Отсюда и разница в скорости.
из - за аппаратной архитектурной оптимизации. Часть A выполняет операции с последовательными адресами памяти, что позволяет аппаратному обеспечению существенно ускорить обработку вычислений. Часть B в основном прыгает в памяти все время, что побеждает много аппаратных оптимизаций.
ключевым понятием для этого конкретного случая является кэш процессора.
массив, который вы объявляете, выложен линейно в памяти. В основном у вас есть большой блок m×n целых чисел, и C делает небольшую хитрость, чтобы заставить вас поверить, что он прямоугольный. Но на самом деле она плоская.
поэтому, когда вы итерируете через него линейно (с M в качестве переменной внешнего цикла), вы действительно идете линейно через память. Что-то, что кэш CPU обрабатывает очень хорошо.
, когда вы переходите с N во внешнем цикле, то вы всегда делает более или менее случайные скачки в памяти (по крайней мере, для аппаратного обеспечения это выглядит так). Вы получаете доступ к первой ячейке, затем перемещаете M целых чисел дальше и делаете то же самое и т. д. Поскольку ваши страницы в памяти обычно составляют около 4 Кб, это вызывает доступ к другой странице для каждого итерация внутреннего цикла. Таким образом, почти любая стратегия кэширования терпит неудачу, и вы видите значительное замедление.проблема здесь в том, как Ваш массив заложен в память.
в памяти компьютера обычно выделяются массивы, такие как, что сначала все столбцы первой строки объединяются, затем второй строки и так далее.
память вашего компьютера лучше всего рассматривать как длинную полосу байтов - это одномерный массив памяти - не двумерный, поэтому многомерные массивы должны быть выделены описанным способом.
сейчас идет дальнейшая проблема: современные процессоры имеют кэши. У них есть несколько кэшей, и они имеют так называемые "линии кэша" для кэша первого уровня. Что это значит. Доступ к памяти быстрый, но недостаточно быстрый. Современные процессоры работают намного быстрее. Так у них на кристалле кэш который ускорит. Кроме того, они больше не обращаются к отдельным ячейкам памяти, но они заполняют одну полную строку кэша в одной выборке. Это также для производительности. Но это поведение дает все преимущества операций, которые обрабатывают данные линейно. Когда вы обращаетесь сначала ко всем столбцам в строке, затем к следующей строке и так далее-фактически вы работаете линейно. Когда вы сначала обрабатываете все первые столбцы всех строк, вы "прыгаете" в памяти. Таким образом, вы всегда заставляете новую строку кэша заполняться, может быть обработано всего несколько байтов, тогда строка кэша, возможно, недействительна при следующем прыжке ....
таким образом, column-major-order плох для современных процессоров, так как он не работает линейно.