我正在使用大量数学函数的算法,最近我们在一个Ubuntu系统上从Solaris平台上移植了g ++ 4.8.2下的代码.
令人惊讶的是,一些算法比以前花了很多时间.背后的原因是该std::tan()功能比做的长两倍std::sin()/std::cos().
用sin/cos代替tan可以大大减少相同结果的计算时间.我想知道为什么会有这样的差异.是因为标准库的实现?棕褐色功能不应该更有效吗?
我写了一个程序来检查函数的时间:
#include <cmath>
#include <iostream>
#include <chrono>
int main(int argc, char * argv[])
{
using namespace std::chrono;
auto start_tan = system_clock::now();
for (int i = 0; i < 50000; ++i)
{
const double & a = static_cast<double>(i);
const double & b = std::tan(a);
}
auto end_tan = system_clock::now();
auto elapsed_time_tan = end_tan - start_tan;
std::cout << "tan : ";
std::cout << elapsed_time_tan.count() << std::endl;
auto start_sincos = system_clock::now();
for (int i = 0; i < 50000; ++i)
{
const double & a = static_cast<double>(i);
const double & b = std::sin(a) / std::cos(a);
}
auto end_sincos = system_clock::now();
auto elapsed_time_sincos = end_sincos - start_sincos;
std::cout << "sincos : " << elapsed_time_sincos.count() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
事实上,在输出中,我有以下时间没有优化:
tan : 8319960
sincos : 4736988
Run Code Online (Sandbox Code Playgroud)
并通过优化(-O2):
tan : 294
sincos : 120
Run Code Online (Sandbox Code Playgroud)
如果有人对这种行为有任何想法.
编辑
我根据@Basile Starynkevitch的回复修改了程序:
#include <cmath>
#include <iostream>
#include <chrono>
int main(int argc, char * argv[])
{
using namespace std::chrono;
if (argc != 2)
{
std::cout << "Need one and only argument : the number of iteration." << std::endl;
return 1;
}
int nb_iter = std::atoi(argv[1]);
std::cout << "Number of iteration programmed : " << nb_iter << std::endl;
double tan_sum = 0.0;
auto start_tan = system_clock::now();
for (int i = 0; i < nb_iter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::tan(a);
tan_sum += b;
}
auto end_tan = system_clock::now();
auto elapsed_time_tan = end_tan - start_tan;
std::cout << "tan : " << elapsed_time_tan.count() << std::endl;
std::cout << "tan sum : " << tan_sum << std::endl;
double sincos_sum = 0.0;
auto start_sincos = system_clock::now();
for (int i = 0; i < nb_iter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::sin(a) / std::cos(a);
sincos_sum += b;
}
auto end_sincos = system_clock::now();
auto elapsed_time_sincos = end_sincos - start_sincos;
std::cout << "sincos : " << elapsed_time_sincos.count() << std::endl;
std::cout << "sincos sum : " << sincos_sum << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
现在结果我-O2只得到类似的时间:
tan : 8345021
sincos : 7838740
Run Code Online (Sandbox Code Playgroud)
但仍然存在差异-O2 -mtune=native,但确实更快:
tan : 5426201
sincos : 3721938
Run Code Online (Sandbox Code Playgroud)
我不会用,-ffast-math因为我需要保持IEEE合规性.
您不应该关心非优化代码.
关于优化,GCC编译器可能会抛弃循环,因为您不对结果执行任何操作.BTW b不应该是一个const double&参考,而是一个const double.
如果你想要一个有意义的基准,尝试存储b(或总结).并使迭代次数(50000)成为运行时参数(例如int nbiter = (argc>1)?atoi(argv[1]):1000;)
您可能希望将-O2 -ffast-math -mtune=native优化标记传递给g++(请注意,-ffast-math在优化细节中不符合标准)
随着那些标志与我的变化:
double sumtan=0.0, sumsincos=0.0;
int nbiter = argc>1?atoi(argv[1]):10000;
Run Code Online (Sandbox Code Playgroud)
和
for (int i = 0; i < nbiter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::tan(a);
sumtan += b;
}
Run Code Online (Sandbox Code Playgroud)
和
for (int i = 0; i < nbiter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::sin(a) / std::cos(a);
sumsincos += b;
}
Run Code Online (Sandbox Code Playgroud)
和
std::cout << "tan : " << elapsed_time_tan.count()
<< " sumtan=" << sumtan << std::endl;
Run Code Online (Sandbox Code Playgroud)
和
std::cout << "sincos : " << elapsed_time_sincos.count()
<< " sumsincos=" << sumsincos << std::endl;
Run Code Online (Sandbox Code Playgroud)
使用GCC 4.9.2编译
g++ -std=c++11 -O2 -Wall -ffast-math -mtune=native b.cc -o b.bin
Run Code Online (Sandbox Code Playgroud)
我的时间非常相似:
% ./b.bin 1000000
tan : 77158579 sumtan=-3.42432e+06
sincos : 70219657 sumsincos=-3.42432e+06
Run Code Online (Sandbox Code Playgroud)
这是一款4年前的台式机(英特尔(R)Xeon(R)CPU X3430 @ 2.40GHz)
如果用clang++3.5.0 编译
tan : 78098229 sumtan=-3.42432e+06
sincos : 106817614 sumsincos=-3.42432e+06
Run Code Online (Sandbox Code Playgroud)
PS.时间(和相对表现)是不同的-O3.而一些处理器对机器指令sin,cos并且tan但他们可能无法使用(因为编译器或libm知道它们比常规慢).GCC有内建这些.