我遇到了带有以下“内核”作为性能阻止程序的代码集。由于我可以使用最新的英特尔®至强融核™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);
这里有几个问题:
由于_mm_srli_si128不接受非立即参数,因此我必须在其中使用循环,请问有什么方法可以消除它?
_mm_testc_si128(pixel16,mask1)通常对性能没有帮助,这当然是由于data [] []的分布所致;但是,它“计算a的按位NOT,然后与b进行AND运算,如果结果为零,则将CF设置为1,否则将CF设置为0”,有什么方法可以获取“ ANDNOT”的结果,以便我这样做不需要再次计算_mm_andnot_si128吗?
由于内部循环长度通常小于16,因此可能不适用于AVX512;但是,通过加载data [y] [x]和data [y + 1] [x]然后将它们组合为一个__m256i,是否值得将y间隔展开2?但是,由于KNL(AVX512BW)尚无法将8bit int转换为16bit int,因此它可能比当前版本更令人沮丧。
总的来说,在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数组,您将需要#rows和#column以及要检查的行和列的开始和结束位置。我确定您知道在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
除了所有这些,我认为您的数据[y] [x]&0x1应该能够始终使用某些#pragma向量或#pragma simd(等等)... -vec-report 3应该允许您进行计算。
如果没有,那么内联的小部分可能是最好的??
我不知道您需要什么,但是在第一个示例中,我在单个内核上获得了超过250 MB /秒的位吞吐量……所以您知道会发生什么。
我非常相信,最好的方法就是对数据进行直方图绘制。然后,对每个直方图索引值进行位测试,然后乘以该bin处的直方图bin计数。当然,对于较大的计数值,它更快。一旦知道了每个直方图索引的位模式,该部分就永远不会改变。因此,对于较大的“加总计数”和较小的“字节大小”,无疑会更快。对于小计数大小和64位或更大的计数,使用IBITS可能会更快。
从9月16日左右开始在英特尔网络研讨会上覆盖了直方图(c和fortran)。
fortran的一个优点是,对于单个字节,可以将直方图的尺寸标注为(-128:128),这使得将值直接放入正确的bin中变得容易。
关于c - AVX512关于位测试和操作的性能建议,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40371478/