Почему разыменование делает мою программу быстрее? - программирование
Подтвердить что ты не робот

Почему разыменование делает мою программу быстрее?

Учитывая следующие тестовые программы:

Значение цикла в стеке

int main( void ) {
    int iterations = 1000000000;

    while ( iterations > 0 )
        -- iterations;
}

Значение цикла в стеке (разыменовано)

int main( void ) {
    int iterations = 1000000000;
    int * p = & iterations;

    while ( * p > 0 )
        -- * p;
}

Значение цикла в куче

#include <stdlib.h>

int main( void ) {
    int * p = malloc( sizeof( int ) );
    * p = 1000000000;

    while ( *p > 0 )
        -- * p;
}

Скомпилировав их с -O0, я получаю следующее время выполнения:

case1.c
real    0m2.698s
user    0m2.690s
sys     0m0.003s

case2.c
real    0m2.574s
user    0m2.567s
sys     0m0.000s

case3.c
real    0m2.566s
user    0m2.560s
sys     0m0.000s

[править] Ниже приведено среднее значение для 10 исполнений:

case1.c
2.70364

case2.c
2.57091

case3.c
2.57000

Почему время выполнения больше с первым тестовым примером, который кажется самым простым?

Моя текущая архитектура - это виртуальная машина x86 (Archlinux). Я получаю эти результаты как с gcc (4.8.0), так и с clang (3.3).

[edit 1] Сгенерированные коды ассемблера почти идентичны, за исключением того, что второй и третий имеют больше инструкций, чем первый.

[edit 2] Эти характеристики воспроизводятся (в моей системе). Каждое исполнение будет иметь тот же порядок величины.

[edit 3] Мне действительно не нравятся выступления неоптимизированной программы, но я не понимаю, почему это было бы медленнее, и мне любопытно.

4b9b3361

Ответ 1

Трудно сказать, если это причина, потому что я делаю некоторые догадки, и вы не указали некоторые особенности (например, какую цель вы используете). Но то, что я вижу при компиляции без оптимизации с целью x86, - это следующие последовательности для уменьшения переменной iterations:

Случай 1:

L3:
    sub DWORD PTR [esp+12], 1
L2:
    cmp DWORD PTR [esp+12], 0
    jg  L3

Случай 2:

L3:
    mov eax, DWORD PTR [esp+12]
    mov eax, DWORD PTR [eax]
    lea edx, [eax-1]
    mov eax, DWORD PTR [esp+12]
    mov DWORD PTR [eax], edx
L2:
    mov eax, DWORD PTR [esp+12]
    mov eax, DWORD PTR [eax]
    test    eax, eax
    jg  L3

Одна большая разница, которую вы видите в случае 1, заключается в том, что команда в L3 считывает и записывает местоположение памяти. За ней сразу следует инструкция, которая считывает то же самое место памяти, которое было написано. Такая последовательность инструкций (то же место памяти, записанное, а затем сразу используемое в следующей инструкции) часто вызывает какой-то конвейерный останов в современных процессорах.

Вы заметите, что запись, последовавшая сразу после чтения того же места, отсутствует в случае 2.

Опять же - этот ответ - немного информированное предположение.