超快速舍入功能(PBC)

时间:2016-04-08 11:10:09

标签: c performance floating-point modeling

我真的需要在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;
}

固定边界,没有圆(),它的工作速度更快。 那么,也许有人知道更好的方法?...

2 个答案:

答案 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])

查看generated assembly

您还应该考虑对代码进行矢量化。

答案 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()的当前代码将是最快的选择,但你可以轻松地比使用向量快三倍。