G ++ достаточно умен, чтобы оптимизировать это? - PullRequest
2 голосов
/ 22 марта 2012

Представьте себе следующую ситуацию:

struct Args
{
  bool arg;
};
void thing(Args arg)
{
  if(arg.arg)
    cout<<"arg.arg is true\n";
  else
    cout<<"arg.arg is false\n";
}
int main()
{
  Args a;
  a.arg=false;
  thing(a);
}

Достаточно ли умен компилятор для удаления ветвей switch, if и else, которые, очевидно, никогда не будут вызываться в течение программы?Должна ли рассматриваемая переменная, которая управляет этими утверждениями, быть const?И, наконец, правильно ли вообще не использовать переменные, а использовать препроцессор (я боюсь мысли об этом коде)?

Просто чтобы прояснить, реальная ситуация такова, что я 'я пишу класс, в котором программист может выбрать, включить ли определенную функцию.Отключение этой функции может сэкономить много времени обработки на сервере и пропускную способность между классом и сервером.Я пытаюсь выяснить, следует ли мне использовать переменную в качестве аргумента конструктора, производной препроцессора или какого-либо другого решения.Я не хочу даже рассматривать логическую ветвь, если функция включена, если она отключена.Я знаю, что с помощью решения препроцессора это будет сделано, но я хочу избежать массового использования #ifdef, #elseif и хочу иметь возможность повторно использовать один согласованный общий объект.Доступ к исходному тексту для программиста не составляет проблем, поскольку он будет открытым.

РЕДАКТИРОВАТЬ: Я протестировал нижнюю линию компиляции и посмотрел на сборку.Я не могу полностью понять это, но я видел инструкцию перехода (jne).Вот сборка, если кто-нибудь может разобрать:

    .file   "blah.cpp"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "arg.arg is true\n"
.LC1:
    .string "arg.arg is false\n"
    .text
    .p2align 4,,15
.globl _Z5thing4Args
    .type   _Z5thing4Args, @function
_Z5thing4Args:
.LFB1003:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    movl    %esp, %ebp
    .cfi_offset 5, -8
    .cfi_def_cfa_register 5
    subl    $24, %esp
    cmpb    $0, 8(%ebp)
    jne .L5
    movl    $17, 8(%esp)
    movl    $.LC1, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    leave
    .cfi_remember_state
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .p2align 4,,7
    .p2align 3
.L5:
    .cfi_restore_state
    movl    $16, 8(%esp)
    movl    $.LC0, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    leave
    .cfi_def_cfa 4, 4
    .cfi_restore 5
    ret
    .cfi_endproc
.LFE1003:
    .size   _Z5thing4Args, .-_Z5thing4Args
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB1004:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    movl    %esp, %ebp
    .cfi_offset 5, -8
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    movl    $17, 8(%esp)
    movl    $.LC1, 4(%esp)
    movl    $_ZSt4cout, (%esp)
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    xorl    %eax, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1004:
    .size   main, .-main
    .p2align 4,,15
    .type   _GLOBAL__I__Z5thing4Args, @function
_GLOBAL__I__Z5thing4Args:
.LFB1009:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    movl    %esp, %ebp
    .cfi_offset 5, -8
    .cfi_def_cfa_register 5
    subl    $24, %esp
    movl    $_ZStL8__ioinit, (%esp)
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, 8(%esp)
    movl    $_ZStL8__ioinit, 4(%esp)
    movl    $_ZNSt8ios_base4InitD1Ev, (%esp)
    call    __cxa_atexit
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1009:
    .size   _GLOBAL__I__Z5thing4Args, .-_GLOBAL__I__Z5thing4Args
    .section    .ctors,"aw",@progbits
    .align 4
    .long   _GLOBAL__I__Z5thing4Args
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .weakref    _ZL20__gthrw_pthread_oncePiPFvvE,pthread_once
    .weakref    _ZL27__gthrw_pthread_getspecificj,pthread_getspecific
    .weakref    _ZL27__gthrw_pthread_setspecificjPKv,pthread_setspecific
    .weakref    _ZL22__gthrw_pthread_createPmPK14pthread_attr_tPFPvS3_ES3_,pthread_create
    .weakref    _ZL20__gthrw_pthread_joinmPPv,pthread_join
    .weakref    _ZL21__gthrw_pthread_equalmm,pthread_equal
    .weakref    _ZL20__gthrw_pthread_selfv,pthread_self
    .weakref    _ZL22__gthrw_pthread_detachm,pthread_detach
    .weakref    _ZL22__gthrw_pthread_cancelm,pthread_cancel
    .weakref    _ZL19__gthrw_sched_yieldv,sched_yield
    .weakref    _ZL26__gthrw_pthread_mutex_lockP15pthread_mutex_t,pthread_mutex_lock
    .weakref    _ZL29__gthrw_pthread_mutex_trylockP15pthread_mutex_t,pthread_mutex_trylock
    .weakref    _ZL31__gthrw_pthread_mutex_timedlockP15pthread_mutex_tPK8timespec,pthread_mutex_timedlock
    .weakref    _ZL28__gthrw_pthread_mutex_unlockP15pthread_mutex_t,pthread_mutex_unlock
    .weakref    _ZL26__gthrw_pthread_mutex_initP15pthread_mutex_tPK19pthread_mutexattr_t,pthread_mutex_init
    .weakref    _ZL29__gthrw_pthread_mutex_destroyP15pthread_mutex_t,pthread_mutex_destroy
    .weakref    _ZL30__gthrw_pthread_cond_broadcastP14pthread_cond_t,pthread_cond_broadcast
    .weakref    _ZL27__gthrw_pthread_cond_signalP14pthread_cond_t,pthread_cond_signal
    .weakref    _ZL25__gthrw_pthread_cond_waitP14pthread_cond_tP15pthread_mutex_t,pthread_cond_wait
    .weakref    _ZL30__gthrw_pthread_cond_timedwaitP14pthread_cond_tP15pthread_mutex_tPK8timespec,pthread_cond_timedwait
    .weakref    _ZL28__gthrw_pthread_cond_destroyP14pthread_cond_t,pthread_cond_destroy
    .weakref    _ZL26__gthrw_pthread_key_createPjPFvPvE,pthread_key_create
    .weakref    _ZL26__gthrw_pthread_key_deletej,pthread_key_delete
    .weakref    _ZL30__gthrw_pthread_mutexattr_initP19pthread_mutexattr_t,pthread_mutexattr_init
    .weakref    _ZL33__gthrw_pthread_mutexattr_settypeP19pthread_mutexattr_ti,pthread_mutexattr_settype
    .weakref    _ZL33__gthrw_pthread_mutexattr_destroyP19pthread_mutexattr_t,pthread_mutexattr_destroy
    .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
    .section    .note.GNU-stack,"",@progbits

РЕДАКТИРОВАТЬ: я посмотрел на сборку больше после добавления asm("#aksdjfh"), как предложено ниже, и я обнаружил, что компилятор не избавиться от него,Так #ifdef s единственный вариант?Или это инструкция jne, которую я могу эффективно игнорировать для производительности?

Ответы [ 7 ]

7 голосов
/ 22 марта 2012

Попробуйте сами:

$ g++ -O3 -S test.cpp -o test.s

-O3 включает оптимизацию, -S указывает компилятору остановиться после генерации кода сборки, а -o выбирает, где разместить выходные данные. Затем вы можете проверить файл "test.s" и посмотреть, оптимизировал ли он его или нет. Очевидно, что это требует определенных знаний по сборке. Вы также можете захотеть -masm=intel, если вы, как и я, сочтете синтаксис AT & T нечитаемым и предпочитаете синтаксис Intel.

Это может помочь добавить строки типа asm("# this is something") в код. Они будут отображаться как комментарии в сгенерированной сборке, что может упростить идентификацию интересующих вас частей.

На моей машине снимок GCC 4.8, похоже, не оптимизирует этот мертвый код. Я добавил один из этих asm-комментариев в каждую ветку, чтобы распознать их, и он сгенерировал это:

    .file   "test.cpp"
    .intel_syntax noprefix
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "arg.arg is true\n"
.LC1:
    .string "arg.arg is false\n"
    .text
    .p2align 4,,15
    .globl  _Z5thing4Args
    .type   _Z5thing4Args, @function
_Z5thing4Args:
.LFB1215:
    .cfi_startproc
    sub esp, 28
    .cfi_def_cfa_offset 32
    cmp BYTE PTR [esp+32], 0
    jne .L6
#APP
# 13 "test.cpp" 1
    This is the false branch
# 0 "" 2
#NO_APP
    mov DWORD PTR [esp+8], 17
    mov DWORD PTR [esp+4], OFFSET FLAT:.LC1
    mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    add esp, 28
    .cfi_remember_state
    .cfi_def_cfa_offset 4
    ret
    .p2align 4,,7
    .p2align 3
.L6:
    .cfi_restore_state
#APP
# 10 "test.cpp" 1
    This is the true branch
# 0 "" 2
#NO_APP
    mov DWORD PTR [esp+8], 16
    mov DWORD PTR [esp+4], OFFSET FLAT:.LC0
    mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    add esp, 28
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
.LFE1215:
    .size   _Z5thing4Args, .-_Z5thing4Args
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1216:
    .cfi_startproc
    push    ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    mov ebp, esp
    .cfi_def_cfa_register 5
    and esp, -16
    sub esp, 16
    mov BYTE PTR [esp], 0
    call    _Z5thing4Args
    xor eax, eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1216:
    .size   main, .-main
    .p2align 4,,15
    .type   _GLOBAL__sub_I__Z5thing4Args, @function
_GLOBAL__sub_I__Z5thing4Args:
.LFB1367:
    .cfi_startproc
    sub esp, 28
    .cfi_def_cfa_offset 32
    mov DWORD PTR [esp], OFFSET FLAT:_ZStL8__ioinit
    call    _ZNSt8ios_base4InitC1Ev
    mov DWORD PTR [esp+8], OFFSET FLAT:__dso_handle
    mov DWORD PTR [esp+4], OFFSET FLAT:_ZStL8__ioinit
    mov DWORD PTR [esp], OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
    call    __cxa_atexit
    add esp, 28
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
.LFE1367:
    .size   _GLOBAL__sub_I__Z5thing4Args, .-_GLOBAL__sub_I__Z5thing4Args
    .section    .init_array,"aw"
    .align 4
    .long   _GLOBAL__sub_I__Z5thing4Args
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (GNU) 4.8.0 20120311 (experimental)"
    .section    .note.GNU-stack,"",@progbits

Если вы посмотрите на эти комментарии, вы найдете их оба с последующим вызовом некоторой std::cout функции-члена.

Это происходит потому, что как есть, функция видна в других единицах перевода: если вы сейчас создаете файл nasty.cpp с объявлением void thing(Args arg); и вызовом со значением true, код должен существует.

Так что я немного поэкспериментировал. Если я отмечу функцию как static, что означает, что она является внутренней для этого модуля перевода, GCC действительно оптимизирует мертвый код:

    .file   "test.cpp"
    .intel_syntax noprefix
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "arg.arg is false\n"
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1216:
    .cfi_startproc
    push    ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    mov ebp, esp
    .cfi_def_cfa_register 5
    and esp, -16
    sub esp, 16
#APP
# 13 "test.cpp" 1
    This is the false branch
# 0 "" 2
#NO_APP
    mov DWORD PTR [esp+4], OFFSET FLAT:.LC0
    mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    xor eax, eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1216:
    .size   main, .-main
    .p2align 4,,15
    .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1367:
    .cfi_startproc
    sub esp, 28
    .cfi_def_cfa_offset 32
    mov DWORD PTR [esp], OFFSET FLAT:_ZStL8__ioinit
    call    _ZNSt8ios_base4InitC1Ev
    mov DWORD PTR [esp+8], OFFSET FLAT:__dso_handle
    mov DWORD PTR [esp+4], OFFSET FLAT:_ZStL8__ioinit
    mov DWORD PTR [esp], OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
    call    __cxa_atexit
    add esp, 28
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
.LFE1367:
    .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
    .section    .init_array,"aw"
    .align 4
    .long   _GLOBAL__sub_I_main
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (GNU) 4.8.0 20120311 (experimental)"
    .section    .note.GNU-stack,"",@progbits

В этом коде вы не найдете "Это истинная ветвь". Также обратите внимание, как ложная ветвь была перемещена в функцию main, а функция thing больше не существует. GCC просто вставил код функции и не стал его генерировать, потому что теперь он не будет использоваться где-либо еще, поскольку я добавил static.

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

1 голос
/ 22 марта 2012

Этот тип оптимизации на самом деле требует совсем немного, и ответ может быть другим, когда код становится немного сложнее.

Просто чтобы прояснить, реальная ситуация в том, что я пишу класс, где программист может выбрать, включить ли определенную функцию. Отключение этой функции может сэкономить много времени обработки на сервере и пропускную способность между классом и сервером. Я пытаюсь выяснить, следует ли мне использовать переменную в качестве аргумента конструктора, производной препроцессора или какого-либо другого решения.

Возможно, вы рассмотрите возможность использования Политики , популяризированной Андре Александреску.

1 голос
/ 22 марта 2012

Я не знаю о gcc, но Clang ... так что gcc, вероятно, тоже!

#include <stdio.h>

struct Args
{
  bool arg;
};
static void thing(Args arg)
{
  if(arg.arg)
    printf("arg.arg is true\n");
  else
    printf("arg.arg is false\n");
}
int main()
{
  Args a;
  a.arg=false;
  thing(a);
}

Примечание: использование iostreams загромождает вывод, поэтому я изменил его на стиль печати printf.Я также добавил static, чтобы избежать выделения функции, она все еще была встроена без нее.

Генерируется следующий IR:

@str = internal constant [17 x i8] c"arg.arg is false\00"

define i32 @main() nounwind uwtable {
  %puts.i = tail call i32 @puts(i8* getelementptr inbounds ([17 x i8]* @str, i64 0, i64 0)) nounwind
  ret i32 0
}

Вы заметите, что:

  • thing полностью встроен
  • thing не излучается (static эффект)
  • "arg.arg is true\n" даже не сохраняется (static эффект)

Требуемое имя оптимизации: Постоянное распространение .

1 голос
/ 22 марта 2012

Короткий ответ - нет (по крайней мере, версия gcc, которая мне нужна).

Длинный ответ в значительной степени такой же, как сказал Р. Мартиньо Фернандес: чтобы выяснить подобные вещи, пусть компилятор выводит язык ассемблера (-S в командной строке gcc), а затем исследует его язык ассемблера. В этом случае соответствующая часть выглядит следующим образом:

Данные:

LC0:
    .ascii "arg.arg is true\12\0"
LC1:
    .ascii "arg.arg is false\12\0"

Код:

LCFI2:
    cmpb    $0, 8(%ebp)
    jne L5
    movl    $17, 8(%esp)
    movl    $LC1, 4(%esp)
    movl    $__ZSt4cout, (%esp)
    call    __ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    leave
    ret

L5:
    movl    $16, 8(%esp)
    movl    $LC0, 4(%esp)
    movl    $__ZSt4cout, (%esp)
    call    __ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    leave

cmpb $0, 8(%ebp) по сути if (whatever==false). В зависимости от результата этого сравнения он продолжит выполнение кода ниже LCFI2, который печатает LC1, или L5, который печатает LC0.

0 голосов
/ 22 марта 2012

Поскольку другие ответы отвечают на вопрос напрямую, вот еще один способ обойти вашу проблему: вы можете использовать шаблоны.Они всегда будут оптимизированы так, как вы хотите, потому что они технически две разные функции.

    template<bool do_thing_1>
    void thing(std::integral_constant<bool, do_thing_1>)
    {
        if (do_thing_1)
            cout<<"arg.arg is true\n";
        else
            cout<<"arg.arg is false\n";
    }
    int main() {
        thing(std::true_type()); 
        thing(std::false_type());
    }

Если вы хотите, чтобы она зависела от переменной, то нет никакой оптимизации,то, что у тебя есть, лучше всего.

0 голосов
/ 22 марта 2012

Существует множество факторов, которые могут позволить или нет компилятору оптимизировать вызовы функций.

Прежде всего, нет никаких доказательств того, что функция 'вещь' нигде не вызывается, поэтому она будетконечно, не может быть встроен автоматически, если только он не был объявлен статическим (чтобы сделать его локальным для текущей единицы перевода).Вы можете дать эти подсказки компилятору, явно добавив модификатор inline при объявлении функции thing.Встроенный модификатор позволит компилятору раскрыть код функции, где она вызывается.В вашем случае, вставка, безусловно, оптимизирует код под if, и выживет только второй «cout».

Однако я бы не стал рассчитывать на такое поведение для включения / выключения функции времени компиляции.

По сути, это делает основную логику кода неотличимой от конфигурации.Это усложнит отладку.

Хотя я склонен согласиться с тем, что многие # ifdef / # endif не совсем симпатичны, они чрезвычайно распространены в ситуациях, подобных вашей., который я лично склонен использовать, когда это возможно:

Если «функции», которые необходимо включить / отключить, могут быть достаточно хорошо изолированы, вы можете создавать классы или функции, которые представляют каждое возможное поведение.Поместите каждый вариант в свой собственный файл cpp и используйте свою систему конфигурации сборки, чтобы выбрать файл для компиляции во время сборки.

0 голосов
/ 22 марта 2012

Да, но только если полный путь виден компилятору (и оптимизация включена). Вы можете проверить это с помощью флага GCC -S. Оптимизация во время соединения (-flto для последних версий GCC) тоже может сделать это, но я никогда не проверял.

Редактировать 0:

В качестве альтернативы #ifdef magic в источнике рассмотрите возможность разделения функциональности по разным файлам реализации и используйте make(1), чтобы выбрать правильный во время сборки.

...