0

我遇到了一组带有以下“内核”作为性能阻止程序的代码。由于我可以访问最新的 Intel(R) Xeon Phi(TM) CPU 7210 (KNL),因此我希望使用 AVX512 内在函数加速它。

for( int y = starty; y <= endy; y++)
{
    // hence data[][] is "unsigned char" while result[] is "int"
    for( int x = startx; x <= endx; x++)
    {
        if( (data[y][x]&0x1) == 0 )
            result[x] += data[y][x];
    }
}

在分析了代码的行为后,我发现内循环的长度大多小于 16,所以我写了以下内容

register int xlen = xend - xstart + 1;

__m512i zero5 = _mm512_setzero_si512();
__m256i zero2 = _mm512_castsi512_si256(zero5);
__m128i zero1 = _mm512_castsi512_si128(zero5);
__m256i mask2 = _mm256_set1_epi8(0x1);
__m128i mask1 = _mm256_castsi256_si128(mask2);

register __m512i psprof0 = zero5;

for( int i = 0; i < (16-xlen)&(~0x1); i += 2 ) mask1 = _mm_srli_si128(mask1, 2);
if( (16-xlen)&(0x1) ) mask1 = _mm_srli_si128(mask1, 1);

#pragma vector nontemporal
#pragma prefetch data
for( int y = starty; y <= endy; y++ )
{
    __m128i pixel16  = _mm_loadu_si128((__m128i*)&data[y][startx]);

    // if ( _mm_testc_si128(pixel16, mask1) ) continue;

    __m128i mask16    = _mm_andnot_si128(pixel16, mask1);
    __m128i pixel16n  = _mm_sign_epi8(pixel16, mask16);
            psprof0   = _mm512_add_epi32(psprof0, _mm512_cvtepu8_epi32(pixel16n));
}

_mm512_storeu_si512(&result[startx], psprof0);  

这里有几个问题:

  1. 由于_mm_srli_si128不接受非立即参数,我必须在那里使用循环,请问有什么办法可以消除它?
  2. _mm_testc_si128(pixel16, mask1)大多对性能没有帮助,这当然是由于data[][]的分布;但是,它“计算 a 的按位非,然后与 b 进行 AND,如果结果为零,则将 CF 设置为 1,否则将 CF 设置为 0”,有什么方法可以得到“ANDNOT”的结果,以便我这样做不需要再计算_mm_andnot_si128
  3. 由于内环长度大多小于 16,它可能不太适合 AVX512;但是,通过加载 data[y][x] 和 data[y+1][x],然后将它们组合成一个 __m256i,将 y 间隔展开 2 是否值得?但是,由于 KNL (AVX512BW) 上还没有 8bit int 到 16bit int 的转换,它可能比当前版本更令人沮丧。
  4. 一般来说,任何提高 KNL 上这一小段代码性能的建议/建议都非常感谢 :) (它已经在 OpenMP 循环区域内,因此现在可能不可用)

上述第 3 点:

static inline __m256i vec_256_combine_128(__m128i a, __m128i b)
{
// combine two __m128i into one __m256i
return _mm256_insertf128_si256(_mm256_castsi128_si256(a), b, 1);
}

static inline __m128i vec_256_add_128(__m256i a)
{
// add lower 128bit and higher 128bit of __m256i consists of epi16
return _mm_add_epi16(_mm256_castsi256_si128(a), _mm256_extracti128_si256(a, 1));
} 

for( int y = starty; y <= endy; y += 2 )
{
    __m128i pixel16a  = _mm_load_si128((__m128i*)&pEdgeImage[y][sx]);
    __m128i pixel16b  = _mm_load_si128((__m128i*)&pEdgeImage[y+1][sx]);
    if ( y == ye ) 
        pixel16b  = zero1;  

    __m256i pixel16   = vec_256_combine_128(pixel16a, pixel16b);

    if ( _mm256_testc_si256(pixel16, mask1) ) continue;

    __m256i mask16    = _mm256_andnot_si256(pixel16, mask1);
    __m256i pixel16n  = _mm256_sign_epi8(pixel16, mask16);

    __m256i pixel16lo = _mm256_unpacklo_epi8(pixel16n, zero2);
    __m256i pixel16hi = _mm256_unpackhi_epi8(pixel16n, zero2);

            psprof0   = _mm256_add_epi16(psprof0, vec_256_combine_128(vec_256_add_128(pixel16lo), vec_256_add_128(pixel16hi)));
}
4

1 回答 1

0

这是一个线性版本,它返回归一化的位计数(8 个浮点数),其中它通过条目数进行归一化。(用手戳,很可能是一两个错字)

PURE FUNCTION BITS8(nGot, DataIn, nBits) BIND(C, NAME='BITS8')
USE OMP_LIB
USE, INTRINSIC :: IOS_C_BINDING, ONLY: C_FLOAT, C_INT8_Y, C_INT
IMPLICIT NONE
INTEGER(C_INT)                    , INTENT(IN) :: nGot
INTEGER(C_INT8_T)                 , INTENT(IN) :: nBits !Which should come in as 8
INTEGER(C_INT8_T), DIMENSION(nGot), INTENT(IN) :: DataIn
!DIR$ ATTRIBUTES ALIGN : 64                    :: Bits
REAL(C_FLOAT), DIMENSION(nBits)                :: Bits8

Bits8 = 0.0E0

!DIR$ ASSUME_ALIGNED DataIn:2
!DIR$ PREFETCH       DataIn:1:64
Sum_Loop: DO I = 1, nGot
!$OMP SIMD REDUCTION(+:Bits8) LINEAR(DataIn) SAFELEN(64)
  Bit_Loop: DO J = 0, nBits-1
    Bits8(J+1) = IBITS(DataIn(I),J, 1) + Bits8(J+1)
  ENDDO Bit_Loop
ENDDO Sum_Loop
!$OMP END

!DIR$ SIMD
Norm_Loop: DO J = 1, nBits
  Bits8(J) = Bits8(J)/nGot
ENDDO Norm_Loop

RETURN
END FUNCTION Bits8

你用'ifort -openmp -O3'等编译它。显然,对于一个2数组,你需要#rows和#columns,以及你想要检查的行和列的开始和结束位置。我相信您知道在 c 与 fortran 中行和列是相反的。

要计算出结尾的下划线戏剧,请在 .o 文件上使用“nm”,并且 BIND(C, NAME=) 也可以提供帮助。

可能你可以使用更精简的东西,然后在你的 C 中内联函数,并将 SIMD REDUCTION 放在 C 端。如果您在“c 侧”处理数组,则您的优势在于无需担心行/列差异。

PURE FUNCTION BITS8(DataIn) BIND(C, NAME='BITS8')
USE OMP_LIB
USE, INTRINSIC :: IOS_C_BINDING, ONLY: C_INT8_T, C_INT
IMPLICIT NONE
INTEGER(C_INT8_T), PARAMETER      :: nBits = 8
INTEGER(C_INT8_T)    , INTENT(IN) :: DataIn
INTEGER(C_INT), DIMENSION(nBits)  :: Bits8

! Bits = 0.0E0

!DIR$ ASSUME_ALIGNED DataIn:2
!DIR$ PREFETCH       DataIn:1:64
Bit_Loop: DO J = 0, nBits-1
  Bits8(J+1) = IBITS(DataIn(I),J, 1)
ENDDO Bit_Loop
!$OMP END

RETURN
END FUNCTION Bits8

另一种方法是:

PURE FUNCTION BITS8(nrows, ncols, startrow, startCol, EndRow, EndCol, DataIn) BIND(C, NAME='BITS8')
USE OMP_LIB
USE, INTRINSIC :: IOS_C_BINDING, ONLY: C_FLOAT, C_INT8_Y, C_INT
IMPLICIT NONE
INTEGER(C_INT8_T)                  , PARAMETER        :: nBits = 8
INTEGER(C_INT)                           , INTENT(IN) :: nRows
INTEGER(C_INT)                           , INTENT(IN) :: nCols
INTEGER(C_INT)                           , INTENT(IN) :: StartRow
INTEGER(C_INT)                           , INTENT(IN) :: StartCol
INTEGER(C_INT)                           , INTENT(IN) :: EndRow
INTEGER(C_INT)                           , INTENT(IN) :: EndCol
INTEGER(C_INT8_T), DIMENSION(ncols,nrows), INTENT(IN) :: DataIn
!DIR$ ATTRIBUTES ALIGN : 64                           :: Bits8
INTEGER(C_INT), DIMENSION(nBits)                      :: Bits8
INTEGER(C_INT)                                        :: I, J, K

!DIR$ ASSUME_ALIGNED DataIn:64
Bits8 = 0

Row_Loop: DO J = StartCol, EndCol
!DIR$ PREFETCH       DataIn:1:64
  Col_Loop: DO I = StartRow, EndRow
    !$OMP SIMD REDUCTION(+:Bits8) LINEAR(DataIn) SAFELEN(64)
    Bit_Loop: DO K = 0, nBits-1
      Bits8(K+1) = IBITS(DataIn(I,J),K, 1) + Bits8(K+1)
    ENDDO Bit_Loop
  ENDDO Sum_Loop
ENDDO Sum_Loop
!$OMP END

RETURN
END FUNCTION Bits8

除此之外,我认为您的 data[y][x]&0x1 应该能够始终使用一些 #pragma 矢量或 #pragma simd (等)... -vec-report 3 应该可以让您解决。

如果没有,那么内联的小部分可能是最好的?

我不知道您需要什么,但在第一个示例中,我在单核上获得了 >250 MB/秒的位吞吐量……所以您知道会发生什么。

我非常相信最好的方法就是对数据进行直方图。然后对每个直方图索引值进行位测试,并乘以该 bin 处的直方图 bin 计数。当然,对于较大的计数值,它会更快。一旦您知道每个直方图索引的位模式,该部分就永远不会改变。因此,对于较大的“求和计数”和较小的“字节大小”,这肯定会更快。对于较小的计数大小和 64 位或更大,那么使用 IBITS 可能会更快。

从 9 月 16 日左右(c 和 fortran)开始,英特尔网络研讨会上涵盖了直方图。

fortran 的一个优点是,对于单个字节,可以将直方图的维度设置为 (-128:128),这样可以直接将值放入正确的 bin 中。

于 2016-11-16T14:25:49.623 回答