Тайные времена исполнения
Проблема заключается в получении некоторых разрывов в последовательности выполнения для различных размеров ввода.
В частности, я пробовал этот код:
long double a[2000][2000];
int iter = 0;
int main(int argc, char const *argv[]){
istringstream is(argv[1]);
int N;
is >> N;
for(int i = 0; i <= N; ++i){
for (int J = 0; J <= N; ++J){
a[i][J] = (rand()%3+1)*(rand()%4+1);
}
}
clock_t clk= clock();
for(int k = 0; k < N; ++k){
for(int i = k+1; i < N; ++i){
a[i][k] = a[i][k]/a[k][k];
}
for(int i = k+1; i < N; ++i){
for(int j = k+1; j < N; ++j){
iter++;
a[i][j] = a[i][j] - a[i][k]*a[k][j];
}
}
}
clk = clock() - clk;
cout << "Time: " << ((double)clk)/CLOCKS_PER_SEC << "\n";
cout << iter << endl;
}
с использованием g++ 5.4.1 для компиляции С++ 14.
Я попробовал код для различных значений N. Однако что-то действительно странное происходит вокруг N = 500. Время выполнения указано ниже. (Это выходы кода для различных значений N.
N = 200 : 0.022136
N = 300 : 0.06792
N = 400 : 0.149622
N = 500 : 11.8341
N = 600 : 0.508186
N = 700 : 0.805481
N = 800 : 1.2062
N = 900 : 1.7092
N = 1000 : 2.35809
Я много раз пробовал N = 500, а также на другой машине, чтобы получить аналогичные результаты.
Около 500 мы имеем следующее:
N = 494 : 0.282626
N = 495 : 0.284564
N = 496 : 11.5308
N = 497 : 0.288031
N = 498 : 0.289903
N = 499 : 11.9615
N = 500 : 12.4032
N = 501 : 0.293737
N = 502 : 0.295729
N = 503 : 0.297859
N = 504 : 12.4154
N = 505 : 0.301002
N = 506 : 0.304718
N = 507 : 12.4385
Почему это происходит?
Ответы
Ответ 1
Ваша программа может иметь переполнения с плавающей запятой и операции, которые приводят к NaN для определенных случаев (если расчет приводит к бесконечности /NaN, то он распространяется для вашего алгоритма, поэтому почти все числа становятся бесконечными /NaN. Это зависит от rand()
Если вы измените семя с помощью srand()
, вы можете не замедлить работу для случая N=500
).
И поскольку вы используете long double
, скомпилированная программа использует FPU (вы можете воспроизвести это с помощью float
или double
, если вы скомпилируете FPU вместо SSE). Кажется, что FPU обрабатывает бесконечные числа намного медленнее, чем нормальные числа.
Вы можете легко воспроизвести эту проблему с помощью этого фрагмента:
int main() {
volatile long double z = 2;
for (int i=0; i<10000000; i++) {
z *= z;
}
return z;
}
Если вы используете 2 для z
, эта программа выполняется медленно (z
будет переполняться). Если вы замените его на 1, он станет быстрым (z
не будет переполняться).
Подробнее об этом можно прочитать здесь: https://randomascii.wordpress.com/2012/05/20/thats-not-normalthe-performance-of-odd-floats/
Здесь соответствующая часть:
Влияние производительности на FPU x87
Производительность модулей Intels x87 на этих NaN и бесконечных довольно плохо. [...] Даже сегодня, на процессоре SandyBridge, x90 FPU вызывает <сильное > замедление около 370 к одному на NaN и бесконечности.