標準についてはパフォーマンスについてほとんど言及していませんが、明確なルールが1つあります。その最適化では、観察可能な副作用を変更してはいけません。
標準では、スタックまたはヒープの使用法は記述されていないため、使用前の任意の時点でスタック上の変数の領域をコンパイラが割り当てることは完全に正当です。
ただし、最適値はさまざまな要因によって異なります。最も一般的なアーキテクチャでは、スタックポインタの調整を入力と終了の2つの場所で行うのが最も理にかなっています。 x86では、スタックポインタを8ではなく640で変更するのにコストの差はありません。
また、コンパイラが値が変更されないことが確認できれば、オプティマイザはループも。
実際には、x86およびアームベースのプラットフォームのメインストリームコンパイラ(gcc、clang、msvc)は、スタック割り当てを1つのアップ/ダウンに集約し、十分なオプティマイザ設定/引数を与えられたループインバリアントをホイストします。
疑わしい場合は、アセンブリまたはベンチマークを調べます。
我々は非常に迅速にgodboltでこれを証明することができます:GCC 5で
#include <vector>
struct Channel
{
void test(int&);
};
std::vector<Channel> channels;
void test1()
{
while (!channels.empty())
{
for (auto&& channel : channels)
{
int stop_time;
channel.test(stop_time);
}
}
}
void test2()
{
while (!channels.empty())
{
int stop_time;
for (auto&& channel : channels)
{
channel.test(stop_time);
}
}
}
void test3()
{
int stop_time;
while (!channels.empty())
{
for (auto&& channel : channels)
{
channel.test(stop_time);
}
}
}
。1と-O3 this generates three identical pieces of assembly:
test1():
pushq %rbp
pushq %rbx
subq $24, %rsp
.L8:
movq channels+8(%rip), %rbp
movq channels(%rip), %rbx
cmpq %rbp, %rbx
je .L10
.L7:
leaq 12(%rsp), %rsi
movq %rbx, %rdi
addq $1, %rbx
call Channel::test(int&)
cmpq %rbx, %rbp
jne .L7
jmp .L8
.L10:
addq $24, %rsp
popq %rbx
popq %rbp
ret
test2():
pushq %rbp
pushq %rbx
subq $24, %rsp
.L22:
movq channels+8(%rip), %rbp
movq channels(%rip), %rbx
cmpq %rbp, %rbx
je .L20
.L14:
leaq 12(%rsp), %rsi
movq %rbx, %rdi
addq $1, %rbx
call Channel::test(int&)
cmpq %rbx, %rbp
jne .L14
jmp .L22
.L20:
addq $24, %rsp
popq %rbx
popq %rbp
ret
test3():
pushq %rbp
pushq %rbx
subq $24, %rsp
.L26:
movq channels+8(%rip), %rbp
movq channels(%rip), %rbx
cmpq %rbp, %rbx
je .L28
.L25:
leaq 12(%rsp), %rsi
movq %rbx, %rdi
addq $1, %rbx
call Channel::test(int&)
cmpq %rbx, %rbp
jne .L25
jmp .L26
.L28:
addq $24, %rsp
popq %rbx
popq %rbp
ret
あなたはそれをタイムアウトして見つけませんか?私はあなたがどんな違いに気付くか疑問に思う。 – smac89
私はそれらがまったく同じコードにコンパイルされると確信している。 – melpomene
ケースIIの方が速いですか?変数は一度だけ初期化されないからです。 – seccpur