我正在用C++做一些性能关键的工作,我们目前正在使用整数计算来解决本质上浮点的问题,因为"它更快".这会导致很多令人烦恼的问题,并增加了许多烦人的代码.
现在,我记得在大约386天的时间里读到关于浮点计算如此缓慢的情况,我相信(IIRC)有一个可选的共同进程.但是现在肯定会有指数级更复杂和更强大的CPU,如果进行浮点或整数计算,它在"速度"上没有区别吗?特别是因为与导致管道停滞或从主存储器中取出某些东西相比,实际计算时间很短?
我知道正确的答案是在目标硬件上进行基准测试,测试它的好方法是什么?我编写了两个很小的C++程序,并将它们的运行时间与Linux上的"时间"进行了比较,但实际的运行时间变化太大(无法帮助我在虚拟服务器上运行).没有花一整天的时间来运行数百个基准测试,制作图表等等,我可以做些什么来合理地测试相对速度?任何想法或想法?我完全错了吗?
我使用的程序如下,它们不相同:
#include#include #include #include int main( int argc, char** argv ) { int accum = 0; srand( time( NULL ) ); for( unsigned int i = 0; i < 100000000; ++i ) { accum += rand( ) % 365; } std::cout << accum << std::endl; return 0; }
计划2:
#include#include #include #include int main( int argc, char** argv ) { float accum = 0; srand( time( NULL ) ); for( unsigned int i = 0; i < 100000000; ++i ) { accum += (float)( rand( ) % 365 ); } std::cout << accum << std::endl; return 0; }
提前致谢!
编辑:我关心的平台是在桌面Linux和Windows机器上运行的常规x86或x86-64.
编辑2(粘贴自下面的评论):我们目前有广泛的代码库.我真的反对我们"因为整数计算更快而不能使用浮点数"的概括 - 而且我正在寻找一种方法(如果这是真的)来反驳这种广义假设.我意识到,除了完成所有工作并在之后进行分析之外,我们无法预测确切的结果.
无论如何,感谢您的所有优秀答案和帮助.随意添加其他东西:).
例如(较少的数字更快),
64位Intel Xeon X5550 @ 2.67GHz,gcc 4.1.2 -O3
short add/sub: 1.005460 [0] short mul/div: 3.926543 [0] long add/sub: 0.000000 [0] long mul/div: 7.378581 [0] long long add/sub: 0.000000 [0] long long mul/div: 7.378593 [0] float add/sub: 0.993583 [0] float mul/div: 1.821565 [0] double add/sub: 0.993884 [0] double mul/div: 1.988664 [0]
32位双核AMD Opteron(tm)处理器265 @ 1.81GHz,gcc 3.4.6 -O3
short add/sub: 0.553863 [0] short mul/div: 12.509163 [0] long add/sub: 0.556912 [0] long mul/div: 12.748019 [0] long long add/sub: 5.298999 [0] long long mul/div: 20.461186 [0] float add/sub: 2.688253 [0] float mul/div: 4.683886 [0] double add/sub: 2.700834 [0] double mul/div: 4.646755 [0]
正如丹指出,甚至当你正常化的时钟频率(这本身可以在流水线的设计被误导),结果会大相径庭基于CPU架构(个别ALU/FPU性能,以及实际的ALU /的FPU的数量可用每超标量设计中的核心,它影响并行执行多少独立操作 - 下面的代码不会执行后一个因素,因为下面的所有操作都是顺序依赖的.)
穷人的FPU/ALU操作基准:
#include#ifdef _WIN32 #include #else #include #endif #include #include double mygettime(void) { # ifdef _WIN32 struct _timeb tb; _ftime(&tb); return (double)tb.time + (0.001 * (double)tb.millitm); # else struct timeval tv; if(gettimeofday(&tv, 0) < 0) { perror("oops"); } return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec); # endif } template< typename Type > void my_test(const char* name) { Type v = 0; // Do not use constants or repeating values // to avoid loop unroll optimizations. // All values >0 to avoid division by 0 // Perform ten ops/iteration to reduce // impact of ++i below on measurements Type v0 = (Type)(rand() % 256)/16 + 1; Type v1 = (Type)(rand() % 256)/16 + 1; Type v2 = (Type)(rand() % 256)/16 + 1; Type v3 = (Type)(rand() % 256)/16 + 1; Type v4 = (Type)(rand() % 256)/16 + 1; Type v5 = (Type)(rand() % 256)/16 + 1; Type v6 = (Type)(rand() % 256)/16 + 1; Type v7 = (Type)(rand() % 256)/16 + 1; Type v8 = (Type)(rand() % 256)/16 + 1; Type v9 = (Type)(rand() % 256)/16 + 1; double t1 = mygettime(); for (size_t i = 0; i < 100000000; ++i) { v += v0; v -= v1; v += v2; v -= v3; v += v4; v -= v5; v += v6; v -= v7; v += v8; v -= v9; } // Pretend we make use of v so compiler doesn't optimize out // the loop completely printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1); t1 = mygettime(); for (size_t i = 0; i < 100000000; ++i) { v /= v0; v *= v1; v /= v2; v *= v3; v /= v4; v *= v5; v /= v6; v *= v7; v /= v8; v *= v9; } // Pretend we make use of v so compiler doesn't optimize out // the loop completely printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1); } int main() { my_test< short >("short"); my_test< long >("long"); my_test< long long >("long long"); my_test< float >("float"); my_test< double >("double"); return 0; }
唉,我只能给你一个"依赖"答案......
根据我的经验,性能有很多很多变量......特别是在整数和浮点数学之间.它在处理器之间变化很大(即使在同一系列中,例如x86),因为不同的处理器具有不同的"管道"长度.此外,一些操作通常非常简单(例如添加)并且具有通过处理器的加速路径,而其他操作(例如除法)需要更长时间.
另一个重要变量是数据所在的位置.如果您只需要添加一些值,那么所有数据都可以驻留在缓存中,可以将它们快速发送到CPU.已经具有高速缓存中的数据的非常非常慢的浮点运算将比需要从系统存储器复制整数的整数运算快许多倍.
我假设您正在提出这个问题,因为您正在开发一个性能关键型应用程序.如果您正在为x86体系结构进行开发,并且需要额外的性能,则可能需要考虑使用SSE扩展.这可以大大加快单精度浮点运算,因为可以同时对多个数据执行相同的操作,另外还有一个单独的*寄存器用于SSE操作.(我在你的第二个例子中注意到你使用"float"而不是"double",让我觉得你正在使用单精度数学).
*注意:使用旧的MMX指令实际上会减慢程序的速度,因为那些旧的指令实际上使用的是与FPU相同的寄存器,因此无法同时使用FPU和MMX.
定点和浮点数学之间的实际速度可能存在显着差异,但ALU与FPU的理论最佳情况吞吐量完全无关.相反,您的体系结构中没有使用的整数和浮点寄存器(实际寄存器,而不是寄存器名称)的数量(例如,用于循环控制),适合高速缓存行的每种类型的元素数量考虑到整数与浮点数学的不同语义,优化可能 - 这些效应将占主导地位.算法的数据依赖性在这里发挥了重要作用,因此没有一般比较可以预测问题的性能差距.
例如,整数加法是可交换的,因此如果编译器看到一个类似于用于基准测试的循环(假设随机数据是事先准备的,这样就不会模糊结果),它可以展开循环并计算部分和没有依赖关系,然后在循环终止时添加它们.但是对于浮点数,编译器必须按照您请求的相同顺序执行操作(您在那里有序列点,因此编译器必须保证相同的结果,这不允许重新排序)所以每次添加都有很强的依赖性.前一个的结果.
您也可能同时在缓存中容纳更多整数操作数.因此,即使在FPU理论上具有更高吞吐量的机器上,定点版本也可能优于浮动版本一个数量级.
加法比快rand
,因此你的程序(尤其)是无用的.
您需要识别性能热点并逐步修改程序.听起来您的开发环境存在问题需要首先解决.对于小问题集,是否无法在PC上运行程序?
通常,尝试使用整数运算的FP作业是缓慢的处方.
TIL这很多(很多).以下是使用gnu编译器的一些结果(顺便说一下我也通过在机器上编译来检查,来自xenial的gnu g ++ 5.4比精确的linaro上的4.6.3快得多)
英特尔i7 4700MQ xenial
short add: 0.822491 short sub: 0.832757 short mul: 1.007533 short div: 3.459642 long add: 0.824088 long sub: 0.867495 long mul: 1.017164 long div: 5.662498 long long add: 0.873705 long long sub: 0.873177 long long mul: 1.019648 long long div: 5.657374 float add: 1.137084 float sub: 1.140690 float mul: 1.410767 float div: 2.093982 double add: 1.139156 double sub: 1.146221 double mul: 1.405541 double div: 2.093173
英特尔i3 2370M也有类似的结果
short add: 1.369983 short sub: 1.235122 short mul: 1.345993 short div: 4.198790 long add: 1.224552 long sub: 1.223314 long mul: 1.346309 long div: 7.275912 long long add: 1.235526 long long sub: 1.223865 long long mul: 1.346409 long long div: 7.271491 float add: 1.507352 float sub: 1.506573 float mul: 2.006751 float div: 2.762262 double add: 1.507561 double sub: 1.506817 double mul: 1.843164 double div: 2.877484
Intel(R)Celeron(R)2955U(运行xenial的Acer C720 Chromebook)
short add: 1.999639 short sub: 1.919501 short mul: 2.292759 short div: 7.801453 long add: 1.987842 long sub: 1.933746 long mul: 2.292715 long div: 12.797286 long long add: 1.920429 long long sub: 1.987339 long long mul: 2.292952 long long div: 12.795385 float add: 2.580141 float sub: 2.579344 float mul: 3.152459 float div: 4.716983 double add: 2.579279 double sub: 2.579290 double mul: 3.152649 double div: 4.691226
DigitalOcean 1GB Droplet Intel(R)Xeon(R)CPU E5-2630L v2(运行可靠)
short add: 1.094323 short sub: 1.095886 short mul: 1.356369 short div: 4.256722 long add: 1.111328 long sub: 1.079420 long mul: 1.356105 long div: 7.422517 long long add: 1.057854 long long sub: 1.099414 long long mul: 1.368913 long long div: 7.424180 float add: 1.516550 float sub: 1.544005 float mul: 1.879592 float div: 2.798318 double add: 1.534624 double sub: 1.533405 double mul: 1.866442 double div: 2.777649
AMD Opteron(tm)处理器4122(精确)
short add: 3.396932 short sub: 3.530665 short mul: 3.524118 short div: 15.226630 long add: 3.522978 long sub: 3.439746 long mul: 5.051004 long div: 15.125845 long long add: 4.008773 long long sub: 4.138124 long long mul: 5.090263 long long div: 14.769520 float add: 6.357209 float sub: 6.393084 float mul: 6.303037 float div: 17.541792 double add: 6.415921 double sub: 6.342832 double mul: 6.321899 double div: 15.362536
这使用来自http://pastebin.com/Kx8WGUfg的代码作为benchmark-pc.c
g++ -fpermissive -O3 -o benchmark-pc benchmark-pc.c
我已经多次通过,但这似乎是一般数字相同的情况.
一个值得注意的例外似乎是ALU mul vs FPU mul.加法和减法似乎微不足道.
以上是图表形式(点击查看完整尺寸,更低更快更好):
https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc
i7 4700MQ Linux Ubuntu Xenial 64位(应用于2018-03-13的所有补丁)short add: 0.773049 short sub: 0.789793 short mul: 0.960152 short div: 3.273668 int add: 0.837695 int sub: 0.804066 int mul: 0.960840 int div: 3.281113 long add: 0.829946 long sub: 0.829168 long mul: 0.960717 long div: 5.363420 long long add: 0.828654 long long sub: 0.805897 long long mul: 0.964164 long long div: 5.359342 float add: 1.081649 float sub: 1.080351 float mul: 1.323401 float div: 1.984582 double add: 1.081079 double sub: 1.082572 double mul: 1.323857 double div: 1.968488AMD Opteron(tm)处理器4122(精确,DreamHost共享主机)
short add: 1.235603 short sub: 1.235017 short mul: 1.280661 short div: 5.535520 int add: 1.233110 int sub: 1.232561 int mul: 1.280593 int div: 5.350998 long add: 1.281022 long sub: 1.251045 long mul: 1.834241 long div: 5.350325 long long add: 1.279738 long long sub: 1.249189 long long mul: 1.841852 long long div: 5.351960 float add: 2.307852 float sub: 2.305122 float mul: 2.298346 float div: 4.833562 double add: 2.305454 double sub: 2.307195 double mul: 2.302797 double div: 5.485736Intel Xeon E5-2630L v2 @ 2.4GHz(Trusty 64位,DigitalOcean VPS)
short add: 1.040745 short sub: 0.998255 short mul: 1.240751 short div: 3.900671 int add: 1.054430 int sub: 1.000328 int mul: 1.250496 int div: 3.904415 long add: 0.995786 long sub: 1.021743 long mul: 1.335557 long div: 7.693886 long long add: 1.139643 long long sub: 1.103039 long long mul: 1.409939 long long div: 7.652080 float add: 1.572640 float sub: 1.532714 float mul: 1.864489 float div: 2.825330 double add: 1.535827 double sub: 1.535055 double mul: 1.881584 double div: 2.777245
需要考虑的两点 -
现代硬件可以重叠指令,并行执行它们并对它们重新排序以充分利用硬件.而且,任何重要的浮点程序都可能具有重要的整数工作,即使它只是计算索引到数组,循环计数器等等所以即使你有一个慢浮点指令它也可能在一个单独的硬件上运行与一些整数工作重叠.我的观点是,即使浮点指令慢于整数,您的整体程序也可能运行得更快,因为它可以使用更多的硬件.
与往常一样,唯一可以确定的是分析您的实际程序.
第二点是,现在大多数CPU都有浮点SIMD指令,可以同时对多个浮点值进行操作.例如,您可以将4个浮点数加载到单个SSE寄存器中,并在它们上并行执行4次乘法运算.如果你可以重写部分代码来使用SSE指令,那么它似乎比整数版本更快.Visual c ++提供了编译器内部函数来执行此操作,有关一些信息,请参阅http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx.
如果没有余数运算,则浮点版本会慢很多。由于所有添加都是顺序的,因此cpu将无法并行化求和。延迟至关重要。FPU添加延迟通常为3个周期,而整数添加为1个周期。但是,余数运算符的分频器可能是关键部分,因为它在现代CPU上尚未完全流水线化。因此,假设除法/余数指令将消耗大量时间,则由于添加延迟而导致的差异将很小。