【问题标题】:OpenMP reduction on SSE2 vectorSSE2 向量上的 OpenMP 缩减
【发布时间】:2019-06-11 01:17:05
【问题描述】:

我想使用 SSE2 内在函数计算每个通道的图像平均值(3 个感兴趣的通道 + 1 个我们在此忽略的 alpha 通道)。我试过了:

  __m128 average = _mm_setzero_ps();

  #pragma omp parallel for reduction(+:average)
  for(size_t k = 0; k < roi_out->height * roi_out->width * ch; k += ch)
  {
    float *in = ((float *)temp) + k;
    average += _mm_load_ps(in);
  }

但我在 GCC 中收到此错误:user-defined reduction not found for average

SSE2 有可能吗?怎么了?

编辑

这行得通:

float sum[4] = { 0.0f };

#pragma omp parallel for simd reduction(+:sum[:4])
for(size_t k = 0; k < roi_out->height * roi_out->width * ch; k += ch)
{
  float *in = ((float *)temp) + k;
  for (int i = 0; i < ch; ++i) sum[i] += in[i];
}

const __m128 average = _mm_load_ps(sum) / ((float)roi_out->height * roi_out->width);

【问题讨论】:

  • 如果您希望人们尝试改进您的代码,您应该提供一个最低限度的工作和可编译示例。也许你应该看看#pragma omp declare reduction。显然 gcc 抱怨它没有定义。
  • _mm_load_ps(in); 只有在 in 是 16 字节对齐时才是安全的。如果ch=3 则不会。此外,让编译器使用omp simd 进行自动向量化将有望让它使用多个累加器来隐藏 FP-add 延迟。如果它使用 3 或 6 个累加器,它可能会避免重叠并利用所有 12 或 24 个向量元素,并在最后整理混洗以将正确的元素加在一起成为 3 个不同的和。 (或者,如果可能的话,您应该自己这样做,以便将单线程性能提高 3 或 4(addps 延迟)乘以 4/3,以免浪费一个元素。)
  • _mm_loadu_ps(in)。或者像我建议的那样,使用 3 或 6 个向量手动进行向量化(因为 3*4 = 12 = lcm(channels, vector_width) 将通道与向量元素对齐),因此您只需要没有重叠的对齐负载。您也许可以让 OpenMP 帮助您并行化手动矢量化循环,但我强烈建议您使用它,以使每个线程的速度提高约 4 倍(如果您开始遇到 DRAM 或 L3 每核带宽瓶颈,则速度会更低,但是在诸如台式机或笔记本电脑之类的内核很少的机器上,您应该会获得很大的加速,并且只需要几个内核就可以使 DRAM 饱和。)
  • 哦,那很好,您的数据已对齐。您的问题说您的图像有 3 个通道,所以我假设 ch=3。你没有说有一个阿尔法通道。您仍然希望使用多个累加器来隐藏 FP 延迟,尤其是当您的数据在 L2 或 L3 缓存中可能很热时。但是每 4 个周期 16 个字节可能仍然是比内存更严重的瓶颈。
  • 没有为 simd 矢量类型预定义缩减。您可以在 gcc 的 bugzilla 上提交增强请求,这似乎是一个明智且简单的扩展。

标签: openmp sse simd


【解决方案1】:

您可以像这样用户定义自定义缩减:

#pragma omp declare reduction \
    (addps:__m128:omp_out+=omp_in) \
    initializer(omp_priv=_mm_setzero_ps())

然后像这样使用它:

#pragma omp parallel for reduction(addps:average)
for(size_t k = 0; k < size * ch; k += ch)
{
  average += _mm_loadu_ps(data+k);
}

我认为,最重要的是,openmp 需要知道如何为您的还原获取中性元素(此处为 _mm_setzero_ps())。

完整的工作示例:https://godbolt.org/z/Fpqttc

有趣的链接:http://pages.tacc.utexas.edu/~eijkhout/pcse/html/omp-reduction.html#User-definedreductions

【讨论】:

  • 在 Godbolt 输出中,每个线程运行的实际 addps 循环很难找到。似乎在get_average(float const*, unsigned long) [clone ._omp_fn.0]: 的最底部,主函数作为arg 传递给GOMP_parallel。它使用addps 和一个累加器,在 FP 上增加了延迟而不是吞吐量,从而实现了每个线程吞吐量的大约 1/8 (Skylake) 到 1/3 (Haswell),它可以在缓存中热数据的情况下实现吞吐量。我怀疑即使数据来自 DRAM,它也会帮助一些人使用多个累加器展开,尤其是使用 HT。
  • @PeterCordes,我玩了一下展开版:godbolt.org/z/ZJ5JGs(未经测试,不处理大小不能被 4 整除的情况,...)
  • 通常 clang 知道如何使用多个累加器展开。 IDK 为什么它不能在这里管理,即使-ffast-math :/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-27
  • 1970-01-01
  • 1970-01-01
  • 2016-05-12
  • 1970-01-01
  • 2021-06-17
  • 1970-01-01
相关资源
最近更新 更多