Я хочу, чтобы время вызова функции с помощью rdtsc. Поэтому я измерил его двумя способами следующим образом.rdtsc время для измерения функции
- Назовите его в цикле. Совокупность каждой разности rdtsc внутри цикла и деление на количество вызовов. (Предположим, что это N)
- Назовите его в цикле. Получите разность rdtsc самого цикла и разделите его на N.
Но я вижу пару непоследовательных поведений.
- Когда я увеличиваю N, времена становятся уменьшенными монотонно в обоих методах 1 и 2. Для метода 2 это понятно, поскольку оно амортизирует контрольные надстройки цикла. Но я не уверен, как это делается для метода 1.
- Фактически для метода 2 каждый раз, когда я увеличиваю N, значение, которое я получаю для N = 1, кажется, просто делится на новый N каждый раз. Проверка разборки gdb заставила меня понять, что это некоторая оптимизация компилятора на -O2, где цикл пропускается во втором случае. Поэтому я повторил с -O0, где разборка gdb показывает, что фактический цикл существует и для второго случая.
код приведен ниже.
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
typedef unsigned long long ticks;
static __inline__ ticks getticks(void) {
unsigned a, d;
asm volatile("rdtsc" : "=a" (a), "=d" (d));
return ((ticks)a) | (((ticks)d) << 32);
}
__attribute__ ((noinline))
void bar() {
}
int main(int argc, char** argv) {
long long N = 1000000;
N = atoi(argv[1]);
int i;
long long bar_total = 0;
ticks start = 0, end = 0;
for (i = 0; i < N; i++) {
start = getticks();
bar();
end = getticks();
bar_total += (end - start);
}
fprintf(stdout, "Total invocations : %lld\n", N);
fprintf(stdout, "[regular] bar overhead : %lf\n", ((double)bar_total/ N));
start = getticks();
for (i = 0; i < N; i++) {
bar();
}
end = getticks();
bar_total = (end - start);
fprintf(stdout, "[Loop] bar overhead : %lf\n", ((double)bar_total/ N));
return 0;
}
Любая идея, что здесь происходит? Я также могу установить разборку gdb, если это необходимо. Я использовал реализацию RDTSC от http://dasher.wustl.edu/tinker/distribution/fftw/kernel/cycle.h
Edit: Я буду иметь, чтобы убрать мое второе утверждение, что в -O0 время потеряется прямо пропорциональна N во втором случае. Я предполагаю, что это была некоторая ошибка, которую я совершил во время сборки, в результате чего сохранилась старая версия. Любой, как он все еще опускается несколько вместе с рисунком для метода 1. Здесь приведены некоторые числа для разных значений N.
taskset -c 2 ./example.exe 1
Total invocations : 1
[regular] bar overhead : 108.000000
[Loop] bar overhead : 138.000000
taskset -c 2 ./example.exe 10
Total invocations : 10
[regular] bar overhead : 52.900000
[Loop] bar overhead : 40.700000
taskset -c 2 ./example.exe 100
Total invocations : 100
[regular] bar overhead : 46.780000
[Loop] bar overhead : 15.570000
taskset -c 2 ./example.exe 1000
Total invocations : 1000
[regular] bar overhead : 46.069000
[Loop] bar overhead : 13.669000
taskset -c 2 ./example.exe 100000
Total invocations : 10000
[regular] bar overhead : 46.010100
[Loop] bar overhead : 13.444900
taskset -c 2 ./example.exe 100000000
Total invocations : 100000000
[regular] bar overhead : 26.970272
[Loop] bar overhead : 5.201252
taskset -c 2 ./example.exe 1000000000
Total invocations : 1000000000
[regular] bar overhead : 18.853279
[Loop] bar overhead : 5.218234
taskset -c 2 ./example.exe 10000000000
Total invocations : 1410065408
[regular] bar overhead : 18.540719
[Loop] bar overhead : 5.216395
Сейчас я вижу два новых поведения.
- Метод 1 сходится медленнее, чем метод 2. Но все же я озадачен тем, почему существует такая резкая разница в значениях для разных параметров N. Возможно, я делаю какую-то основную ошибку, которую я сейчас не вижу.
- Значение метода 1 на самом деле больше, чем метод 2 с некоторым запасом. Я ожидал, что он будет на уровне или немного меньше значения метода 2, так как он не содержит накладных расходов на управление контуром.
Вопросы
Таким образом, в итоге мои вопросы
Почему значения, приведенные обоими методами изменить так резко при увеличении N? Специально для метода 1, который не учитывает служебные данные управления циклом.
Почему второй результат метода меньше, чем первый метод, когда первый метод исключает издержки управления циклом в вычислениях?
Edit 2
Что касается предложенного rdtscp решения.
Будучи непосвященным о встроенном сборке, я сделал следующее.
static __inline__ ticks getstart(void) {
unsigned cycles_high = 0, cycles_low = 0;
asm volatile ("CPUID\n\t"
"RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low)::
"%rax", "%rbx", "%rcx", "%rdx");
return ((ticks)cycles_high) | (((ticks)cycles_low) << 32);
}
static __inline__ ticks getend(void) {
unsigned cycles_high = 0, cycles_low = 0;
asm volatile("RDTSCP\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
"CPUID\n\t": "=r" (cycles_high), "=r" (cycles_low)::
"%rax", "%rbx", "%rcx", "%rdx");
return ((ticks)cycles_high) | (((ticks)cycles_low) << 32);
}
и использованные выше методы до и после вызова функции. Но теперь я получаю нечувствительные результаты, как следует.
Total invocations : 1000000
[regular] bar overhead : 304743228324.708374
[Loop] bar overhead : 33145641307.734016
Какой улов? Я хотел отбросить их как встроенные методы, так как я вижу их использование в нескольких местах.
A. Решение в комментариях.
Можете ли вы опубликовать вывод программы для нескольких N? – osgx
Добавлены некоторые выходы для разных значений N. Пожалуйста, см. Также мою ретракцию. – chamibuddhika
Спасибо. Какой у вас процессор? И в чем вопрос? Вы должны изменить свою функцию rdtsc, чтобы включить 'cpuid' или другую инструкцию по сериализации - проверить https://www.ccsl.carleton.ca/~jamuir/rdtscpm1.pdf page 3« 3.1. Внеочередное исполнение »; или переключиться на 'rdtscp': http://stackoverflow.com/questions/12631856/difference-between-rdtscp-rdtsc-memory-and-cpuid-rdtsc?rq=1 – osgx