使用 g++ 4.8.2 时,tan() 计算比 sin()/cos() 长两倍
tan() computation is two times longer than sin()/cos() with g++ 4.8.2
我正在研究使用大量数学函数的算法,最近我们将代码移植到来自 Solaris 平台的 Ubuntu 系统上的 g++ 4.8.2 下。
令人惊讶的是,一些算法比以前花费了很多时间。背后的原因是 std::tan()
函数比 std::sin()/std::cos()
.
长两倍
用 sin/cos 替换 tan 大大减少了相同结果的计算时间。我想知道为什么会有这样的差异。是因为标准库的实现吗? 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;
}
事实上,在输出中我有以下未优化的时间:
tan : 8319960
sincos : 4736988
并进行优化 (-O2):
tan : 294
sincos : 120
如果有人对这种行为有任何想法。
编辑
我根据@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;
}
现在结果是 -O2
的时间差不多 :
tan : 8345021
sincos : 7838740
但是和-O2 -mtune=native
还是有区别的,但是确实更快:
tan : 5426201
sincos : 3721938
我不会使用 -ffast-math
,因为我需要保持 IEEE 合规性。
您不应该关心未优化的代码。
关于优化,GCC 编译器可能会抛出循环,因为您没有对结果做任何事情。顺便说一句 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;
和
for (int i = 0; i < nbiter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::tan(a);
sumtan += b;
}
和
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;
}
和
std::cout << "tan : " << elapsed_time_tan.count()
<< " sumtan=" << sumtan << std::endl;
和
std::cout << "sincos : " << elapsed_time_sincos.count()
<< " sumsincos=" << sumsincos << std::endl;
使用
使用 GCC 4.9.2 编译
g++ -std=c++11 -O2 -Wall -ffast-math -mtune=native b.cc -o b.bin
我得到的时间非常相似:
% ./b.bin 1000000
tan : 77158579 sumtan=-3.42432e+06
sincos : 70219657 sumsincos=-3.42432e+06
这是一台 4 年前的台式机(Intel(R) Xeon(R) CPU X3430 @ 2.40GHz)
如果使用 clang++
3.5.0
编译
tan : 78098229 sumtan=-3.42432e+06
sincos : 106817614 sumsincos=-3.42432e+06
PS。时间(和相对性能)与 -O3
不同。有些处理器有 sin
、cos
和 tan
的机器指令,但它们可能不会被使用(因为编译器或 libm
知道它们比例程慢)。 GCC 有这些 builtins。
阅读英特尔开发人员手册。
三角函数不如 x86 上的其他数学函数准确,因此 sin / cos 不会给出与 tan 相同的结果,如果符合 IEEE 标准是您提出这个问题的原因,您应该记住这一点。
关于加速,sin和cos可以从同一条指令中得到,只要编译器不是脑死。以相同的精度计算 tan 需要更多的工作。因此,编译器无法在不违反标准的情况下替换 sin/cos。
根据这些小数点后几位对你是否重要,你可能需要看看这个
What is the error of trigonometric instructions on x86?
我正在研究使用大量数学函数的算法,最近我们将代码移植到来自 Solaris 平台的 Ubuntu 系统上的 g++ 4.8.2 下。
令人惊讶的是,一些算法比以前花费了很多时间。背后的原因是 std::tan()
函数比 std::sin()/std::cos()
.
用 sin/cos 替换 tan 大大减少了相同结果的计算时间。我想知道为什么会有这样的差异。是因为标准库的实现吗? 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;
}
事实上,在输出中我有以下未优化的时间:
tan : 8319960
sincos : 4736988
并进行优化 (-O2):
tan : 294
sincos : 120
如果有人对这种行为有任何想法。
编辑
我根据@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;
}
现在结果是 -O2
的时间差不多 :
tan : 8345021
sincos : 7838740
但是和-O2 -mtune=native
还是有区别的,但是确实更快:
tan : 5426201
sincos : 3721938
我不会使用 -ffast-math
,因为我需要保持 IEEE 合规性。
您不应该关心未优化的代码。
关于优化,GCC 编译器可能会抛出循环,因为您没有对结果做任何事情。顺便说一句 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;
和
for (int i = 0; i < nbiter; ++i)
{
const double & a = static_cast<double>(i);
const double b = std::tan(a);
sumtan += b;
}
和
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;
}
和
std::cout << "tan : " << elapsed_time_tan.count()
<< " sumtan=" << sumtan << std::endl;
和
std::cout << "sincos : " << elapsed_time_sincos.count()
<< " sumsincos=" << sumsincos << std::endl;
使用
使用 GCC 4.9.2 编译 g++ -std=c++11 -O2 -Wall -ffast-math -mtune=native b.cc -o b.bin
我得到的时间非常相似:
% ./b.bin 1000000
tan : 77158579 sumtan=-3.42432e+06
sincos : 70219657 sumsincos=-3.42432e+06
这是一台 4 年前的台式机(Intel(R) Xeon(R) CPU X3430 @ 2.40GHz)
如果使用 clang++
3.5.0
tan : 78098229 sumtan=-3.42432e+06
sincos : 106817614 sumsincos=-3.42432e+06
PS。时间(和相对性能)与 -O3
不同。有些处理器有 sin
、cos
和 tan
的机器指令,但它们可能不会被使用(因为编译器或 libm
知道它们比例程慢)。 GCC 有这些 builtins。
阅读英特尔开发人员手册。 三角函数不如 x86 上的其他数学函数准确,因此 sin / cos 不会给出与 tan 相同的结果,如果符合 IEEE 标准是您提出这个问题的原因,您应该记住这一点。
关于加速,sin和cos可以从同一条指令中得到,只要编译器不是脑死。以相同的精度计算 tan 需要更多的工作。因此,编译器无法在不违反标准的情况下替换 sin/cos。
根据这些小数点后几位对你是否重要,你可能需要看看这个 What is the error of trigonometric instructions on x86?