累计双打与整数

use*_*112 2 c++ floating-point cpu double performance

a声明为double时,以下代码较慢,而不是int:

double a = 0;
int j[1000];

for(int i=0; i<1000; i++){
    a += (i * j[i]);
}
Run Code Online (Sandbox Code Playgroud)

如果编译器选择不同的汇编指令而不是a声明为int时所选择的汇编指令会导致双精度加法的性能下降吗?

我试图了解CPU是否在运行时对单/双精度进行任何"转换",这在组装和成本执行时间中看不到?

gha*_*.st 6

让我们将循环中的表达式分解为各自的部分.为此,我们将重写代码,以便每行只有一个赋值和一个操作.从int版本开始时,它看起来如下所示:

// not depending on a
/* 1 */ auto t1 = j[i];
/* 2 */ auto t2 = i * j

// depending on decltype(a)
/* 3 */ decltype(a+t2) t3 = static_cast<decltype(a+t2)>(t2);
/* 4 */ decltype(a+t3) t4 = static_cast<decltype(a+t3)>(a);
/* 5 */ a = t3 + t4;
Run Code Online (Sandbox Code Playgroud)

前两个操作根本不依赖于类型a,并且在任何一种情况下都会完全相同.

但是,从操作3开始,存在差异.原因是添加at2编译器必须首先将它们转换为通用类型.在a整数的情况下,操作3和4什么都不做(int+ intyield int,所以两个强制转换都将ints 转换为ints).但是,在aa 的情况下double,t2必须在添加之前转换为double(int+ double给出double).

这意味着操作5中的添加类型也不同:它可以是一个intdouble另外一个.忽略一个明显的方面,即a double通常是a的两倍int,这意味着计算机此时需要做一些不同的事情.

对x64的影响

当使用优化编译器为现代x64机器编译该程序时,应该注意,当按原样说明时,整个程序可以被优化掉.假设这没有发生并且您的编译器不应用任何非法优化,并且您可以使用未初始化的变量(元素j)引入的UB ,可能会发生以下情况:

// not depending on a
MOV EAX, i // copy i to EAX register
IMUL j[i] // EAX = EAX * j[i] (high 32 bits are stored in EDX and ignored)

// if a is int
ADD a, EAX // integer addition: a += EAX

// else if a is double
CVTSI2SD XMM0, EAX // convert the multiplication result to double
ADDPD a, XMM0 // double addition: a += XMM0

// endif
Run Code Online (Sandbox Code Playgroud)

一个好的编译器会稍微展开循环并交错其中的一些,因为循环限制是已知的.正如您所看到的,操作至少有两倍的增加,以及依赖链的指令.此外,第二个版本中的指令比第一个版本中的单个指令.

虽然我确信第二个版本可以在一个更高效的版本中声明,但应该注意的是ADD,第一个版本的整数是几乎任何CPU上最快的操作之一,并且通常比它的浮点数更快.

因此,回答您的问题:CPU确实执行浮点和整数之间的转换 - 这些转换在程序集中可见并且具有(可能很大的)运行时成本.

单精度怎么样?

既然您也询问了单精度,那么让我们来看看使用时会发生什么float:

// not depending on a
MOV EAX, i // copy i to EAX register
IMUL j[i] // EAX = EAX * j[i] (high 32 bits are stored in EDX and ignored)

// if a is float
CVTSI2SS XMM0, EAX // convert the multiplication result to float
ADDPS a, XMM0 // float addition: a += XMM0
Run Code Online (Sandbox Code Playgroud)

该组件不显示显著差异(我们刚刚更换的两个D用于doubleSsingle).而且,有趣的是,性能上的差异也会很小(例如,Haswell核心将转换为1μop,而转换为浮动,而加法本身表现出相同的性能).

验证

为了验证我的声明,我已经运行了2000000次循环,并确保a没有优化.结果是:

int   : 601.1 ms
float : 2567 ms
double: 2593 ms
Run Code Online (Sandbox Code Playgroud)