我正在用C ++做一些性能关键的工作,我们目前正在使用整数计算来解决本质上浮点的问题,因为“它更快”。这会导致很多令人讨厌的问题并且会增加许多烦人的代码。
现在,我记得在大约386天左右读取浮点计算如此缓慢的情况,我相信(IIRC)有一个可选的共同进程。但是现在肯定会有指数级更复杂和更强大的CPU,如果进行浮点或整数计算,它在“速度”上没有区别吗?特别是因为与导致管道停滞或从主存储器中取出某些东西相比,实际计算时间很短?
我知道正确的答案是在目标硬件上进行基准测试,测试它的好方法是什么?我编写了两个很小的C ++程序,并将它们的运行时间与Linux上的“时间”进行了比较,但实际的运行时间变化太大(无法帮助我在虚拟服务器上运行)。没有花一整天的时间来运行数百个基准测试,制作图表等等,我可以做些什么来合理地测试相对速度?任何想法或想法?我完全错了吗?
我使用的程序如下,它们无论如何都不相同:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>
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 <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>
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(粘贴自下面的评论):我们目前拥有广泛的代码库。我真的反对我们“因为整数计算更快而不能使用浮点数”的概括 - 而且我正在寻找一种方法(如果这是真的)来反驳这种广义假设。我意识到,除了完成所有工作并在之后进行剖析之外,我们无法预测确切的结果。
无论如何,感谢您的所有优秀答案和帮助。随意添加其他内容:)。
答案 0 :(得分:47)
例如(较少的数字更快),
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]
作为Dan pointed out,即使您对时钟频率进行标准化(在流水线设计中可能会产生误导),结果也会因CPU架构而异常变化(个别 { {3}} / ALU性能,以及实际 ALU / FPU数量可用于FPU个设计中的每个核心影响了多少superscalar - 下面的代码没有执行后一个因素,因为下面的所有操作都依次依赖。)
穷人的FPU / ALU操作基准:
#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>
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;
}
答案 1 :(得分:31)
根据我的经验,性能有很多很多变量......尤其是在整数和变量之间。浮点数学。它在处理器之间变化很大(即使在x86等同一系列中也是如此),因为不同的处理器具有不同的“管道”长度。此外,一些操作通常非常简单(例如添加)并且通过处理器具有加速路径,而其他操作(例如除法)需要更长时间。
另一个大变量是数据所在的位置。如果您只需要添加一些值,那么所有数据都可以驻留在缓存中,可以将它们快速发送到CPU。缓存中已有数据的非常非常慢的浮点运算将比需要从系统内存中复制整数的整数运算快许多倍。
我认为您提出这个问题是因为您正在开发一个性能关键型应用程序。如果您正在为x86体系结构进行开发,并且需要额外的性能,则可能需要考虑使用SSE扩展。这可以大大加快单精度浮点运算,因为可以同时对多个数据执行相同的操作,另外还有一个单独的*寄存器用于SSE操作。 (我在你的第二个例子中注意到你使用“float”而不是“double”,让我觉得你正在使用单精度数学)。
*注意:使用旧的MMX指令实际上会降低程序速度,因为那些旧指令实际上使用的是与FPU相同的寄存器,因此无法同时使用FPU和MMX。
答案 2 :(得分:19)
定点和浮点数学之间的实际速度可能存在显着差异,但ALU与FPU的理论最佳情况吞吐量完全无关。相反,您的体系结构中没有使用的整数和浮点寄存器(实际寄存器,而不是寄存器名称)的数量(例如,用于循环控制),适合高速缓存行的每种类型的元素数量考虑到整数与浮点数学的不同语义,优化可能 - 这些效应将占主导地位。算法的数据依赖性在这里起着重要作用,因此没有一般比较可以预测问题的性能差距。
例如,整数加法是可交换的,因此如果编译器看到一个类似于用于基准测试的循环(假设随机数据是事先准备的,那么它不会模糊结果),它可以展开循环并计算部分求和没有依赖关系,然后在循环终止时添加它们。但是对于浮点数,编译器必须按照您请求的相同顺序执行操作(您在那里有序列点,因此编译器必须保证相同的结果,这不允许重新排序)所以每次添加都有很强的依赖性。前一个的结果。
您可能同时在缓存中容纳更多整数操作数。因此,即使在FPU理论上具有更高吞吐量的机器上,定点版本的性能也可能超过浮动版本一个数量级。
答案 3 :(得分:18)
加法比rand
快得多,所以你的程序(尤其)无用。
您需要识别性能热点并逐步修改程序。听起来您的开发环境存在问题需要先解决。是否无法在PC上运行程序来处理小问题集?
通常,尝试使用整数运算的FP作业是缓慢的处方。
答案 4 :(得分:13)
TIL这很多(很多)。以下是使用gnu编译器的一些结果(顺便说一句我也通过在机器上编译来检查,来自xenial的gnu g ++ 5.4比精确的linaro快4.6.3)
Intel 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
Intel 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.968488
AMD 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.485736
Intel 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
答案 5 :(得分:7)
需要考虑两点 -
现代硬件可以重叠指令,并行执行它们并对它们重新排序以充分利用硬件。而且,任何重要的浮点程序都可能具有重要的整数工作,即使它只是计算索引到数组,循环计数器等等所以即使你有一个慢浮点指令它也可能在一个单独的硬件上运行与一些整数工作重叠。我的观点是,即使浮点指令慢于整数,您的整体程序也可能运行得更快,因为它可以使用更多的硬件。
与往常一样,唯一可以确定实际程序的方法。
第二点是,目前大多数CPU都有浮点SIMD指令,可以同时对多个浮点值进行操作。例如,您可以将4个浮点数加载到单个SSE寄存器中,并在它们上并行执行4次乘法运算。如果你可以重写部分代码来使用SSE指令,那么它似乎比整数版本更快。 Visual c ++提供了编译器内部函数来执行此操作,有关某些信息,请参阅http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx。
答案 6 :(得分:4)
除非您编写的代码每秒会被调用数百万次(例如,在图形应用程序中向屏幕绘制一条线),否则整数与浮点运算很少成为瓶颈。
效率问题的通常第一步是分析您的代码,以查看实际运行时间的位置。 linux命令是gprof
。
修改强>
虽然我认为你总是可以使用整数和浮点数来实现线条绘制算法,但是要多次调用它,看看它是否有所作为:
答案 7 :(得分:4)
今天,整数运算通常比浮点运算快一点。因此,如果您可以使用整数和浮点中的相同操作进行计算,请使用整数。但是你说的是#34;这会导致很多令人讨厌的问题并且会增加许多令人讨厌的代码&#34;。听起来你需要更多的操作,因为你使用整数运算而不是浮点运算。在这种情况下,浮点运行速度会更快,因为
一旦你需要更多的整数运算,你可能需要更多,所以轻微的速度优势远远超过额外的操作消耗
浮点代码更简单,这意味着编写代码更快,这意味着如果它对速度至关重要,您可以花更多时间优化代码。
答案 8 :(得分:3)
我运行了一个测试,只是在数字上加了1而不是rand()。结果(在x86-64上)是:
答案 9 :(得分:3)
如果没有余数操作,浮点版本会慢很多。由于所有添加都是顺序的,因此cpu将无法并行求和。延迟将至关重要。 FPU添加延迟通常为3个周期,而整数添加为1个周期。但是,余数运算符的除法器可能是关键部分,因为它在现代cpu上并没有完全流水线化。因此,假设除法/余数指令将消耗大部分时间,由于增加延迟而产生的差异将很小。
答案 10 :(得分:-1)
基于那种非常可靠的“我听过的东西”,在过去,整数计算的速度比浮动点快20到50倍,而现在它的速度不到两倍。