Это правильный метод для сравнения времени процессора между C ++ и Python?

Я заинтересован в сравнении времени процессора некоторых частей кода, написанных на 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,

проблемы:

  1. Я читал, что C ++ clock() измеряет процессорное время, которое мне и нужно, но я не могу найти, включает ли оно как пользовательское, так и системное время.
  2. Результаты C ++ гораздо менее точны. Это почему?
  3. Общая справедливость сравнения, как упоминалось.

обновил код 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 получит мне немного лучшее разрешение, но я не уверен, сколько мне стоит в него читать. Установка флага оптимизации, безусловно, имеет большое значение.

3

Решение

документация говорит:

«Возвращает приблизительное время процессора, использованное процессом с начала определенной эпохой реализации, связанной с выполнением программы. Чтобы преобразовать значение результата в секунды, разделите его на 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 предлагает лучшее разрешение.«

Так:

  1. Это только процессорное время. Но 2 потока = 2 * процессорное время. Смотрите пример на cppreference.

  2. Он вообще не подходит для точных измерений зерна, как объяснено выше. Вы были на грани его точности.

  3. Измерение настенных часов IMO — единственная разумная вещь, но это довольно личное мнение. Особенно с многопоточными приложениями и многопроцессорностью в целом. В противном случае результаты system+user должно быть похоже в любом случае.

РЕДАКТИРОВАТЬ: на 3. Это, конечно, верно для вычислительных задач. Если ваш процесс использует sleep или отказаться от выполнения обратно в систему, это может быть более целесообразным измерения времени процессора. Также относительно комментария, который clock разрешение … плохо. Это так, но чтобы быть справедливым, можно утверждать, что вы не должны измерять такие короткие вычисления. IMO это слишком плохо, но если вы измеряете время в течение нескольких секунд, я думаю, это хорошо. Я бы лично использовал другие доступные инструменты.

1

Другие решения

Установка флага оптимизации, безусловно, имеет большое значение.

C ++ — это язык, который требует оптимизации компиляции, особенно если рассматриваемый код использует контейнеры и итераторы из стандартной библиотеки C ++. Просто ++iterator сокращается от цепочки вызовов функций хорошего размера при неоптимизированной компиляции до одного или двух операторов сборки, когда включена оптимизация.

Тем не менее, я знал, что компилятор сделает с вашим тестовым кодом. Любой приличный оптимизирующий компилятор сделает это for (int i=0; i<N; i++) continue; петля исчезает. Это как будто править на работе. Этот цикл ничего не делает, поэтому компилятор может обращаться с ним так, как будто его там даже не было.

Когда я смотрю на поведение ЦП подозреваемой загрузки ЦП, я пишу простой драйвер (в отдельном файле), который вызывает подозрительную функцию несколько раз, иногда очень большое количество раз. Я компилирую функциональность для тестирования с включенной оптимизацией, но компилирую драйвер с отключенной оптимизацией. Я не хочу, чтобы слишком умный оптимизирующий компилятор видел, что эти 100 000 обращений к function_to_be_tested() может быть извлечен из цикла, а затем дополнительно оптимизировать цикл.

Существует несколько веских причин для вызова тестовой функции несколько раз между одиночным вызовом для запуска таймера и остановки таймера. Вот почему Python имеет timeit модуль.

1

По вопросам рекламы [email protected]