Бенчмарк лжи.Они редко измеряют то, что вы хотите от них.В данном конкретном случае методы, вероятно, были встроенными, и поэтому __assume
был просто избыточен.
Что касается самого вопроса, да, это может помочь.Переключение обычно реализуется с помощью таблицы переходов, уменьшая размер этой таблицы или удаляя некоторые записи, компилятор может выбрать лучшие инструкции ЦП для реализации switch
.
В вашем крайнем случае, он может превратить switch
в if (i == 0) { } else { }
структуру, которая обычно эффективна.
Кроме того, обрезка мертвых веток помогает сохранить код чистым, а меньшее количество кода означает лучшее использование кэша инструкций ЦП.
Однако это микрооптимизации, и они редко окупаются: вам нужен профилировщик, чтобы указать их вам, и даже им может быть трудно понять, какое конкретное преобразование сделать (лучше всего __assume
)?).Это работа эксперта.
РЕДАКТИРОВАТЬ : в действии с LLVM
void foo(void);
void bar(void);
void regular(int i) {
switch(i) {
case 0: foo(); break;
case 1: bar(); break;
}
}
void optimized(int i) {
switch(i) {
case 0: foo(); break;
case 1: bar(); break;
default: __builtin_unreachable();
}
}
Обратите внимание, что единственная разница заключается в наличии или отсутствии __builtin_unreachable()
, которыйаналогично MSVC __assume(0)
.
define void @regular(i32 %i) nounwind uwtable {
switch i32 %i, label %3 [
i32 0, label %1
i32 1, label %2
]
; <label>:1 ; preds = %0
tail call void @foo() nounwind
br label %3
; <label>:2 ; preds = %0
tail call void @bar() nounwind
br label %3
; <label>:3 ; preds = %2, %1, %0
ret void
}
define void @optimized(i32 %i) nounwind uwtable {
%cond = icmp eq i32 %i, 1
br i1 %cond, label %2, label %1
; <label>:1 ; preds = %0
tail call void @foo() nounwind
br label %3
; <label>:2 ; preds = %0
tail call void @bar() nounwind
br label %3
; <label>:3 ; preds = %2, %1
ret void
}
И обратите внимание, как оператор switch
в regular
может быть оптимизирован для простого сравнения в optimized
.
.следующая сборка x86:
.globl regular | .globl optimized
.align 16, 0x90 | .align 16, 0x90
.type regular,@function | .type optimized,@function
regular: | optimized:
.Ltmp0: | .Ltmp3:
.cfi_startproc | .cfi_startproc
# BB#0: | # BB#0:
cmpl $1, %edi | cmpl $1, %edi
je .LBB0_3 | je .LBB1_2
# BB#1: |
testl %edi, %edi |
jne .LBB0_4 |
# BB#2: | # BB#1:
jmp foo | jmp foo
.LBB0_3: | .LBB1_2:
jmp bar | jmp bar
.LBB0_4: |
ret |
.Ltmp1: | .Ltmp4:
.size regular, .Ltmp1-regular | .size optimized, .Ltmp4-optimized
.Ltmp2: | .Ltmp5:
.cfi_endproc | .cfi_endproc
.Leh_func_end0: | .Leh_func_end1:
Обратите внимание, что во втором случае:
- код более жесткий (меньше инструкций)
- есть одно сравнение/ jump (cmpl / je) на всех путях (а не один путь с одним прыжком и путь с двумя)
Также обратите внимание, как это так близко, что я понятия не имею, как что-либо измеритькроме шума ...
С другой стороны, семантически это указывает на намерение, хотя, возможно, assert
может лучше подходить только для семантики.