簡體   English   中英

什么系列的內在函數將完成這個 paeth 預測代碼?

[英]What series of intrinsics will complete this paeth prediction code?

我有一個在 arrays 上運行的 Paeth 預測 function:

std::array<std::uint8_t,4> birunji::paeth_prediction
    (const std::array<std::uint8_t,4>& a,
     const std::array<std::uint8_t,4>& b,
     const std::array<std::uint8_t,4>& c)
{
    std::array<std::int16_t,4> pa;
    std::array<std::int16_t,4> pb;
    std::array<std::int16_t,4> pc;

    std::array<std::uint8_t,4> results;

    for(std::size_t i = 0; i < 4; ++i)
    {
        pa[i] = b[i] - c[i];
        pb[i] = a[i] - c[i];
        pc[i] = pa[i] + pb[i];

        pa[i] = std::abs(pa[i]);
        pb[i] = std::abs(pb[i]);
        pc[i] = std::abs(pc[i]);

        if(pa[i] <= pb[i] && pa[i] <= pc[i])
            results[i] = a[i];
        else if(pb[i] <= pc[i])
            results[i] = b[i];
        else
            results[i] = c[i];
    }

    return results;
}

我正在嘗試手動使用內在函數來矢量化代碼(用於學習目的)。

__m128i birunji::paeth_prediction(const __m128i& a,
                                  const __m128i& b,
                                  const __m128i& c)
{
    __m128i pa = _mm_sub_epi16(b, c);
    __m128i pb = _mm_sub_epi16(a, c);
    __m128i pc = _mm_add_epi16(pa, pb);
    
    pa = _mm_abs_epi16(pa);
    pb = _mm_abs_epi16(pb);
    pc = _mm_abs_epi16(pc);

    __m128i pa_le_pb = _mm_cmpgt_epi16(pb, pa);
    __m128i pa_le_pc = _mm_cmpgt_epi16(pc, pa);
    __m128i pb_le_pc = _mm_cmpgt_epi16(pc, pb);

    return
    _mm_and_si128(_mm_and_si128(pa_le_pb, pa_le_pc),
                  _mm_and_si128(_mm_and_si128(pb_le_pc,b),a));
}

我遇到的麻煩是條件語句。 我如何成功地矢量化這些? 我不確定我上面的嘗試是否正確。

_mm_cmpgt_epi16可用於比較。 請注意_mm_cmpgt_epi16(a, b) = !(a <= b) ,但是_mm_cmpgt_epi16(b, a) != (a <= b) ,因為它不是大於或等於比較,而是嚴格大於比較。 所以掩碼是反轉的,但這在這種情況下同樣有用,不需要顯式反轉。

這個 function 本身不應該返回條件,它應該根據條件從abc中返回 select。 如果 SSE4.1 可用,則可以使用_mm_blendv_epi8來實現該選擇。 例如(未測試):

__m128i paeth_prediction(__m128i a, __m128i b, __m128i c)
{
    __m128i pa = _mm_sub_epi16(b, c);
    __m128i pb = _mm_sub_epi16(a, c);
    __m128i pc = _mm_add_epi16(pa, pb);
    
    pa = _mm_abs_epi16(pa);
    pb = _mm_abs_epi16(pb);
    pc = _mm_abs_epi16(pc);

    __m128i not_pa_le_pb = _mm_cmpgt_epi16(pa, pb);
    __m128i not_pa_le_pc = _mm_cmpgt_epi16(pa, pc);
    __m128i not_pb_le_pc = _mm_cmpgt_epi16(pb, pc);
    __m128i not_take_a = _mm_or_si128(not_pa_le_pb, not_pa_le_pc);
    __m128i t = _mm_blendv_epi8(b, c, not_pb_le_pc);
    return _mm_blendv_epi8(a, t, not_take_a);
}

最后兩行實現如下邏輯:

如果PB不小於或等於PC,取C,否則取B。
如果 PA 不小於或等於 PBPA 不小於或等於 PC,則取上一步的結果,否則取 A。

如果沒有 SSE4.1,可以使用 AND/ANDNOT/OR 實現混合。

我已經更改了 function 的簽名,因此它按值獲取向量,通過 const 引用獲取它們是不必要的(向量復制起來很簡單)並且可以從間接中增加開銷,盡管如果 function 可能會刪除這樣的開銷最終被編譯器內聯。

作為變體, _mm_min_epi16可用於實現部分邏輯:

__m128i paeth_prediction(__m128i a, __m128i b, __m128i c)
{
    __m128i pa = _mm_sub_epi16(b, c);
    __m128i pb = _mm_sub_epi16(a, c);
    __m128i pc = _mm_add_epi16(pa, pb);
    
    pa = _mm_abs_epi16(pa);
    pb = _mm_abs_epi16(pb);
    pc = _mm_abs_epi16(pc);

    __m128i not_pb_le_pc = _mm_cmpgt_epi16(pb, pc);
    __m128i take_a = _mm_cmpeq_epi16(pa, _mm_min_epi16(pa, _mm_min_epi16(pb, pc)));
    __m128i t = _mm_blendv_epi8(b, c, not_pb_le_pc);
    return _mm_blendv_epi8(t, a, take_a);
}

因為條件pa <= pb && pa <= pc等價於pa == min(pa, pb, pc)

生成的匯編代碼看起來要好一些,但我沒有以任何方式對其進行測試,包括性能。

您可以通過完全避免任何轉換為int16_t來簡化計算。 首先,請注意pa<=pcpb<=pc當且僅當a<=c<=bb<=c<=a時為真。 如果c小於或等於兩者,則返回max(a,b) 如果c大於或等於,則返回min(a,b)

所以我們可以首先使用minmax操作“排序” ab

A = min(a,b)
B = max(a,b)

這留下了三種可能的情況:

A<=B<=c  --> A
c<=A<=B  --> B
A< c< B  --> c

這意味着在 C++ 代碼中

std::array<std::uint8_t,4> birunji::paeth_prediction
    (const std::array<std::uint8_t,4>& a,
     const std::array<std::uint8_t,4>& b,
     const std::array<std::uint8_t,4>& c)
{
    std::array<std::uint8_t,4> results;

    for(std::size_t i = 0; i < 4; ++i)
    {
        uint8_t A = std::min(a[i],b[i]);
        uint8_t B = std::max(a[i],b[i]);
        if     (B<=c[i]) results[i] = A;
        else if(c[i]<=A) results[i] = B;
        else             results[i] = c[i];
    }

    return results;
}

不幸的是,沒有無符號 SIMD 比較(在 AVX-512 之前),但我們可以使用(x<=y) == (max(x,y)==y)來模擬它(或者進行飽和減法並與零比較.

可能的(未經測試的)SIMD 實現(這也適用於任意多個元素——但您可以只加載最低 32 位中的四個元素並忽略結果的 rest):

__m128i paeth_prediction(__m128i a, __m128i b, __m128i c)
{
    __m128i A = _mm_min_epu8(a, b);
    __m128i B = _mm_max_epu8(a, b);

    __m128i A_greater_equal_c = _mm_cmpeq_epi8(_mm_max_epu8(A, c), A);
    __m128i B_less_equal_c    = _mm_cmpeq_epi8(_mm_min_epu8(B, c), B);

    // if you don't have SSE 4.1, this can be done using bitwise and/or operations:
    __m128i t = _mm_blendv_epi8(b, c, A_greater_equal_c);
    return _mm_blendv_epi8(a, t, B_less_equal_c);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM