实际上,您要做的是编写一个循环,编译器可以轻松高效地进行向量化和并行化。如果您有两个数组n[i] 和m[i],那么任何现代编译器都可能在给出正确标志的情况下弄清楚如何优化n[i]*(n[i]+1)/2 + m[i]。试图强制编译器一次对一个词进行优化通常会适得其反。当您并行化关键循环时,现代硬件是最快的。如果您不想使用为此目的而设计的不可移植的内在函数或库,您可以通过最小化数据依赖性和编写易于静态分析的代码来最好地实现这一目标。
您可能无法使用(n*n + n)/2 + m 改进生成的代码,即将多项式转换为嵌套形式。这是高效的,因为它使代码生成器能够仅使用一个向量寄存器作为累加器,从而最大限度地增加 SIMD 可用的数量。您应该酌情使用restrict 和alignas 以启用最大优化。
(编辑: 负数的右移是实现定义的,因为它可能是逻辑的或算术的。我编写的代码执行无符号数学运算,这让编译器可以优化 /2 以>>1 给你。在评论中,robthebloke 提出,如果你使用有符号变量而不是无符号变量,并且你知道它们总是非负的,编译器可能无法静态推断这一点,因此可能不会将/2 优化为>>1。在这种情况下,您可以编写>>1 或强制转换(uint32_t)n[i] 来进行更好地定义的无符号数学。一个不安全的数学优化标志也可能重新启用它。)
这种矢量化可能比在每个元素上单独查找表要快。
结果将在 0 到 125,750 的范围内,这对于 unsigned short 来说太大了,因此可以容纳它的最小类型是 int32_t 或 uint32_t。 (或者uint_least32_t,如果你愿意的话。)使用最小类型的数组可以实现最大向量化。
如果您想帮助优化器,您可以启用 OpenMP 并添加 #pragma omp simd,以明确告诉编译器向量化此循环。您还可以使用 OpenMP 来启用多线程。
在 C++ 中,您可以选择 std::valarray<uint32_t> 或表达式模板,它们是表达这种令人尴尬的并行计算的非常优雅的方式。
以下程序compiles to vectorized code 在 GCC、Clang 或 ICC 上给出适当的优化标志。 Clang 编译成一个循环,每次迭代计算 256 个元素。
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#define N (1L<<20)
typedef uint_least32_t elem_t;
const elem_t n[N];
const elem_t m[N];
elem_t a[N];
int main(void)
{
for ( ptrdiff_t i = 0; i < N; ++i) {
a[i] = (n[i]*n[i] + n[i])/2 + m[i];
}
return EXIT_SUCCESS;
}
您可以尝试将 alignas 说明符添加到数组中,但这实际上不会导致 GCC、Clang 或 ICC 执行对齐的加载或存储。 (有一个 GCC 扩展来启用这种优化。)
如果启用 OpenMP 库(GCC 或 Clang 中的 -fopenmp),则可以添加该行
#pragma omp for
紧接在for 循环之前,或更复杂的版本,并获得a loop that is both multithreaded and vectorized。如果有一种方法可以通过标准的便携式 C 显着改进这一点,我很想亲自了解它。
我写的 MWE 很简单。在实际代码中,您可能希望将整个循环(该内部循环是其中的一部分)从main() 中移出并移到诸如
之类的函数中
elem_t* func( const ptrdiff_t nelems,
const elem_t n[nelems],
const elem_t m[nelems],
elem_t a[nelems]
)
{
for ( ptrdiff_t i = 0; i < nelems; ++i) {
a[i] = (n[i]*n[i] + n[i])/2 + m[i];
}
return a;
}
如果你比较生成的程序集,你会发现除非你内联它,否则它的效率并不高,主要是因为编译器不再知道编译时的迭代次数或有任何关于n对齐的信息, m 或 a.
您还可以通过将输入元素存储为uint16_t 来节省一些内存,但可能不会节省计算时间。输入数组使用一半的内存,但循环不能操作比以前更多的元素,因为计算使用相同大小的元素。小心将用于计算的临时值转换为不会溢出的类型!
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#define N (1L<<20)
const uint16_t n[N];
const uint16_t m[N];
uint32_t a[N];
int main(void)
{
for ( ptrdiff_t i = 0; i < N; ++i) {
a[i] = ((uint32_t)n[i]*n[i] + n[i])/2 + m[i];
}
return EXIT_SUCCESS;
}