Я пишу несколько функций для дистрибутивов и использовал нормальный дистрибутив для запуска тестов между моей реализацией и C ++ Boost.
Учитывая функцию плотности вероятности (pdf: http://www.mathworks.com/help/stats/normpdf.html)
Который я написал так:
double NormalDistribution1D::prob(double x) {
return (1 / (sigma * (std::sqrt(boost::math::constants::pi<double>()*2))))*std::exp((-1 / 2)*(((x - mu) / sigma)*((x - mu) / sigma)));
}
Чтобы сравнить мои результаты с тем, как это делается с C ++ Boost:
boost::math::normal_distribution <> d(mu, sigma);
return boost::math::pdf(d, x);
Я не очень позитивно удивлен — мою версию взяли 44278 нано секунд, повышение
только 326.
Поэтому я немного поиграл и написал метод probboost в своем классе NormalDistribution1D-Class и сравнил все три:
void MATTest::runNormalDistribution1DTest1() {
double mu = 0;
double sigma = 1;
double x = 0;
std::chrono::high_resolution_clock::time_point tn_start = std::chrono::high_resolution_clock::now();
NormalDistribution1D *n = new NormalDistribution1D(mu, sigma);
double nres = n->prob(x);
std::chrono::high_resolution_clock::time_point tn_end = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point tdn_start = std::chrono::high_resolution_clock::now();
NormalDistribution1D *n1 = new NormalDistribution1D(mu, sigma);
double nres1 = n1->probboost(x);
std::chrono::high_resolution_clock::time_point tdn_end = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point td_start = std::chrono::high_resolution_clock::now();
boost::math::normal_distribution <> d(mu, sigma);
double dres = boost::math::pdf(d, x);
std::chrono::high_resolution_clock::time_point td_end = std::chrono::high_resolution_clock::now();
std::cout << "Mu : " << mu << "; Sigma: " << sigma << "; x" << x << std::endl;
if (nres == dres) {
std::cout << "Result" << nres << std::endl;
} else {
std::cout << "\033[1;31mRes incorrect: " << nres << "; Correct: " << dres << "\033[0m" << std::endl;
}auto duration_n = std::chrono::duration_cast<std::chrono::nanoseconds>(tn_end - tn_start).count();
auto duration_d = std::chrono::duration_cast<std::chrono::nanoseconds>(td_end - td_start).count();
auto duration_dn = std::chrono::duration_cast<std::chrono::nanoseconds>(tdn_end - tdn_start).count();
std::cout << "own boost: " << duration_dn << std::endl;
if (duration_n < duration_d) {
std::cout << "Boost: " << (duration_d) << "; own implementation: " << duration_n << std::endl;
} else {
std::cout << "\033[1;31mBoost faster: " << (duration_d) << "; than own implementation: " << duration_n << "\033[0m" << std::endl;
}
}
Результаты (компилирование и запуск метода проверки 3 раза)
собственное ускорение: 1082 Повышение быстрее: 326; чем собственная реализация: 44278
собственный буст: 774 Буст быстрее: 216; чем собственная реализация: 34291
собственное ускорение: 769 Повышение быстрее: 230; чем собственная реализация: 33456
Теперь это озадачивает меня:
Как это возможно, что метод из класса занимает в 3 раза больше, чем операторы, которые были вызваны напрямую?
Мои варианты компиляции:
g++ -O2 -c -g -std=c++11 -MMD -MP -MF "build/Debug/GNU-Linux-x86/main.o.d" -o build/Debug/GNU-Linux-x86/main.o main.cpp
g++ -O2 -o ***Classes***
Во-первых, вы распределяете свой объект динамически, с new
:
NormalDistribution1D *n = new NormalDistribution1D(mu, sigma);
double nres = n->prob(x);
Если бы вы делали то же самое, что и с бустом, одного этого было бы достаточно, чтобы иметь такую же (или сопоставимую) скорость:
NormalDistribution1D n(mu, sigma);
double nres = n.prob(x);
Теперь, я не знаю, как вы выразили свое выражение в NormalDistribution1D::prob()
это важно, но я скептически отношусь к тому, что было бы важно написать это более «оптимизированным» способом, потому что подобные арифметические выражения — это то, что компиляторы могут оптимизировать очень хорошо. Может быть, это будет быстрее, если вы используете --ffast-math
переключатель, который даст больше свободы оптимизации компилятору.
Кроме того, если определение double NormalDistribution1D::prob(double x)
находится в другом модуле компиляции (другой файл .cpp), компилятор не сможет включить его, и это также приведет к заметным накладным расходам (возможно, в два раза медленнее или меньше). В boost почти все реализовано внутри заголовков, поэтому встроенные строки всегда будут происходить, когда компилятор кажется подходящим. Вы можете преодолеть эту проблему, если вы компилируете и связываете с помощью gcc -flto
переключатель.
Вы не скомпилировали с -ffast-math
вариант. Это означает, что компилятор не может (на самом деле, не должен!) Упрощать (-1 / 2)*(((x - mu) / sigma)*((x - mu) / sigma))
в форме, аналогичной той, которая используется в boost::math::pdf
,
expo = (x - mu) / sigma
expo *= -x
expo /= 2
result = std::exp(expo)
result /= sigma * std::sqrt(2 * boost::math::constants::pi<double>())
Вышеуказанное заставляет компилятор выполнять быстрые (но потенциально небезопасные / неточные) вычисления, не прибегая к использованию -ffast_math
,
Во-вторых, разница во времени между приведенным выше кодом и вашим кодом минимальна по сравнению со временем, необходимым для выделения из кучи (new
) против стека (локальная переменная). Вы определяете стоимость выделения динамической памяти.