Объявить функцию без побочных эффектов

C ++ не является функциональным языком программирования. Функции, вызывающие побочные эффекты, являются нормой для функций, регулярно изменяющих переменные-члены. Если компилятор не видит тела функции, он предполагает, что функция вызывает побочные эффекты. Это может привести к упущению возможностей оптимизации.

Упущенная возможность для оптимизации

Рассмотрим следующий код. Здесь функция main (почему-то) дважды вычисляет квадрат общего числа аргументов и возвращает сумму квадратов.

int square(int x);
int main(int argc, char *argv[])
{
    auto x = square(argc);
    auto y = square(argc);
    return x+y;
}

Здесь компилятор не видит тела функции square, поэтому он предполагает, что функция может вызывать побочные эффекты. В следующем ассемблерном коде показаны два вызова функции добавления.

Компилятор не смог оптимизировать второй вызов, так как он не может предположить, что квадратная функция не имеет побочных эффектов.

main:
        push    rbp
        mov     ebp, edi
        push    rbx
        sub     rsp, 8
        call    square(int)
        mov     edi, ebp
        mov     ebx, eax
        call    square(int)
        add     rsp, 8
        add     eax, ebx
        pop     rbx
        pop     rbp
        ret

Оптимизация с атрибутом чистой функции

Если бы существовал способ сообщить компилятору, что функция является чистой (т.е. без побочных эффектов), компилятор мог бы использовать эту информацию для оптимизации кода.

Если мы просто добавим атрибут [[gnu::pure]] к функции square, компилятор будет считать, что функция не вызывает побочных эффектов.

Когда функция объявляется чистой, компилятор также гарантирует, что:

  • Функция не обращается к глобальным переменным и не обновляет их.
  • Возвращаемое значение функции просто зависит от входных параметров.
[[gnu::pure]] int square(int x);
int main(int argc, char *argv[])
{
    auto x = square(argc);
    auto y = square(argc);
    return x+y;
}

Это приводит к значительной оптимизации, и компилятор может просто вызвать функцию square один раз и просто удвоить результат - add eax, eax. Чистое объявление гарантирует, что несколько вызовов функции с одинаковыми параметрами всегда будут приводить к одному и тому же возвращаемому значению.

main:
        sub     rsp, 8
        call    square(int)
        add     rsp, 8
        add     eax, eax
        ret

На момент написания этой статьи атрибут [[gnu::pure]] поддерживается компиляторами GCC и Clang.

Учить больше

Еженедельное видео Джейсона Тернера по C ++, которое легло в основу этого сообщения.

Ссылки на обозреватель компилятора для примера, представленного выше: