【问题标题】:How to calculate mod/remainder using SSE?如何使用 SSE 计算模数/余数?
【发布时间】:2020-12-26 20:42:01
【问题描述】:

在 x64/sse 上使用向量指令计算 x % M 的最佳/最快方法是什么? (我的意思是 mod/remainder)。

我找不到打包 mod 的任何操作码,所以我认为我能做的最好的是将 int 提升为浮点数,然后使用 DIVPS 和 ROUNDPS 计算 x - m * floor(x / m)。

或者有没有更好的选择我错过了?

UPDATE:M 只在运行时知道,实际循环如下所示:

unsigned x[SIZE], M[SIZE], answer[SIZE];
for (int i = 0; i < SIZE; i++) {
  answer[i] = x[i] % M[i];
}

如果有任何帮助,M 也已知在 1 - 640000000 范围内。

【问题讨论】:

  • 没有比这更快的了。此外,您可能需要检查一次舍入错误。如果小数部分非常接近0.5,您计算的商可能并不总是四舍五入到正确的整数。
  • 除非 M 是 2 的幂,否则我认为你不走运。
  • M 是编译时间常数吗?
  • 好的,我现在明白了,M 每次迭代都会改变M[i]。那我回答的方法对你没用。

标签: assembly sse division


【解决方案1】:

如果M 是编译时常量或在循环中是常量,那么您可以calculated a reciprocal and then do multiplication and a shift 代替使用除法。我们可以写

x/M = (x*(2^n/M))>>n

因子2^n/M(又名magic number)应在循环之前或编译时计算。

例如,如果我们想要x[i]/5,并且我们知道x[i] 小于2^15,我们可以使用2^n/M = 0xCCCDn = 18

#include <stdio.h>
#define N 32768
int x[N], y[N], z[N];

int main(void) {
    for(int i=0; i<N; i++) x[i] = i;
    int M = 5;
    int fact = 0xCCCD;
    int n = 18;
    for(int i=0; i<N; i++) {
        y[i] = x[i]/M;
        z[i] = (fact*x[i])>>n;
        if(y[i] != z[i]) printf("%d %d\n", y[i], z[i]);
    }
}

有几种不同的方法可以确定幻数和n。我使用Agner Fog's Vector Class Library(VCL)。它为您使用 SSE2 或 AVX2 处理 32 位数字(而不是上面代码中的 15 位数字)。如果您想查看汇编代码来执行此操作,他的 assembly library 也为 SSE2(可能还有 AVX2)执行此操作

有关详细信息,请参阅 VCL 手册的第 22 页。他的汇编库手册中也有描述。

【讨论】:

  • 对不起,我忘了告诉 M 只在运行时可用。我会更新问题。感谢您的回答,我不知道 Fog 的 VCL,它似乎非常有用。
  • @Ricbit M 不需要是编译时间常数。它只需要在一个循环中保持不变。计算factn 需要时间。如果您必须为每次迭代都这样做,那将比进行除法要慢。但是如果你在循环之前计算它们,那么使用乘法和移位会快得多。
  • 事实证明,即使不使用 sse,我也确实通过用两个乘法替换 mod 获得了收益。再次感谢您的想法。
  • 应该指出,对于 16 位数字,有 _mm_mulhi_epu16 来获取每个产品的高半部分(位 [31:16])。这意味着您不必扩大到 32 位,实际上也不应该因为 SSE4 pmulld 在 Haswell 及更高版本上较慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-17
  • 1970-01-01
  • 2018-10-26
  • 2018-04-19
  • 1970-01-01
  • 2014-06-11
  • 2013-04-01
相关资源
最近更新 更多