使用数组表示法访问 SSE 向量寄存器
Accessing SSE vector registers using array notation
当我惊奇地发现以下方法有效时,我正在摆弄 sse。我试图访问 __m128.
中的单个浮点数
__m128 x = _mm_set_ps(1.0, 2.0, 3.0, 4.0);
cout << x[1] << endl;
在这里我可以像读取数组一样读取它。 x[0] 打印 4,x[1] 打印 3,这与我的预期相反,但这可能与字节顺序有关。我想知道的是这是一种标准/推荐的方式来读取甚至写入向量的各个组件。我在网上看到的所有例子似乎都在使用向量类型和数组之间的联合。
不,那是不合法的,MSDN 文档明确警告不要这样做。
在 C++ 中,您可以使用 valarray
。在 C 中,大多数编译器都支持 GCC 扩展 __attribute__ ((vector_size(N)))
.
在自 Parallel Studio 2011 以来的英特尔编译器上,MSVC 甚至更长,但 GCC 不是(尽管 GCC 和 Clang 确实支持类型双关),您可以编写:
__m128 result = foo();
float f1 = result.m128_f32[0];
无论这是否是一般的未定义行为,您可能关心的编译器都支持它,未来的实现不太可能编译使用 m128_f32
的代码但默默地破坏它。您也许可以使用其他内在函数(例如 _mm_store_ss()
)来提取浮点数。
最便携的解决方案是memcpy()
。
您在问两个问题:read/write x86 SIMD 寄存器的顺序和执行此操作的方法。
您可以像这样以大端格式设置寄存器的值
__m128 x = _mm_set_ps(4.0, 3.0, 2.0, 1.0)
或者像这样的小端格式
__m128 x = _mm_setr_ps(1.0, 2.0, 3.0, 4.0) // r presumably means reverse
混淆可能来自数组。我们 write/store 数组采用小端格式,与硬件无关。我的意思是例如我们写
float xa[] = {1.0, 2.0, 3.0, 4.0};
(x86 架构以小端格式存储每个浮点数的字节,但这是一个单独的问题)。
所以只有一种方法可以按顺序加载数组
__m128 x = _mm_loadu_ps(xa);
现在我们可以回答你的另一个问题了。如果您想访问 SSE 寄存器的多个元素,最好的方法是将值存储到这样的数组中
float t[4]; _mm_storeu_ps(t, x);
由于是存储到数组中,所以就顺序而言只有一种方法,就像加载一样。
我认为使用存储内在函数是最好的解决方案,因为它不依赖于特定于编译器的实现。这将适用于 C 和 C++ 中的 GCC、ICC、Clang 和 MSVC。这就是内在函数的意义所在。它们为您提供不依赖于特定编译器实现或汇编语法的类似汇编的功能。
但如果您只想要第一个元素,请使用 _mm_cvtss_f32
。
This is also worth reading.
更多关于 enddianess 的信息。
如果我们以小端格式编写数字,可能会减少混淆。考虑以我们通常写数字的方式(big-endian 风格)比较分行写的整数:
54321
4321
321
21
1
我们最终通过输入适当的空格来右对齐数字。如果我们使用小端格式,它应该是
12345
1234
123
1
这需要较少的编写工作,但我们可能会从右到左阅读数字。
当我惊奇地发现以下方法有效时,我正在摆弄 sse。我试图访问 __m128.
中的单个浮点数__m128 x = _mm_set_ps(1.0, 2.0, 3.0, 4.0);
cout << x[1] << endl;
在这里我可以像读取数组一样读取它。 x[0] 打印 4,x[1] 打印 3,这与我的预期相反,但这可能与字节顺序有关。我想知道的是这是一种标准/推荐的方式来读取甚至写入向量的各个组件。我在网上看到的所有例子似乎都在使用向量类型和数组之间的联合。
不,那是不合法的,MSDN 文档明确警告不要这样做。
在 C++ 中,您可以使用 valarray
。在 C 中,大多数编译器都支持 GCC 扩展 __attribute__ ((vector_size(N)))
.
在自 Parallel Studio 2011 以来的英特尔编译器上,MSVC 甚至更长,但 GCC 不是(尽管 GCC 和 Clang 确实支持类型双关),您可以编写:
__m128 result = foo();
float f1 = result.m128_f32[0];
无论这是否是一般的未定义行为,您可能关心的编译器都支持它,未来的实现不太可能编译使用 m128_f32
的代码但默默地破坏它。您也许可以使用其他内在函数(例如 _mm_store_ss()
)来提取浮点数。
最便携的解决方案是memcpy()
。
您在问两个问题:read/write x86 SIMD 寄存器的顺序和执行此操作的方法。
您可以像这样以大端格式设置寄存器的值
__m128 x = _mm_set_ps(4.0, 3.0, 2.0, 1.0)
或者像这样的小端格式
__m128 x = _mm_setr_ps(1.0, 2.0, 3.0, 4.0) // r presumably means reverse
混淆可能来自数组。我们 write/store 数组采用小端格式,与硬件无关。我的意思是例如我们写
float xa[] = {1.0, 2.0, 3.0, 4.0};
(x86 架构以小端格式存储每个浮点数的字节,但这是一个单独的问题)。
所以只有一种方法可以按顺序加载数组
__m128 x = _mm_loadu_ps(xa);
现在我们可以回答你的另一个问题了。如果您想访问 SSE 寄存器的多个元素,最好的方法是将值存储到这样的数组中
float t[4]; _mm_storeu_ps(t, x);
由于是存储到数组中,所以就顺序而言只有一种方法,就像加载一样。
我认为使用存储内在函数是最好的解决方案,因为它不依赖于特定于编译器的实现。这将适用于 C 和 C++ 中的 GCC、ICC、Clang 和 MSVC。这就是内在函数的意义所在。它们为您提供不依赖于特定编译器实现或汇编语法的类似汇编的功能。
但如果您只想要第一个元素,请使用 _mm_cvtss_f32
。
This is also worth reading.
更多关于 enddianess 的信息。
如果我们以小端格式编写数字,可能会减少混淆。考虑以我们通常写数字的方式(big-endian 风格)比较分行写的整数:
54321
4321
321
21
1
我们最终通过输入适当的空格来右对齐数字。如果我们使用小端格式,它应该是
12345
1234
123
1
这需要较少的编写工作,但我们可能会从右到左阅读数字。