Я заинтересован в сравнении времени процессора некоторых частей кода, написанных на C ++ и Python (работающих на Linux). Приведут ли следующие методы к «честному» сравнению?
С использованием ресурсный модуль:
import resource
def cpu_time():
return resource.getrusage(resource.RUSAGE_SELF)[0]+\ # time in user mode
resource.getrusage(resource.RUSAGE_SELF)[1] # time in system mode
что позволяет рассчитать время примерно так:
def timefunc( func ):
start=cpu_time()
func()
return (cpu_time()-start)
Затем я тестирую как:
def f():
for i in range(int(1e6)):
pass
avg = 0
for k in range(10):
avg += timefunc( f ) / 10.0
print avg
=> 0.002199700000000071
С использованием ctime
Lib:
#include <ctime>
#include <iostream>
int main() {
double avg = 0.0;
int N = (int) 1e6;
for (int k=0; k<10; k++) {
clock_t start;
start = clock();
for (int i=0; i<N; i++) continue;
avg += (double)(clock()-start) / 10.0 / CLOCKS_PER_SEC;
}
std::cout << avg << '\n';
return 0;
}
который дает 0.002
,
проблемы:
clock()
измеряет процессорное время, которое мне и нужно, но я не могу найти, включает ли оно как пользовательское, так и системное время.обновил код c ++ согласно предложению Дэвида в комментариях:
#include <sys/resource.h>
#include <iostream>
int main() {
double avg = 0.0;
int N = (int) 1e6;
int tally = 0;
struct rusage usage;
struct timeval ustart, ustop, sstart, sstop;
getrusage(RUSAGE_SELF, &usage);
ustart = usage.ru_utime;
sstart = usage.ru_stime;
for (int k=0; k<10; k++) {
ustart = usage.ru_utime;
sstart = usage.ru_stime;
for (int i=0; i<N; i++) continue;
getrusage(RUSAGE_SELF, &usage);
ustop = usage.ru_utime;
sstop = usage.ru_stime;
avg += (
(ustop.tv_sec+ustop.tv_usec/1e6+
sstop.tv_sec+sstop.tv_usec/1e6)
-
(ustart.tv_sec+ustart.tv_usec/1e6+
sstart.tv_sec+sstart.tv_usec/1e6)
) / 10.0;
}
std::cout << avg << '\n';
return 0;
}
Бег:
g++ -O0 cpptimes.cpp ; ./a.out
=> 0.0020996
g++ -O1 cpptimes.cpp ; ./a.out
=> 0
Так что я полагаю getrusage
получит мне немного лучшее разрешение, но я не уверен, сколько мне стоит в него читать. Установка флага оптимизации, безусловно, имеет большое значение.
документация говорит:
«Возвращает приблизительное время процессора, использованное процессом с начала определенной эпохой реализации, связанной с выполнением программы. Чтобы преобразовать значение результата в секунды, разделите его на CLOCKS_PER_SEC.«
Это довольно расплывчато. CLOCK_PER_SEC
установлен в 10^6
и приблизительное обозначает плохое разрешение, а не то, что текущие часы тикают более чем на 1000 быстрее, а результаты округляются. Это может быть не очень технический термин, но он уместен. Фактическое разрешение везде, где я тестировал, было около 100 Гц = 0,01 с. Так было годами. Отметьте дату здесь http://www.guyrutenberg.com/2007/09/10/resolution-problems-in-clock/.
Затем следует документ:В POSIX-совместимых системах clock_gettime с идентификатором часов CLOCK_PROCESS_CPUTIME_ID предлагает лучшее разрешение.«
Так:
Это только процессорное время. Но 2 потока = 2 * процессорное время. Смотрите пример на cppreference.
Он вообще не подходит для точных измерений зерна, как объяснено выше. Вы были на грани его точности.
Измерение настенных часов IMO — единственная разумная вещь, но это довольно личное мнение. Особенно с многопоточными приложениями и многопроцессорностью в целом. В противном случае результаты system
+user
должно быть похоже в любом случае.
РЕДАКТИРОВАТЬ: на 3. Это, конечно, верно для вычислительных задач. Если ваш процесс использует sleep
или отказаться от выполнения обратно в систему, это может быть более целесообразным измерения времени процессора. Также относительно комментария, который clock
разрешение … плохо. Это так, но чтобы быть справедливым, можно утверждать, что вы не должны измерять такие короткие вычисления. IMO это слишком плохо, но если вы измеряете время в течение нескольких секунд, я думаю, это хорошо. Я бы лично использовал другие доступные инструменты.
Установка флага оптимизации, безусловно, имеет большое значение.
C ++ — это язык, который требует оптимизации компиляции, особенно если рассматриваемый код использует контейнеры и итераторы из стандартной библиотеки C ++. Просто ++iterator
сокращается от цепочки вызовов функций хорошего размера при неоптимизированной компиляции до одного или двух операторов сборки, когда включена оптимизация.
Тем не менее, я знал, что компилятор сделает с вашим тестовым кодом. Любой приличный оптимизирующий компилятор сделает это for (int i=0; i<N; i++) continue;
петля исчезает. Это как будто править на работе. Этот цикл ничего не делает, поэтому компилятор может обращаться с ним так, как будто его там даже не было.
Когда я смотрю на поведение ЦП подозреваемой загрузки ЦП, я пишу простой драйвер (в отдельном файле), который вызывает подозрительную функцию несколько раз, иногда очень большое количество раз. Я компилирую функциональность для тестирования с включенной оптимизацией, но компилирую драйвер с отключенной оптимизацией. Я не хочу, чтобы слишком умный оптимизирующий компилятор видел, что эти 100 000 обращений к function_to_be_tested()
может быть извлечен из цикла, а затем дополнительно оптимизировать цикл.
Существует несколько веских причин для вызова тестовой функции несколько раз между одиночным вызовом для запуска таймера и остановки таймера. Вот почему Python имеет timeit
модуль.