Разница между характеристикой функции при передаче параметра как постоянной времени компиляции или переменной

В коде ядра Linux есть макрос, используемый для тестирования бит (версия Linux 2.6.2): ​​

#define test_bit(nr, addr)                      \
        (__builtin_constant_p((nr))             \
         ? constant_test_bit((nr), (addr))      \
         : variable_test_bit((nr), (addr)))

где constant_test_bit и variable_test_bit определяются как:

static inline int constant_test_bit(int nr, const volatile unsigned long *addr  )
{       
        return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0;
}


static __inline__ int variable_test_bit(int nr, const volatile unsigned long *addr)
{       
        int oldbit;

        __asm__ __volatile__(
                "btl %2,%1\n\tsbbl %0,%0"
                :"=r" (oldbit)
                :"m" (ADDR),"Ir" (nr));
        return oldbit;
}

Я понимаю, что __builtin_constant_p используется для определения того, является ли переменная постоянной времени компиляции или неизвестной. Мой вопрос: есть ли разница в производительности между этими двумя функциями, когда аргумент является константой времени компиляции или нет? Зачем использовать версию C, когда она есть, и использовать версию сборки, если это не так?

UPDATE: Для проверки производительности используется следующая основная функция:

константа, call constant_test_bit:

int main(void) {
        unsigned long i, j = 21;
        unsigned long cnt = 0;
        srand(111)
        //j = rand() % 31;
        for (i = 1; i < (1 << 30); i++) {
                j = (j + 1) % 28;
                if (constant_test_bit(j, &i))
                        cnt++;
        }
        if (__builtin_constant_p(j))
                printf("j is a compile time constant\n");
        return 0;
}

Это правильно выводит предложение j является...

Для других ситуаций просто раскомментируйте строку, которая присваивает "случайное" число до j и соответственно меняет имя функции. Когда эта строка раскоментирована, вывод будет пустым, и это ожидается.

Я использую gcc test.c -O1 для компиляции, и вот результат:

constant, constant_test_bit:

$ time ./a.out 

j is compile time constant

real    0m0.454s
user    0m0.450s
sys     0m0.000s

constant, variable_test_bit (опустить time ./a.out, то же для следующего):

j is compile time constant

real    0m0.885s
user    0m0.883s
sys     0m0.000s

variable, constant_test_bit:

real    0m0.485s
user    0m0.477s
sys     0m0.007s

variable, variable_test_bit:

real    0m3.471s
user    0m3.467s
sys     0m0.000s

У меня каждая версия выполняется несколько раз, и приведенные выше результаты являются их типичными значениями. Кажется, функция constant_test_bit всегда быстрее, чем функция variable_test_bit, независимо от того, является ли параметр постоянной времени компиляции или нет... Для двух последних результатов (когда j не является константой) переменная версия даже значительно медленнее, чем постоянный. Я что-то пропустил?

+7
03 мая '15 в 11:20
источник поделиться
1 ответ

Используя godbolt, мы можем сделать эксперимент, используя константу_test_bit, следующие две тестовые функции скомпилированы gcc с флагом -O3:

// Non constant expression test case
int func1(unsigned long i, unsigned long j)
{
  int x = constant_test_bit(j, &i) ;
  return x ;
}

// constant expression test case
int func2(unsigned long i)
{
  int x = constant_test_bit(21, &i) ;
  return x ;
}

Мы видим, что оптимизатор способен оптимизировать случай постоянного выражения следующим образом:

shrq    $21, %rax
andl    $1, %eax

в то время как случай непостоянного выражения заканчивается следующим образом:

sarl    $5, %eax
andl    $31, %ecx
cltq
leaq    -8(%rsp,%rax,8), %rax
movq    (%rax), %rax
shrq    %cl, %rax
andl    $1, %eax

Таким образом, оптимизатор может создать гораздо лучший код для случая с постоянным выражением, и мы можем видеть, что непостоянный случай для constant_test_bit довольно плох по сравнению с ручной прокаткой сборки в variable_test_bit, и разработчик должен верить случай постоянного выражения для constant_test_bit заканчивается лучше, чем:

btl %edi,8(%rsp)
sbbl %esi,%esi 

для большинства случаев.

Что касается того, почему ваш тестовый пример, похоже, показывает другой вывод, что ваш тестовый пример является ошибочным. Я не смог решить все проблемы. Но если мы рассмотрим этот случай, используя constant_test_bit с непостоянным выражением, мы видим, что оптимизатор способен перемещать всю работу за пределами посмотрите и уменьшите работу, связанную с constant_test_bit внутри цикла, чтобы:

movq    (%rax), %rdi

даже с более старой версией gcc, но этот случай может не иметь отношения к случаям, в которых используется test_bit. Могут быть более конкретные случаи, когда такая оптимизация невозможна.

+5
03 мая '15 в 18:30
источник

Посмотрите другие вопросы по меткам или Задайте вопрос