GCC 4.4: избегайте проверки диапазона на инструкции switch / case в gcc?
это только проблема в версиях GCC до 4.4, это было исправлено в GCC 4.5.
можно ли сказать компилятору, что переменная, используемая в коммутаторе, вписывается в предоставленные операторы case? В частности, если это небольшой диапазон и сгенерирована таблица переходов.
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 (сообщает Натон и Йенс Gustedt.)
-
функции в примере есть для удобства чтения подумайте, что это встроенные или просто утверждения. Выполнение вызова функции на x86 дорого.
также пример, как указано в примечании, принадлежит внутри цикла данных (big data.)
сгенерированный код с gcc 4.2 / OS X:
[...] andl , %eax cmpl , %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 , %eax;
ja L11;
-
хорошо, я собираюсь с уродливым решением и добавлением специального случая для версий gcc ниже 4.4, используя другую версию без переключение и использование расширений goto и gcc &&label.
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; [...] }
обратите внимание, что массив меток статичен, поэтому он не вычисляется каждый вызов.
6 ответов
Я попытался скомпилировать что-то простое и сопоставимое с-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 x4,%esp ;; do stuff with the stack
8048426: 8b 45 08 mov 0x8(%ebp),%eax ;; x86 sucks, we get it
8048429: 83 e0 07 and x7,%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
вы пробовали играть с уровнями оптимизации?
возможно, вы могли бы использовать массив указателей на функции вместо переключателя ?
#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;
}
вы пробовали объявлении switch
переменной в поле?
struct Container {
uint16_t a:3;
uint16_t unused:13;
};
struct Container cont;
cont.a = 5; /* assign some value */
switch( cont.a ) {
...
}
надеюсь, что это работает!
этот вопрос, безусловно, интересен с точки зрения пропущенной оптимизации компилятора, которая, по-видимому, очевидна для нас, и я потратил значительное время, пытаясь придумать простое решение, в основном из личного любопытства.
тем не менее, я должен признать Я очень скептически отношусь к тому, что эта дополнительная инструкция когда-либо приведет к измеримой разнице в производительности на практике, особенно на новом mac. Если у вас есть какие-либо значительные объем данных, вы будете связаны с вводом-выводом, и одна инструкция никогда не будет вашим узким местом. Если у вас есть небольшой объем данных, вам нужно будет выполнить много расчетов неоднократно, прежде чем одна инструкция станет узким местом.
вы бы опубликовали некоторый код, чтобы показать, что действительно есть разница в производительности? Или опишите код и данные, с которыми вы работаете?
Я не пытался, но я не уверен, что gcc_unreachable
делает то же самое как __builtin_unreachable
. Гуглить двух,gcc_unreachable
по-видимому, разработан как инструмент утверждения для разработки самого GCC, возможно, с включенной подсказкой прогнозирования ветвей, тогда как __builtin_unreachable
делает программу мгновенно неопределенной-что звучит как удаление базового блока, который вы хотеть.