Проводил я на днях замер скорости выполнения основных операций в языке C. Далее приводятся выходные данные моей небольшой программки. Компиляция проводилась без оптимизации и со статическими библиотеками. В качестве средства замера результатов использовалась функция clock() возвращающая количество процессорного времени использованного программой.
Сразу можно заметить, что время выполнения пустого цикла (по результам работы программы) больше, чем время выполнения того же цикла, но уже с инструкцией i++. Это можно объяснить прежде всего тем, что в случае, если цикл не умещается внутри кратных 16 байтам адресов требуются дополнительные такты процессора для доставки инструкций в декодер. В моём случае это еще 4-5 наносекунд. Кроме того не стоит забывать о том, что в процессорах PIII только один из 3-х конвейеров полноценный и в случае, если команда не может быть обработана в 2-ом или 3-ем она будет дожидаться освобождения первого конвейера. Для меня это сложная тема в которой я ничего не понимаю. :) Перейдем непосредственно к тесту.

Тестирование проводилось на следующей конфигурации:

* CPU: Pentium III 600MHz EB
* MB Chipset: i815
* RAM: 512 MB
* OS: ASPLinux 9.0 Ural (Kernel 2.4.20-asp не пересобирал )


Компилировалась программа командой:
$ gcc -O0 -static -o timings timings.c -lm
Результаты выполнения команды
$ ./timings > log.txt :

Код:
CLOCKS_PER_SEC = 1000000
Пустые циклы (n = 5000):
; 11.60
{} 11.68
Целочисленные операции (n = 5000):
Присваивание (n = 5000):
v=i; 10.64
v=20; 13.28
Сложение (n = 5000):
v++; 10.00
v+=20; 11.68
v+=i; 12.88
v=v+i; 12.88
v=i+j; 12.96
Вычитание (n = 5000):
v--; 11.68
v-=20; 11.84
v-=i; 13.12
v=v-i; 12.96
v=i-j; 12.48
Умножение (n = 5000):
v*=20; 13.68
v*=i; 15.04
v=v*i; 14.96
v=i*j; 13.28
Деление (n = 5000):
v/=20; 24.88
v/=i; 77.28
v=v/i; 76.48
v=i/j; 59.92
Остаток от деления (n = 5000):
v%=20; 77.68
v%=i; 77.04
v=v%i; 77.60
v=i%j; 59.84
Операции над битами(n = 5000):
v=i|j; 13.28
v=i|45; 11.60
v=65|45; 9.92
v|=i; 13.44
v|=45; 11.68
v=i&j; 13.28
v=i&45; 11.60
v=65&45; 10.00
v&=i; 13.12
v&=45; 10.00
v=i^j; 11.68
v=i^45; 11.52
v=65^45; 9.92
v^=i; 12.96
v^=45; 10.00
v=i<<j; 12.48
v=i<<5; 11.68
v=65<<5; 10.00
v<<=i; 12.88
v<<=5; 10.00
v=i>>j; 11.60
v=i>>5; 11.20
v=65>>5; 8.40
v>>=i; 12.96
v>>=5; 10.00
Операции с массивами (n = 5000):
aa[0]=20; 11.76
aa[0]=i; 12.96
aa[i]=20; 20.88
aa[i]=i; 20.00
aa[0]+=i; 14.96
aa[0]-=i; 14.96
aa[i]+=i; 21.52
aa[i]-=i; 22.32
#Подготовка массивов:
for(i = 1; i<=5000; i++) {ab[i]=i;ac[i]=i;}
aa[i]=ab[i]+ac[j]; 22.72
aa[i]=ab[i]-ac[j]; 22.88
aa[i]=ab[i]*ac[j]; 24.40
aa[i]=ab[i]/ac[j]; 60.08
aa[i]=ab[i]%ac[j]; 60.16
Двигаем массивы (n = 500):
for(v=0;v<n;v++) aa[v]=aa[v+1]; 10928.00
#Тоже самое подменив ac=aa+1(free(ac);ac=aa+1;):
for(v=0;v<n;v++) aa[v]=ac[v] 9752.00
memmove(aa,aa+1,n); 704.00
memmove(aa,ac,n); 712.00
Копируем массивы (n = 500):
for(v=0;v<n;v++) aa[v]=ab[v]; 9752.00
memcpy(aa,ab,n); 552.00
Числа с плавающей точкой (n = 5000):
Присваивание (n = 5000):
fa=i; 12.48
fa=20.0; 8.32
fa=fc; 10.00
Сложение (n = 5000):
fa++; 13.36
fa+=20.0; 13.28
fa+=fb; 11.76
fa=fa+fc; 13.36
fa=fb+fc; 14.96
Вычитание (n = 5000):
fa--; 14.96
fa-=20.0; 13.28
fa-=fb; 14.96
fa=fa-fc; 11.84
fa=fb-fc; 9.92
Умножение (n = 5000):
fa*=20.0; 16.64
fa*=fb; 16.64
fa=fa*fc; 16.64
fa=fb*fc; 11.68
Деление (n = 5000):
fa/=20.0; 23.28
fa/=fb; 23.28
fa=fa/fc; 23.28
fa=fb/fc; 63.12
Ветвление (n = 5000):
if(0);else; 11.68
if(-1);else; 9.92
if(0); 11.68
if(-1); 6.64
if(j<i); 11.60
v=(j==i?i:j); 10.00
v=(j>i?i:j); 17.12
v=(j<i?i:j); 19.12
v=(0?i:j); 11.68
v=(-1?i:j); 10.00
Математика (n = 1000):
v=rand(); 92.00
fa=sqrt(i); 174.00
fa=sin(i); 200.00
fa=sinh(i); 576.00
fa=cos(i); 202.00
fa=cosh(i); 524.00
fa=tan(i); 278.00
fa=tanh(i); 66.00
fa=asin(i); 568.00
fa=asinh(i); 436.00
fa=acos(i); 574.00
fa=acosh(i); 496.00
fa=atan(i); 280.00
fa=atanh(i); 622.00
Память (n = 500):
free(malloc(1)); 216.00
free(malloc(2)); 216.00
free(malloc(64)); 216.00
free(malloc(1024)); 328.00
free(malloc(4096)); 320.00
free(malloc(65536)); 336.00
free(malloc(i)); 336.00

Кое что о результатах.
Очевидно, что массивы копируются специальными функциями значительно быстрей, что самые дорогие операции это деление и остаток от деления. Операции с элементами массивами (кроме первого элемента) дороже, чем с простыми переменными. Операции с плавающей точкой обходятся значительно дороже, чем целочисленные. А математические операции представили нам вершину прожорливости. Должен заметить что тандем malloc и free работал значительно быстрей я ожидал время порядка 1000-1500 нс при связке free(malloc(1024)) возможно особенность системы, возможно значительный объем свободной памяти или мои "кривые" ожидания.
После компиляции оптимизированной программы почти все "легенькие" операции выполнялись за ко 3.5 нс вместе с временем работы цикла равного(пустой цикл) 3.28 нс.
Рекомендую перекомпилировать программу своим компилятором. Вся программа сводится к макросу следующего вида:
Код:
#define test_time(op1,n,trials) \
{\
clock_t sum_clock = 0, start_clock, diff_clock;\
printf ("%st", #op1);\
int t,i,j;\
for(t = 0; t<trials; t++)\
{\
start_clock = clock();\
for(j = 1; j<=n; j++)\
{\
for(i = 1; i<=n; i++)\
{\
op1\
diff_clock = clock() - start_clock;\
printf ("%dt",diff_clock);\
sum_clock += diff_clock;\
}\
}\
}\
float result_time = 0.0;\
result_time = 1e9 * sum_clock/n/n/CLOCKS_PER_SEC/trials;\
printf("%'.2fn", result_time );\
fsync(stdout);\
fflush(stdout);\
}

Эту printf ("%dt",diff_clock); часть я вставил, что для слежения за адекватностью результатов вдруг внутренние часы переполнятся. А это fsync(stdout); fflush(stdout); для того чтобы данные сохранялись на диск немедленно в случае перенаправления STDOUT . Образом можно периодически смотреть файл во время выполнения программы например так: $ tail -n 3 log.txt
Тело функции main выглядит примерно так:
Код:
int main(void)
{
int v = 0, n = 5000;
float fa = 0.0,fb = 0.0,fc = 0.0;
int *aa, *ab, *ac;

printf("CLOCKS_PER_SEC = %dn",CLOCKS_PER_SEC);
printf("Пустые циклы (n = %d):n", n);
test_time( ; ,n,5);
test_time( {} ,n,5);

printf("Целочисленные операции (n = %d):n", n);
printf("Присваивание (n = %d):n", n);
test_time( v=i; ,n ,5);
test_time( v=20; ,n ,5);

return 0;
}
Исходный код и результаты (логи) тестов в файле timing.zip.

Автор: LogRus
Information
  • Posted on 31.01.2010 22:27
  • Просмотры: 469