PPL Combinable 的 SIMD 对齐问题

Posted

技术标签:

【中文标题】PPL Combinable 的 SIMD 对齐问题【英文标题】:SIMD alignment issue with PPL Combinable 【发布时间】:2015-07-12 14:45:04 【问题描述】:

我正在尝试将数组元素与 SIMD 并行求和。 为了避免锁定,我使用了可组合的本地线程,它并不总是在 16 个字节上对齐 因为 _mm_add_epi32 抛出异常

concurrency::combinable<__m128i> sum_combine;

int length = 40; // multiple of 8
concurrency::parallel_for(0, length , 8, [&](int it)


    __m128i v1 = _mm_load_si128(reinterpret_cast<__m128i*>(input_arr + it));
    __m128i v2 = _mm_load_si128(reinterpret_cast<__m128i*>(input_arr + it + sizeof(uint32_t)));

    auto temp = _mm_add_epi32(v1, v2);

    auto &sum = sum_combine.local();   // here is the problem 


    TRACE(L"%d\n", it);
    TRACE(L"add %x\n", &sum);

    ASSERT(((unsigned long)&sum & 15) == 0);

    sum = _mm_add_epi32(temp, sum);

);

这里是来自 ppl.h 的组合的定义

template<typename _Ty>
class combinable

private:

// Disable warning C4324: structure was padded due to __declspec(align())
// This padding is expected and necessary.
#pragma warning(push)
#pragma warning(disable: 4324)
    __declspec(align(64))
    struct _Node
    
        unsigned long _M_key;
        _Ty _M_value;                   // this might not be aligned on 16 bytes
        _Node* _M_chain;

        _Node(unsigned long _Key, _Ty _InitialValue)
            : _M_key(_Key), _M_value(_InitialValue), _M_chain(NULL)
        
        
    ;

有时对齐没问题,代码工作正常,但大多数时候它不工作

我尝试过使用以下,但这不能编译

union combine 

        unsigned short x[sizeof(__m128i) / sizeof(unsigned int)];
        __m128i y;
;

concurrency::combinable<combine> sum_combine;
then auto &sum = sum_combine.local().y; 

任何纠正对齐问题的建议,仍然使用combinable。

在 x64 上它可以正常工作,因为默认的 16 字节对齐。在 x86 上,有时存在对齐问题。

【问题讨论】:

您是否尝试过在结构定义中使用#pragma pack(push, 1)#pragma pack(pop) 指令?编译器可以用 0 填充你的结构,除非你明确告诉它不要这样做。 @mike on whcih struct 我应该使用#pragma pack(push, 1) 未对齐的负载可以吗?您可能会损失一些性能,尤其是在旧处理器(例如 Core2)上 @harold 但是 input_arr 已经对齐了,它将如何影响总和对齐,是否存在 _mm_addu_epi32 @PaulR 我刚刚使用了 4 而不是 sizeof(uint32_t) 【参考方案1】:

使用未对齐加载刚刚加载总和

auto &sum = sum_combine.local();


#if !defined(_M_X64) 

if (((unsigned long)&sum & 15) != 0)

    // just for breakpoint means, sum  is unaligned.
    int a = 5;

auto sum_temp = _mm_loadu_si128(&sum);
sum = _mm_add_epi32(temp, sum_temp);

#else

sum = _mm_add_epi32(temp, sum);

#endif

【讨论】:

【参考方案2】:

由于与 _mm_add_epi32 一起使用的 sum 变量未对齐,因此您需要使用未对齐的加载/存储 (_mm_loadu_si128/_mm_storeu_si128) 显式加载/存储 sum。变化:

sum = _mm_add_epi32(temp, sum);

到:

__m128i v2 = _mm_loadu_si128((__m128i *)&sum);
v2 = _mm_add_epi32(v2, temp);
_mm_storeu_si128((__m128i *)&sum, v2);

【讨论】:

仍然是相同的行为 50 % 次异常。我使用 _mm_loadu_si128 尝试了__m128i v2。但是如果 sum.sum_combine() 未对齐怎么办 哦,对了 - 这是 second _mm_add_epi32 崩溃了 - 在这种情况下,您需要使用显式未对齐的负载。让我更新我的答案... @Pual R 感谢您的回答和建议。我做的有点不同。现在它的工作 auto &sum = sum_combine.local();自动 sum_temp = _mm_loadu_si128(&sum); if(((unsigned long)&sum & 15) != 0) // 仅用于断点,这是未对齐的。 int a = 5; sum = _mm_add_epi32(temp, sum_temp);

以上是关于PPL Combinable 的 SIMD 对齐问题的主要内容,如果未能解决你的问题,请参考以下文章

aarch64 上未对齐 SIMD 加载/存储的性能

初始化为“float[10][10]”的数组是不是已经为 SIMD/SSE 对齐内存?

为 SIMD 分配内存对齐的缓冲区; |16 如何给出 16 的奇数倍数,为啥要这样做?

使用 SIMD 指令避免无效的内存加载

SIMD和动态内存分配[重复]

基于 SIMD 的算法实现的复杂性比较