TL; DR: первый цикл работает на 18% быстрее на процессоре Haswell. Зачем? Петли из циклов gcc -O0
(не оптимизированы) с использованием ptr++
vs ++ptr
, но возникает вопрос, почему результирующий asm выполняет по-разному, а не о том, как лучше писать C.
Скажем, мы имеем эти две петли:
movl $0, -48(%ebp) //Loop counter set to 0
movl $_data, -12(%ebp) //Pointer to the data array
movl %eax, -96(%ebp)
movl %edx, -92(%ebp)
jmp L21
L22:
// ptr++
movl -12(%ebp), %eax //Get the current address
leal 4(%eax), %edx //Calculate the next address
movl %edx, -12(%ebp) //Store the new (next) address
// rest of the loop is the same as the other
movl -48(%ebp), %edx //Get the loop counter to edx
movl %edx, (%eax) //Move the loop counter value to the CURRENT address, note -12(%ebp) contains already the next one
addl $1, -48(%ebp) //Increase the counter
L21:
cmpl $999999, -48(%ebp)
jle L22
а второй:
movl %eax, -104(%ebp)
movl %edx, -100(%ebp)
movl $_data-4, -12(%ebp) //Address of the data - 1 element (4 byte)
movl $0, -48(%ebp) //Set the loop counter to 0
jmp L23
L24:
// ++ptr
addl $4, -12(%ebp) //Calculate the CURRENT address by adding one sizeof(int)==4 bytes
movl -12(%ebp), %eax //Store in eax the address
// rest of the loop is the same as the other
movl -48(%ebp), %edx //Store in edx the current loop counter
movl %edx, (%eax) //Move the loop counter value to the current stored address location
addl $1, -48(%ebp) //Increase the loop counter
L23:
cmpl $999999, -48(%ebp)
jle L24
Эти петли делают точно то же самое, но немного по-другому, пожалуйста, обратитесь к комментарию для деталей.
Этот код asm создается из следующих двух циклов С++:
//FIRST LOOP:
for(;index<size;index++){
*(ptr++) = index;
}
//SECOND LOOP:
ptr = data - 1;
for(index = 0;index<size;index++){
*(++ptr) = index;
}
Теперь первый цикл примерно на 18% быстрее, чем второй, независимо от того, в каком порядке выполняются петли, один с ptr++
быстрее, чем тот, у которого ++ptr
.
Чтобы запустить мои тесты, я просто собрал время выполнения этих циклов разного размера и выполнил их как вложенные в другие циклы, так и повторить операцию часто.
Анализ ASM
Глядя на код ASM, второй цикл содержит меньше инструкций, у нас есть 3 movl и 2 addl, тогда как в первом цикле мы имеем 4 movl один addl и один leal, поэтому у нас есть один movl больше и один leal вместо addl
Правильно ли, что операция LEA
для вычисления правильного адреса намного быстрее, чем метод ADD
(+4)? Является ли это причиной разницы в производительности?
Насколько я знаю, как только новый адрес вычисляется до того, как память может быть указана, некоторые циклы часов должны пройти, поэтому второй цикл после добавления $4, -12 (% ebp) должен немного подождать, прежде чем продолжить, тогда как в первом цикле мы можем сразу ссылаться на память, а в то же время LEAL будет вычислять следующий адрес (какая-то лучшая производительность конвейера здесь).
Есть ли какое-то переупорядочение? Я не уверен в моем объяснении различий в производительности этих циклов, могу ли я высказать ваше мнение?