【问题标题】:How to load into __m256 from a float* but reading backwards in memory as opposed to forwards?如何从浮点数*加载到 __m256 但在内存中向后读取而不是向前读取?
【发布时间】:2022-01-12 17:40:06
【问题描述】:

我有一个浮点数组,我想以相反的顺序访问它们。在我的非矢量化代码中,这很容易。

这是我拥有的数据的简化版本。

float A[8] = {a, b, c, d, e, f, g, h};
float B[8] = {s, t, u, v, w, x, y, z};

这是我想做的操作。

float C[8] = {a*z, b*y, c*x, d*w, e*v, f*u, g*t, h*s};

我希望能够进行某种load_ps 操作,它会给我这样的东西:

__m256 A_Loaded         = _mm256_load_ps(&A[0]);
                        = {a, b, c, d, e, f, g, h};

__m256 B_LoadedReversed = _mm256_loadr_ps(&B[7]);
                        = {z, y, x, w, v, u, t, s};

__m256 Output = _mm256_mul_ps(A_Loaded, B_LoadedReversed);
              = {a*z, b*y, c*x, d*w, e*v, f*u, g*t, h*s};

我拥有的一个数据源是一个查找表,因此如果迫在眉睫,可以反转,但更愿意避免这种情况,因为这会使程序的其他领域变得复杂。

我目前使用_mm256_set_ps() 并手动指向我需要的数据有一个拙劣的解决方法,但这并不像我想要的那样高效。

我知道有一个“颠倒的”_mm256_set_ps() (_mm256_setr_ps()),但似乎没有我需要的 _mm256_loadr_ps()

任何关于这个问题的想法和想法将不胜感激!提前致谢。

【问题讨论】:

  • 我从未使用过这些内在函数,但我尝试在使用-mavx 编译并查看assembly code 时手动执行此操作,它看起来还不错.我不知道在使用内部函数时它与汇编代码相比如何。
  • @Ted,在这种情况下,相反的是无操作。您在局部变量中全为零,因此编译器完全传播了常量并将其全部替换为 xorps。如果将浮点数从 main 移到 global,您会看到编译器发出代码,并且它不像 shuffle 那样最佳。
  • @Ted,奇怪的事实:在 MSVC STL 中,有一个优化可以显式实现 std::reverse 用于具有 SSE2/AVX2 shuffle 的琐碎类型,这里是 4 字节类型版本:github.com/microsoft/STL/blob/main/stl/src/… 所以不要依赖编译器,库方面有明确的帮助。
  • @AlexGuteniev 我明白了,谢谢!我没有自己尝试的工具包。看起来不错。哦,还有很酷的 MS 优化。
  • 术语:例如,“表演”是指你在舞台上作为演员。您正在寻找“高性能”。

标签: c++ c x86-64 intrinsics avx


【解决方案1】:

您不能将其表示为负载——所有负载都是“向前”的。

您必须使用洗牌操作。名称中有“permute”或“shuf”的东西。如果 AVX2 可用,_mm256_permutevar8x32_ps 可能适合您的情况。它在一个 shuffle 指令中完成所有工作,尽管它确实需要加载一个 shuffle-control 向量。如果只有 AVX1 可用,Dietrich 的回答建议使用两个 AVX1 shuffle 的方法。

类似这样的东西(如果我没有反转索引):

// AVX2
__m256 B_LoadedReversed = _mm256_permutevar8x32_ps(
                              _mm256_load_ps(&B[0]),  // load B[0..7]
                              _mm256_set_epi32(0, 1, 2, 3, 4, 5, 6, 7));

此类函数的参数之一是索引向量,或者对于其他 shuffle,例如 in-lane _mm256_permute_ps,它是一个 8 位立即数 (imm8)。

参数的每个元素都是源向量元素在目标向量中的位置。对于 imm8 有 2 位位置。

一些洗牌函数对给定向量的子向量执行多次洗牌,但不是这个。

许多 AVX+ shuffle 不会在 lanes(128 位组)之间进行shuffle,但这个可以。

【讨论】:

  • 您能否提供一个简短的示例来说明如何使用它?我找不到我能完全理解的关于该功能的文档。例如。面罩是如何使用的,如何使用它来解决这个问题?会不会在加载后用来反转数据?
  • @JamiePond,详细说明
  • Drat,抱歉,这是一条 AVX2 指令,我无法通过此应用程序支持它。你知道任何香草 AVX 替代品吗?
  • @JamiePond 看看另一个答案,它只是 AVX
【解决方案2】:

您可以使用_mm256_permute_ps_mm_256_permute2f128_ps 分两步反转__m256 中的顺序。

  • _mm256_permute_ps 允许您在每个“通道”内置换高和低 128 位块。

  • _mm_256_permute2f128_ps 允许您跨通道置换 128 位块。

是这样的:

__m256 b = _mm256_loadr_ps(&B[0]);
b = _mm256_permute_ps(b, _MM_SHUFFLE(3, 2, 1, 0));
b = _mm256_permute2f128_ps(b, b, 1);

英特尔内在函数指南中记录了这些说明:https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html

setr_ps 是如何工作的?

setr_ps() 如何逆转事情?它只是颠倒了论点。这是我从 GCC 安装中提取的版本:

extern __inline __m256 __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm256_setr_ps (float __A, float __B, float __C, float __D,
                float __E, float __F, float __G, float __H)
{
  return _mm256_set_ps (__H, __G, __F, __E, __D, __C, __B, __A);
}

您可以看到,setr_ps() 不对应任何底层处理器功能,它只是重新排序参数。

【讨论】:

  • 以下更改对我有用,谢谢! ``` __m256 b = _mm256_load_ps(&B[0]); b = _mm256_permute_ps(b, _MM_SHUFFLE(0, 1, 2, 3)); b = _mm256_permute2f128_ps(b, b, 1); ```
  • @JamiePond:如果可以用AVX2,Alex的vpermps回答效率更高;只进行一次车道交叉洗牌,而不是单独的车道交叉和车道内洗牌。如果您打算使用此 AVX1 版本,您可能希望执行两个单独的 128 位 (__m128) 加载作为 movups / vinsertf128 以提供您的内通道 vpermilps,特别是如果较旧Zen1 或 Sandybridge 等 CPU 是相关的。
猜你喜欢
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多