Я видел этот блог:
http://igoro.com/archive/gallery-of-processor-cache-effects/
"странность" в части 7 - это то, что меня заинтересовало.
Моя первая мысль была "Thats просто С# быть странным".
Не я написал следующий код на С++.
volatile int* p = (volatile int*)_aligned_malloc( sizeof( int ) * 8, 64 );
memset( (void*)p, 0, sizeof( int ) * 8 );
double dStart = t.GetTime();
for (int i = 0; i < 200000000; i++)
{
//p[0]++;p[1]++;p[2]++;p[3]++; // Option 1
//p[0]++;p[2]++;p[4]++;p[6]++; // Option 2
p[0]++;p[2]++; // Option 3
}
double dTime = t.GetTime() - dStart;
Время, которое я получаю на своем 2.4 ГГц Core 2 Quad, выглядит следующим образом:
Option 1 = ~8 cycles per loop.
Option 2 = ~4 cycles per loop.
Option 3 = ~6 cycles per loop.
Теперь это запутанно. Мое рассуждение о различии сводится к задержке записи кэша (3 цикла) на моем чипе и предположению, что кэш имеет 128-битный порт записи (это чистая работа над моей работой).
На этом основании в Варианте 1: он увеличит p [0] (1 цикл), затем увеличит p [2] (1 цикл), тогда он должен ждать 1 цикл (для кеша), затем p [1] (1 цикл), затем подождите 1 цикл (для кеша), затем p [3] (1 цикл). Наконец, 2 цикла для приращения и прыжка (хотя обычно он применяется как декремент и прыжок). Это дает всего 8 циклов.
В Варианте 2: он может увеличивать p [0] и p [4] за один цикл, а затем увеличивать p [2] и p [6] в другом цикле. Затем два цикла для вычитания и прыжка. В кеше нет необходимости. Всего 4 цикла.
В варианте 3: он может увеличивать p [0], тогда ему нужно подождать 2 цикла, а затем увеличить p [2], затем вычесть и перейти. Проблема в том, что если вы установили case 3 для увеличения p [0] и p [4], STILL принимает 6 циклов (что вызывает удаление моего 128-битного порта чтения/записи из воды).
Итак... может ли кто-нибудь сказать мне, что, черт возьми, здесь происходит? Почему дела 3 занимают больше времени? Также мне хотелось бы знать, в чем я ошибался в своем мышлении выше, поскольку у меня, очевидно, что-то не так! Любые идеи были бы высоко оценены!:)
Также было бы интересно посмотреть, как GCC или любой другой компилятор справляется с ним!
Редактировать: идея Джерри Коффина дала мне несколько мыслей.
Я сделал еще несколько тестов (на другой машине, так что простите изменение таймингов) с и без nops и с разным количеством nops
case 2 - 0.46 00401ABD jne (401AB0h)
0 nops - 0.68 00401AB7 jne (401AB0h)
1 nop - 0.61 00401AB8 jne (401AB0h)
2 nops - 0.636 00401AB9 jne (401AB0h)
3 nops - 0.632 00401ABA jne (401AB0h)
4 nops - 0.66 00401ABB jne (401AB0h)
5 nops - 0.52 00401ABC jne (401AB0h)
6 nops - 0.46 00401ABD jne (401AB0h)
7 nops - 0.46 00401ABE jne (401AB0h)
8 nops - 0.46 00401ABF jne (401AB0h)
9 nops - 0.55 00401AC0 jne (401AB0h)
Я включил statetements перехода, чтобы вы могли видеть, что источник и получатель находятся в одной строке кэша. Вы также можете видеть, что мы начинаем получать разницу, когда мы имеем 13 байт или больше друг от друга. Пока мы не достигнем 16... тогда все идет не так.
Итак, Джерри не прав (хотя его предложение немного помогает), однако что-то происходит. Я все больше и больше заинтригован, чтобы попытаться выяснить, что это такое. Похоже, что это скорее какая-то странность, а не какая-то странность.
Кто-нибудь хочет объяснить это любопытным умом?: D
Редактировать 3: Interjay имеет точку разматывания, которая удаляет предыдущее редактирование из воды. С развернутым контуром производительность не улучшается. Вам нужно добавить nop, чтобы сделать промежуток между источником перехода и пунктом назначения таким же, как и для моего хорошего nop-счета выше. Производительность все еще отстойная. Интересно, что мне нужно 6 ночей, чтобы улучшить производительность. Интересно, сколько процессоров может выдавать за цикл? Если его 3, то это объясняет задержку записи кэша... Но, если это так, почему возникает латентность?
Любопытный и любопытный...