In some new CUDA architectures, does an SM unit use a dedicated prefix-sum (sum-scan) hardware for computing atomicAdd(s_sameAddress, 1) quicker than a warp-primitive based reduction/scan algorithm? Even when all warps of block target the same shared memory address?
I’ve not heard of any such hardware. There are whitepapers published for many chip architectures, did you read about it somewhere?
There is a compiler optimization that can identify warp-wide atomic activity, and aggregate it using warp shuffle into an atomic performed by a single thread, thus reducing “atomic pressure”. You can read about the basics here
I was experimenting warp-primitive based implementation versus atomicAdd based implementation for reduction and atomicAdd version was a bit faster even though I had optimized warp-primitive version. I don’t remember exact algorithm, there was warp-level-aggregation but not thread-level-aggregation.
Adding some detail about comparison, in histogram algorithm:
#pragma unroll 4
for (int j = 0; j < 4; j++) {
uint32_t k = ka[j];
uint32_t mask = __match_any_sync(0xFFFFFFFF, k);
const int count = __popc(mask);
const int leaderLane = __ffs(mask) - 1;
if (warpLane == leaderLane) {
atomicAdd(&s_histogram[k], count);
}
}
this is 30% slower than this:
#pragma unroll 4
for (int j = 0; j < 4; j++) {
uint32_t k = ka[j];
atomicAdd(&s_histogram[k], 1);
}
when all inputs are duplicates.
Despite having many times more atomicAdds, its faster.
studying the sass may yield insight. First of all the compiler may already be doing warp-aggregation in the 2nd case. Second, it may have found a simpler instruction sequence to aggregate the count across the warp.
It produced this part:
/*02f0*/ @P3 BRA 0x390 ; /* 0x0000009000003947 */
/* 0x000fea0003800000 */
/*0300*/ LDG.E.128.CONSTANT R4, [R2.64+0x280000] ; /* 0x2800000402047981 */
/* 0x002ea4000c1e9d00 */
/*0310*/ F2I.U32.TRUNC.NTZ R4, R4 ; /* 0x0000000400047305 */
/* 0x004e70000020f000 */
/*0320*/ F2I.U32.TRUNC.NTZ R5, R5 ; /* 0x0000000500057305 */
/* 0x000eb0000020f000 */
/*0330*/ F2I.U32.TRUNC.NTZ R6, R6 ; /* 0x0000000600067305 */
/* 0x000ee2000020f000 */
/*0340*/ ATOMS.POPC.INC.32 RZ, [R4.X4+URZ] ; /* 0x0000000004ff7f8c */
/* 0x0023ee000d00403f */
/*0350*/ F2I.U32.TRUNC.NTZ R7, R7 ; /* 0x0000000700077305 */
/* 0x000f22000020f000 */
/*0360*/ ATOMS.POPC.INC.32 RZ, [R5.X4+URZ] ; /* 0x0000000005ff7f8c */
/* 0x0043e8000d00403f */
/*0370*/ ATOMS.POPC.INC.32 RZ, [R6.X4+URZ] ; /* 0x0000000006ff7f8c */
/* 0x0083e8000d00403f */
/*0380*/ ATOMS.POPC.INC.32 RZ, [R7.X4+URZ] ; /* 0x0000000007ff7f8c */
/* 0x0103e4000d00403f */
/*0390*/ BSYNC B0 ; /* 0x0000000000007941 */
ATOMS.POPC.INC instruction looks like atomic increment but also about population count. Maybe a fused instruction like FMA?
You could try replacing the 1 with another number to prevent .INC variants. Just for testing.