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 доказать, что переменная подходит, и в приведенном выше примере нет ветви по умолчанию? (Без добавления условной ветви, конечно.)

обновления

  1. Это было на OS X 10.6 Snow Leopard с GCC 4.2 (по умолчанию от Xcode.) Этого не произошло с GCC 4.4/4.3 в Linux (сообщает Натон и Йенс Gustedt.)

  2. функции в примере есть для удобства чтения подумайте, что это встроенные или просто утверждения. Выполнение вызова функции на 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;

  3. хорошо, я собираюсь с уродливым решением и добавлением специального случая для версий 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 ) {
...
}

надеюсь, что это работает!


возможно, просто используйте default ярлык для кулака или последнего случая?


этот вопрос, безусловно, интересен с точки зрения пропущенной оптимизации компилятора, которая, по-видимому, очевидна для нас, и я потратил значительное время, пытаясь придумать простое решение, в основном из личного любопытства.

тем не менее, я должен признать Я очень скептически отношусь к тому, что эта дополнительная инструкция когда-либо приведет к измеримой разнице в производительности на практике, особенно на новом mac. Если у вас есть какие-либо значительные объем данных, вы будете связаны с вводом-выводом, и одна инструкция никогда не будет вашим узким местом. Если у вас есть небольшой объем данных, вам нужно будет выполнить много расчетов неоднократно, прежде чем одна инструкция станет узким местом.

вы бы опубликовали некоторый код, чтобы показать, что действительно есть разница в производительности? Или опишите код и данные, с которыми вы работаете?


Я не пытался, но я не уверен, что gcc_unreachable делает то же самое как __builtin_unreachable. Гуглить двух,gcc_unreachable по-видимому, разработан как инструмент утверждения для разработки самого GCC, возможно, с включенной подсказкой прогнозирования ветвей, тогда как __builtin_unreachable делает программу мгновенно неопределенной-что звучит как удаление базового блока, который вы хотеть.

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005funreachable-3075