Я могу воспроизвести результаты на своей машине с теми вариантами, которые вы пишете в своем посте.
Однако, если я также включить link time optimization (я также передать -flto
флаг GCC 4.7.2), результаты одинаковы:
(Я компиляции исходного кода, с container.push_back(Item());
)
$ g++ -std=c++11 -O3 -flto regr.cpp && perf stat -r 10 ./a.out
Performance counter stats for './a.out' (10 runs):
35.426793 task-clock # 0.986 CPUs utilized (+- 1.75%)
4 context-switches # 0.116 K/sec (+- 5.69%)
0 CPU-migrations # 0.006 K/sec (+- 66.67%)
19,801 page-faults # 0.559 M/sec
99,028,466 cycles # 2.795 GHz (+- 1.89%) [77.53%]
50,721,061 stalled-cycles-frontend # 51.22% frontend cycles idle (+- 3.74%) [79.47%]
25,585,331 stalled-cycles-backend # 25.84% backend cycles idle (+- 4.90%) [73.07%]
141,947,224 instructions # 1.43 insns per cycle
# 0.36 stalled cycles per insn (+- 0.52%) [88.72%]
37,697,368 branches # 1064.092 M/sec (+- 0.52%) [88.75%]
26,700 branch-misses # 0.07% of all branches (+- 3.91%) [83.64%]
0.035943226 seconds time elapsed (+- 1.79%)
$ g++ -std=c++98 -O3 -flto regr.cpp && perf stat -r 10 ./a.out
Performance counter stats for './a.out' (10 runs):
35.510495 task-clock # 0.988 CPUs utilized (+- 2.54%)
4 context-switches # 0.101 K/sec (+- 7.41%)
0 CPU-migrations # 0.003 K/sec (+-100.00%)
19,801 page-faults # 0.558 M/sec (+- 0.00%)
98,463,570 cycles # 2.773 GHz (+- 1.09%) [77.71%]
50,079,978 stalled-cycles-frontend # 50.86% frontend cycles idle (+- 2.20%) [79.41%]
26,270,699 stalled-cycles-backend # 26.68% backend cycles idle (+- 8.91%) [74.43%]
141,427,211 instructions # 1.44 insns per cycle
# 0.35 stalled cycles per insn (+- 0.23%) [87.66%]
37,366,375 branches # 1052.263 M/sec (+- 0.48%) [88.61%]
26,621 branch-misses # 0.07% of all branches (+- 5.28%) [83.26%]
0.035953916 seconds time elapsed
По причинам необходимо посмотреть сгенерированный код сборки (g++ -std=c++11 -O3 -S regr.cpp
). В 11 режиме генерируемого кода C++ значительно более загроможденным, чем для C++, 98 Режим и встраивание функции
void std::vector<Item,std::allocator<Item>>::_M_emplace_back_aux<Item>(Item&&)
терпит неудачу в режиме C++ 11 с по умолчанию inline-limit
.
Это не удалось встроить эффект домино. Не потому, что эта функция вызывается (она даже не называется!), Но потому, что мы должны быть готовы: Если это называется, функция argments (Item.a
и Item.b
) уже должны быть в нужном месте. Это приводит к довольно грязный код.
Вот соответствующая часть сгенерированного кода для случая, когда встраивания преуспевает:
.L42:
testq %rbx, %rbx # container$D13376$_M_impl$_M_finish
je .L3 #,
movl $0, (%rbx) #, container$D13376$_M_impl$_M_finish_136->a
movl $0, 4(%rbx) #, container$D13376$_M_impl$_M_finish_136->b
.L3:
addq $8, %rbx #, container$D13376$_M_impl$_M_finish
subq $1, %rbp #, ivtmp.106
je .L41 #,
.L14:
cmpq %rbx, %rdx # container$D13376$_M_impl$_M_finish, container$D13376$_M_impl$_M_end_of_storage
jne .L42 #,
Это хорошее и компактный цикл. Теперь давайте сравним это к тому, что из не удалось INLINE случая:
.L49:
testq %rax, %rax # D.15772
je .L26 #,
movq 16(%rsp), %rdx # D.13379, D.13379
movq %rdx, (%rax) # D.13379, *D.15772_60
.L26:
addq $8, %rax #, tmp75
subq $1, %rbx #, ivtmp.117
movq %rax, 40(%rsp) # tmp75, container.D.13376._M_impl._M_finish
je .L48 #,
.L28:
movq 40(%rsp), %rax # container.D.13376._M_impl._M_finish, D.15772
cmpq 48(%rsp), %rax # container.D.13376._M_impl._M_end_of_storage, D.15772
movl $0, 16(%rsp) #, D.13379.a
movl $0, 20(%rsp) #, D.13379.b
jne .L49 #,
leaq 16(%rsp), %rsi #,
leaq 32(%rsp), %rdi #,
call _ZNSt6vectorI4ItemSaIS0_EE19_M_emplace_back_auxIIS0_EEEvDpOT_ #
Этого код суматоха и есть гораздо больше происходит в цикле, чем в предыдущем случае. Перед функция call
(последняя строка показана), аргументы должны быть размещены соответствующим образом:
leaq 16(%rsp), %rsi #,
leaq 32(%rsp), %rdi #,
call _ZNSt6vectorI4ItemSaIS0_EE19_M_emplace_back_auxIIS0_EEEvDpOT_ #
Даже если это фактически никогда не выполняется, то цикл раскладывает вещи перед:
movl $0, 16(%rsp) #, D.13379.a
movl $0, 20(%rsp) #, D.13379.b
Это приводит к грязному коду. Если нет функции call
, потому что inlining преуспевает, у нас есть только 2 команды перемещения в цикле, и нет никакого беспорядка с %rsp
(указатель стека). Однако, если вложение не удается, мы получаем 6 ходов, и мы много играем с %rsp
.
Просто, чтобы обосновать свою теорию (обратите внимание на -finline-limit
), как в режиме C++ 11:
$ g++ -std=c++11 -O3 -finline-limit=105 regr.cpp && perf stat -r 10 ./a.out
Performance counter stats for './a.out' (10 runs):
84.739057 task-clock # 0.993 CPUs utilized (+- 1.34%)
8 context-switches # 0.096 K/sec (+- 2.22%)
1 CPU-migrations # 0.009 K/sec (+- 64.01%)
19,801 page-faults # 0.234 M/sec
266,809,312 cycles # 3.149 GHz (+- 0.58%) [81.20%]
206,804,948 stalled-cycles-frontend # 77.51% frontend cycles idle (+- 0.91%) [81.25%]
129,078,683 stalled-cycles-backend # 48.38% backend cycles idle (+- 1.37%) [69.49%]
183,130,306 instructions # 0.69 insns per cycle
# 1.13 stalled cycles per insn (+- 0.85%) [85.35%]
38,759,720 branches # 457.401 M/sec (+- 0.29%) [85.43%]
24,527 branch-misses # 0.06% of all branches (+- 2.66%) [83.52%]
0.085359326 seconds time elapsed (+- 1.31%)
$ g++ -std=c++11 -O3 -finline-limit=106 regr.cpp && perf stat -r 10 ./a.out
Performance counter stats for './a.out' (10 runs):
37.790325 task-clock # 0.990 CPUs utilized (+- 2.06%)
4 context-switches # 0.098 K/sec (+- 5.77%)
0 CPU-migrations # 0.011 K/sec (+- 55.28%)
19,801 page-faults # 0.524 M/sec
104,699,973 cycles # 2.771 GHz (+- 2.04%) [78.91%]
58,023,151 stalled-cycles-frontend # 55.42% frontend cycles idle (+- 4.03%) [78.88%]
30,572,036 stalled-cycles-backend # 29.20% backend cycles idle (+- 5.31%) [71.40%]
140,669,773 instructions # 1.34 insns per cycle
# 0.41 stalled cycles per insn (+- 1.40%) [88.14%]
38,117,067 branches # 1008.646 M/sec (+- 0.65%) [89.38%]
27,519 branch-misses # 0.07% of all branches (+- 4.01%) [86.16%]
0.038187580 seconds time elapsed (+- 2.05%)
Действительно, если попросить компилятор, чтобы попытаться просто немного сложнее встраивать эту функцию, разница в производительности уходит.
Так что же забрать эту историю? Эти неудачные строки могут стоить очень дорого, и вы должны полностью использовать возможности компилятора: Я могу только рекомендовать оптимизацию времени ссылки. Он дал значительное повышение производительности моим программам (до 2,5x), и все, что мне нужно было сделать, это передать флаг -flto
. Это очень хорошая сделка! ;)
Тем не менее, я не рекомендую уничтожать код с помощью встроенного ключевого слова; пусть компилятор решит, что делать. (Оптимизатору разрешено обрабатывать ключевое слово inline в качестве пробела в любом случае.)
Большой вопрос, +1!
Если вы скомпилируете сборку, вы увидите, что происходит под капотом. См. Также http://stackoverflow.com/questions/8021874/how-can-i-compile-to-assembly-with-gcc – Cogwheel
Что произойдет, если вы измените 'push_back (Item())' на 'emplace_back()' в версия C++ 11? – Cogwheel
См. Выше, что «исправляет» регрессию. Я все еще удивляюсь, почему push_back регрессирует в производительности между C++ 03 и C++ 11. – milianw