我真的需要在C中使用非常快的round()函数 - 蒙特卡洛粒子建模是必要的: 在每个步骤中,您需要将坐标包装到周期性框中以计算体积交互:例如
for(int i=0; i < 3; i++)
{
coor.x[i] = a.XReal.x[i]-b.XReal.x[i];
coor.x[i] = coor.x[i] - SIZE[i]*round(coor.x[i]/SIZE[i]); //PBC
}
我遇到过一些asm hacking with it,但我根本不理解asm :) 像这样的东西
inline int float2int2(float flt)
{
int intgr;
__asm__ __volatile__ ("fld %1; fistp %0;" : "=m" (intgr) : "m" (flt));
return intgr;
}
固定边界,没有圆(),它的工作速度更快。 那么,也许有人知道更好的方法?...
答案 0 :(得分:4)
首先,您可以通过使用正确的编译器选项获得一些收益。以GCC和现代Intel CPU为例,您应该尝试:
-march=nehalem -fno-trapping-math
然后round
的问题是它使用特定的舍入模式,这在大多数平台上都很慢。 nearbyint
(或rint
)应始终更快:
coor.x[i] = coor.x[i] - SIZE[i] * nearbyint(coor.x[i] / SIZE[i])
您还应该考虑对代码进行矢量化。
答案 1 :(得分:2)
而不是寻找快速舍入,理想情况下,您希望将范围缩小的整个过程变得快速。正如@EOF在评论中准确指出的那样,您可以使用C99标准函数,例如remainderf()
或fmodf()
。
coor.x[i] -= SIZE[i]*round(coor.x[i]/SIZE[i]);
// same as
coor.x[i] = remainderf(coor.x[i], SIZE[i]);
fmodf(3)
向零舍入,remainderf(3)
rounds towards nearest。
remainder()
函数计算x
除以y
的余数。返回值为x-n*y
,其中n
为值x / y
,为四舍五入 到最近的整数。如果x-n*y
的绝对值为0.5,则选择n为偶数。
编译器/库有几种不同的策略来实现它们。使用-ffast-math
,gcc 5.3 for x86-64内联remainder(x,y)
实现,将值从SSE寄存器传输到x87寄存器,并在循环中运行FPREM1
(部分余数),直到它设置为表示结果正确的标志。 (FPREM1
的一次执行可以将指数减少至多63)。
clang始终会调用库函数,可以是普通的remainder
入口点,也可以是__remainder_finite
-ffast-math
。
GNU libm定义主要使用整数运算,来自反汇编and the C source的AFAICT。在最近具有快速硬件鸿沟的英特尔CPU上,它可能比你的div,round,mul版本慢。
div,round,mul,sub,快速舍入(使用nearbyint()
,它显然具有最不丑的语义,因此它最容易内联到roundsd
/ roundss
)。 这种方式可以矢量化,并一次完成所有三个坐标。可能需要手动完成,找到第4个元素不会出错的东西。在Intel Haswell上有128b向量:5 uops。单精度:divps
(10-13c延迟,每7c吞吐量一个),roundps
(2 uop,6c延迟,每2c吞吐量一个),mulps
(5c延迟,每个1 0.5c吞吐量),subps
(3c延迟,每1c吞吐量一个)。其中一些竞争对手执行端口。 总延迟:27c 。可能的吞吐量,可能类似每7c一个(完全被divps瓶颈)
gcc内联x87 FPREM1
。 (可能只需要运行一次迭代,所以Haswell:41 uops,27c延迟,每17c吞吐量一次,加上在xmm和x87 regs之间获取数据的一些开销。无法矢量化。
glibc的大多数整数实现:在现代x86 CPU上,不知道,可能比其他两个都差。但是, probably significantly higher accuracy 比手动div / round / mul / sub。
底线,如果这是一个速度问题,你应该明确地考虑使用SSE / AVX进行矢量化以在一个向量中完成一个点的所有三个坐标。或者,一次四个坐标,或任何方便的坐标。理想情况下,您可以使用矢量ALU的所有4个(或AVX)单精度元素。 (或2/4表示双精度)。
即使是标量,我认为使用nearbyint()
的当前代码将是最快的选择,但你可以轻松地比使用向量快三倍。