Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/135.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何在不使用任何SSE指令的情况下设置_m128i?_C++_Constants_Sse_Simd_Sse2 - Fatal编程技术网

C++ 如何在不使用任何SSE指令的情况下设置_m128i?

C++ 如何在不使用任何SSE指令的情况下设置_m128i?,c++,constants,sse,simd,sse2,C++,Constants,Sse,Simd,Sse2,我有许多函数使用相同的常量m128i值。 例如: const __m128i K8 = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); const __m128i K16 = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8); const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4); 所以我想把所有这些常数存储在一个地方。 但有一个问题:

我有许多函数使用相同的常量m128i值。 例如:

const __m128i K8 = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
const __m128i K16 = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8);
const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4);
所以我想把所有这些常数存储在一个地方。 但有一个问题:我在运行时检查现有的CPU扩展。 如果CPU不支持例如SSE(或AVX),则在初始化期间程序将崩溃


那么,是否可以在不使用SSE的情况下初始化这些常量?

我建议将初始化数据全局定义为标量数据,然后将其本地加载到
常量中:

static const uint8_t gK8[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

static inline foo()
{
    const __m128i K8 = _mm_loadu_si128((__m128i *)gK8);

    // ...
}

可以在不使用SSE指令的情况下初始化_m128i向量,但这取决于编译器如何定义_m128i

对于Microsoft Visual Studio,您可以定义下一个宏(它将_m128i定义为char[16]):

你可以使用工会

union M128 {
   char[16] i8;
   __m128i i128;
};

const M128 k8 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
如果M128联合是在使用循环的地方本地定义的,那么这应该没有性能开销(它将在循环开始时加载到内存中一次)。因为它包含类型为_m128i的变量,所以M128继承了正确的对齐方式

void foo()
{
   M128 k8 = ...;
   // use k8.i128 in your for loop
}
如果它是在其他地方定义的,那么在启动循环之前需要复制到本地寄存器中,否则编译器可能无法对其进行优化

void foo()
{
    __m128i tmp = k8.i128;
    // for loop here
}
这将把k8加载到cpu寄存器中,并在循环期间保持它,只要有足够的空闲寄存器来执行循环体


根据您使用的编译器,这些联合可能已经定义(VS有),但编译器提供的定义可能不可移植。

您通常不需要这样做。编译器非常擅长对使用相同常量的多个函数使用相同的存储。就像将同一字符串文字的多个实例合并为一个字符串常量一样,不同函数中相同
\u mm\u set*
的多个实例都将从相同的向量常量加载(或对于
\u mm\u setzero\u si128()
\u mm\u set1\u epi8(-1)

使用Godbolt的二进制输出(反汇编)模式可以查看是否从同一内存块加载不同的函数。看看它添加的注释,它将RIP相对地址解析为绝对地址

  • gcc:,无论它们是来自自动矢量化还是
    \u mm\u set
    。32B常量不能与16B常量重叠,即使16B常量是32B常量的子集

  • 叮当声:。16B和32B常量不重叠,即使其中一个是另一个的子集。某些使用重复常量的函数使用AVX2
    vpbroadcasted
    broadcast加载(在英特尔SnB系列CPU上甚至不使用ALU uop)。出于某种原因,它选择基于操作的元素大小而不是常量的重复性来执行此操作。请注意,clang的asm输出会在每次使用时重复该常量,但最终的二进制文件不会重复

  • MSVC:。与gcc的功能几乎相同。(完整的asm输出很难通过;请使用搜索。我只能通过让
    main
    找到.exe的路径来获取asm,然后用
    cl.exe-O2/FAs
    计算asm输出的路径,然后运行
    system(“type…/foo.asm”)

编译器擅长于此,因为这不是一个新问题。从最早的编译器时代起,它就存在于字符串中

我还没有检查这是否适用于源文件(例如,对于在多个编译单元中使用的内联向量函数)。如果仍然需要静态/全局向量常量,请参见以下内容:


似乎没有简单且可移植的方法静态初始化静态/全局
\uuuu m128
。C编译器甚至不接受
\u mm\u set*
作为初始值设定项,因为它的工作方式类似于函数。他们没有利用这样一个事实,即他们实际上可以看穿编译时常量16B

const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4);   // Illegal in C
// C++: generates a constructor that copies from .rodata to the BSS
即使构造函数只需要SSE1或SSE2,您也不希望这样。太可怕了不要这样做。最后,您要支付两次常量的内存开销



Fabio的
union
答案看起来是静态初始化向量常量的最佳可移植方式,但它意味着您必须访问
\uuum128i
union成员。它可能有助于将相关常量彼此靠近(希望在同一缓存线中)分组,即使它们被分散的函数使用。也有一些不可移植的方法来实现这一点(例如,将相关常量放在它们自己的ELF部分中)。希望这可以将它们组合在
.rodata
部分(成为
.text
部分的一部分)。

我认为这不是问题。即使它们是全局常量,编译器也会将它们转换为浮点数据包(常量值),这些数据包在运行时从“数据”部分加载。假设您只在已验证支持SSE指令的代码路径中使用这些常量,那么您永远不会在不支持SSE指令的平台上执行任何SSE指令。请检查汇编代码的输出以确定。对不起,我不明白所有这些代码的目的是什么。它如何回答这个问题?他已经知道如何初始化常量。为什么你的代码能更好地做到这一点?这是问题的直接答案。作者询问了在不使用SSE指令的情况下如何初始化_m128i。我给出了初始化的例子。啊,我明白了。这就是我错过的。我在答案中添加了这个澄清。我检查了assember输出。它不包含SSE指令。谢谢@ErmIg:这是一条指令,通常在函数项上(为什么要多次加载常数?),因此,除非它是一个非常小的函数,或者很少调用,加载常数会导致缓存丢失,否则我认为成本不会太大。当然。@Paul\R:这是我
void foo()
{
   M128 k8 = ...;
   // use k8.i128 in your for loop
}
void foo()
{
    __m128i tmp = k8.i128;
    // for loop here
}
const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4);   // Illegal in C
// C++: generates a constructor that copies from .rodata to the BSS