gpt4 book ai didi

c - AVX512 位测试和操作性能建议

转载 作者:行者123 更新时间:2023-11-30 16:55:11 31 4
gpt4 key购买 nike

我遇到了一组使用以下“内核”作为性能阻止程序的代码。由于我可以使用最新的 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 的按位 NOT,然后与 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)尚不支持 8 位 int 到 16 位 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)));
}

最佳答案

这是一个线性版本,它返回标准化位计数(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 数组,您将需要 # 行和 # 列,以及要检查的行和列的开头和结尾所在的位置。我相信您知道 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 vector always 或 #pragma simd (等)一起使用... -vec-report 3 应该允许您使用它出来。

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

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

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

9 月 16 日左右的英特尔网络研讨会上介绍了直方图(c 和 fortran)。

Fortran 的一个优点是,对于单个字节,可以将直方图的尺寸设置为 (-128:128),这使得可以直接将值放入正确的容器中。

关于c - AVX512 位测试和操作性能建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40371478/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com