Это - только проблема о версиях GCC до 4,4, это было зафиксировано в GCC 4.5.
Действительно ли возможно сказать компилятору, что переменная, используемая в переключателе, соответствует в обеспеченных операторах выбора? В особенности, если это - маленький диапазон и существует сгенерированная таблица переходов.
extern int a;
main()
{
switch (a & 0x7) { // 0x7 == 111 values are 0-7
case 0: f0(); break;
case 1: f1(); break;
case 2: f2(); break;
case 3: f3(); break;
case 4: f4(); break;
case 5: f5(); break;
case 6: f6(); break;
case 7: f7(); break;
}
}
Я попробовал xor'ing к младшим битам (как пример), с помощью перечислений, с помощью gcc_unreachable () напрасно. Сгенерированный код всегда проверяет, ли переменная в диапазоне, добавляя бессмысленное условное выражение ответвления и отодвигая код вычисления таблицы переходов.
Примечание: это находится в самом внутреннем цикле декодера, производительность значительно имеет значение.
Кажется, что я не только один.
Нет никакого способа сказать gcc, что ответвление по умолчанию никогда не берется, хотя это опустит ответвление по умолчанию, если может оказаться, что значение никогда не вне диапазона на основе более ранних условных проверок.
Так, как Вы помогаете gcc доказать переменные соответствия и нет никакого ответвления по умолчанию в примере выше? (Не добавляя условный переход, конечно.)
Это было на OS X 10,6 Snow Leopard с GCC 4.2 (значение по умолчанию от XCode.) Этого не произошло с GCC 4.4/4.3 в Linux (сообщаемый Nathon и Jens Gustedt.)
Функции в примере там для удобочитаемости, думайте, что они встраиваются или просто операторы. Создание вызова функции на x86 является дорогим.
Также пример, как упомянуто в примечании, принадлежит в цикле на данных (большие данные.)
Сгенерированный код с gcc 4.2/OS X:
[...]
andl $7, %eax
cmpl $7, %eax
ja L11
mov %eax, %eax
leaq L20(%rip), %rdx
movslq (%rdx,%rax,4),%rax
addq %rdx, %rax
jmp *%rax
.align 2,0x90
L20:
.long L12-L20
.long L13-L20
.long L14-L20
.long L15-L20
.long L16-L20
.long L17-L20
.long L18-L20
.long L19-L20
L19:
[...]
Проблема заключается на cmp $7, %eax;
ja L11;
Хорошо, я иду с ужасным решением и добавляю особый случай для gcc версий ниже 4,4 использований другой версии без переключателя и использования goto и &&label расширений gcc.
static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 };
[...]
goto *jtb[a & 0x7];
[...]
while(0) {
c_1:
// something
break;
c_2:
// something
break;
[...]
}
Обратите внимание, что массив маркировок статичен, таким образом, он не вычислил каждый вызов.
Я попробовал скомпилировать что-то простое и сравнимое с -O5 и -fno-inline (мои функции f0-f7 были тривиальными), и он выдал следующее:
8048420: 55 push %ebp ;; function preamble
8048421: 89 e5 mov %esp,%ebp ;; Yeah, yeah, it's a function.
8048423: 83 ec 04 sub $0x4,%esp ;; do stuff with the stack
8048426: 8b 45 08 mov 0x8(%ebp),%eax ;; x86 sucks, we get it
8048429: 83 e0 07 and $0x7,%eax ;; Do the (a & 0x7)
804842c: ff 24 85 a0 85 04 08 jmp *0x80485a0(,%eax,4) ;; Jump table!
8048433: 90 nop
8048434: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048438: 8d 45 08 lea 0x8(%ebp),%eax
804843b: 89 04 24 mov %eax,(%esp)
804843e: e8 bd ff ff ff call 8048400
8048443: 8b 45 08 mov 0x8(%ebp),%eax
8048446: c9 leave
Вы пробовали играть с уровнями оптимизации?
Вы пробовали объявить переменную переключателя
как битовое поле?
struct Container {
uint16_t a:3;
uint16_t unused:13;
};
struct Container cont;
cont.a = 5; /* assign some value */
switch( cont.a ) {
...
}
Надеюсь, это сработает!
Возможно, вы могли бы использовать массив указателей функций вместо переключателя?
#include <stdio.h>
typedef void (*func)(void);
static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }
int main(void)
{
const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
int i;
for (i = 0; i < 8; ++i)
{
f[i]();
}
return 0;
}
Может быть, просто использовать метку по умолчанию
для первого или последнего случая?
Этот вопрос, безусловно, интересен с точки зрения пропущенной оптимизации компилятора, которая кажется нам очевидной, и я потратил значительное время, пытаясь найти простое решение, в основном из личного любопытства.
При этом я должен признать, что я весьма скептически отношусь к тому, что эта дополнительная инструкция когда-либо приведет к ощутимой разнице в производительности на практике, особенно на новом mac. Если у вас есть сколько-нибудь значительный объем данных, вы будете связаны вводом-выводом, и одна инструкция никогда не станет вашим узким местом. Если у вас крошечный объем данных, то вам придется выполнить много-много-много вычислений, прежде чем одна инструкция станет узким местом.
Не могли бы вы опубликовать какой-нибудь код, чтобы показать, что разница в производительности действительно есть? Или опишите код и данные, с которыми вы работаете?