Вот два способа установить отдельный бит в 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
также дает подобные результаты.
Можете ли вы опубликовать код, который вы используете, чтобы сделать время? Такая операция может быть сложно четко.
Теоретически эти последовательности кода должны быть одинаково быстрыми, поэтому наиболее вероятное объяснение (на мой взгляд) состоит в том, что что-то вызывает ваш код времени, чтобы дать поддельными результатами.
Думаю, ты просишь много оптимизатора.
Вы могли бы немного помочь ему, сделав `регистрацию long z = 1L << bit;", а затем или-инг с вашим массивом.
Однако, я предполагаю, что на 90% больше времени, вы имеете в виду, что версия на C занимает 10 циклов, а версия на asm - 5, верно? Как сравнивается производительность при -O2 или -O1?
.Почему GCC так плохо оптимизируется для такой общей операции?
Прелюдия: с конца 1980-х годов сосредоточенность на оптимизации компилятора отошла от микробенчевок, которые сосредоточены на индивидуальных операциях и в направлении MacrObenchmarks , которые фокусируются на приложениях, чей скорость заботятся. В наши дни большинство писателей компилятора сосредоточены на Macrobenchmarks, и разработка хороших бенчарных люксов - это то, что воспринимается серьезно.
Ответ : Никто на GCC не использует ориентир, где разница между или
и или
имеет значение для времени выполнения реальной программы. Если вы можете произвести такую программу, вы сможете привлечь внимание людей в GCC-земле.
Я делаю что-то не так с помощью версии C?
Нет, это совершенно хороший стандарт C. очень читаемый и идиоматический, на самом деле.
Для такого кода:
#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 ГГц.