Эффективность GCC optmization на битовых операциях

Вот два способа установить отдельный бит в C на x86-64:

inline void SetBitC(long *array, int bit) {
   //Pure C version
   *array |= 1<<bit;
}

inline void SetBitASM(long *array, int bit) {
   // Using inline x86 assembly
   asm("bts %1,%0" : "+r" (*array) : "g" (bit));
}

Использование GCC 4.3 с -O3 -march=core2 варианты, версия C сопровождает на 90% больше времени, когда она используется с константой bit. (Обе версии собирают к точно тому же ассемблерному коду, за исключением того, что версия C использует or [1<<num],%rax инструкция вместо a bts [num],%rax инструкция)

Когда использовался с переменной bit, версия C выступает лучше, но все еще значительно медленнее, чем действующее собрание.

У сброса, переключения и проверки битов есть подобные результаты.

Почему GCC оптимизирует так плохо для такой общей операции? Я делаю что-то не так с версией C?

Править: Извините за долгое ожидание вот кодекс, которого я раньше определял эффективность. Это на самом деле началось как простая программная проблема...

int main() {
    // Get the sum of all integers from 1 to 2^28 with bit 11 always set
    unsigned long i,j,c=0;
    for (i=1; i<(1<<28); i++) {
        j = i;
        SetBit(&j, 10);
        c += j;
    }
    printf("Result: %lu\n", c);
    return 0;
}

gcc -O3 -march=core2 -pg test.c
./a.out
gprof
with ASM: 101.12      0.08     0.08                             main
with C:   101.12      0.16     0.16                             main

time ./a.out также дает подобные результаты.

13
задан Dumb Guy 11 January 2010 в 04:57
поделиться

4 ответа

Можете ли вы опубликовать код, который вы используете, чтобы сделать время? Такая операция может быть сложно четко.

Теоретически эти последовательности кода должны быть одинаково быстрыми, поэтому наиболее вероятное объяснение (на мой взгляд) состоит в том, что что-то вызывает ваш код времени, чтобы дать поддельными результатами.

2
ответ дан 1 December 2019 в 23:31
поделиться

Думаю, ты просишь много оптимизатора.

Вы могли бы немного помочь ему, сделав `регистрацию long z = 1L << bit;", а затем или-инг с вашим массивом.

Однако, я предполагаю, что на 90% больше времени, вы имеете в виду, что версия на C занимает 10 циклов, а версия на asm - 5, верно? Как сравнивается производительность при -O2 или -O1?

.
0
ответ дан 1 December 2019 в 23:31
поделиться

Почему GCC так плохо оптимизируется для такой общей операции?

Прелюдия: с конца 1980-х годов сосредоточенность на оптимизации компилятора отошла от микробенчевок, которые сосредоточены на индивидуальных операциях и в направлении MacrObenchmarks , которые фокусируются на приложениях, чей скорость заботятся. В наши дни большинство писателей компилятора сосредоточены на Macrobenchmarks, и разработка хороших бенчарных люксов - это то, что воспринимается серьезно.

Ответ : Никто на GCC не использует ориентир, где разница между или и или имеет значение для времени выполнения реальной программы. Если вы можете произвести такую ​​программу, вы сможете привлечь внимание людей в GCC-земле.

Я делаю что-то не так с помощью версии C?

Нет, это совершенно хороший стандарт C. очень читаемый и идиоматический, на самом деле.

13
ответ дан 1 December 2019 в 23:31
поделиться

Для такого кода:

#include <stdio.h>
#include <time.h>

int main() {
  volatile long long i = 0;
  time_t start = time (NULL);
  for (long long n = 0; n < (1LL << 32); n++) {
    i |= 1 << 10;
  }
  time_t end = time (NULL);
  printf("C took %ds\n", (int)(end - start));
  start = time (NULL);
  for (long long n = 0; n < (1LL << 32); n++) {
    __asm__ ("bts %[bit], %[i]"
                  : [i] "=r"(i)
                  : "[i]"(i), [bit] "i" (10));
  }
  end = time (NULL);
  printf("ASM took %ds\n", (int)(end - start));
}

результат был:

C took 12s
ASM took 10s

Мой флаг был ( -std = gnu99 -O2 -march = core2 ). Без volatile цикл был оптимизирован. gcc 4.4.2.

Никакой разницы не было с:

__asm__ ("bts %[bit], %[i]"
              : [i] "+m"(i)
              : [bit] "r" (10));

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

Дополнительно для такого кода:

#include <stdio.h>
#include <time.h>

int main() {
  volatile long long i = 0;
  time_t start = time (NULL);
  for (long long n = 0; n < (1L << 32); n++) {
    i |= 1 << (n % 32);
  }
  time_t end = time (NULL);
  printf("C took %ds\n", (int)(end - start));
  start = time (NULL);
  for (long long n = 0; n < (1L << 32); n++) {
    __asm__ ("bts %[bit], %[i]"
                  : [i] "+m"(i)
                  : [bit] "r" (n % 32));
  }
  end = time (NULL);
  printf("ASM took %ds\n", (int)(end - start));
}

Результат был:

C took 9s
ASM took 10s

Оба результата были «стабильными». Тестирование процессора Intel (R) Core (TM) 2 Duo CPU T9600 @ 2,80 ГГц.

2
ответ дан 1 December 2019 в 23:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: