add optimized implementation for AVX512 targets#347
Conversation
Those benchmarks are made with Clang, which likes to impersonate Notably, FreeBSD, macOS, the Android NDK, and Termux all link Clang is not affected by this, only GCC. |
|
the previous benchmarks are compiled via For the |
|
The benchmark results compiled by gcc is updated. It is wired that custom secret will significantly affects performance, the clang-compiled avx512 |
|
Clang doesn't appear to even try to unroll the I wonder if we can get it to unroll and jam the loop. |
|
gcc also has the similar performance on the |
|
@easyaspi314 the main factor seems to be the runtime XXH_NO_INLINE XXH64_hash_t
XXH3_hashLong_64b_withSecret(const xxh_u8* XXH_RESTRICT input, size_t len,
const xxh_u8* XXH_RESTRICT secret, size_t secretSize)
{
switch (secretSize) {
case XXH3_SECRET_SIZE_MIN + 0 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 0 * 8);
case XXH3_SECRET_SIZE_MIN + 1 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 1 * 8);
case XXH3_SECRET_SIZE_MIN + 2 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 2 * 8);
case XXH3_SECRET_SIZE_MIN + 3 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 3 * 8);
case XXH3_SECRET_SIZE_MIN + 4 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 4 * 8);
case XXH3_SECRET_SIZE_MIN + 5 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 5 * 8);
case XXH3_SECRET_SIZE_MIN + 6 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 6 * 8);
case XXH3_SECRET_SIZE_MIN + 7 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 7 * 8);
case XXH3_SECRET_SIZE_MIN + 8 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 8 * 8);
case XXH3_SECRET_SIZE_MIN + 9 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN + 9 * 8);
case XXH3_SECRET_SIZE_MIN +10 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +10 * 8);
case XXH3_SECRET_SIZE_MIN +11 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +11 * 8);
case XXH3_SECRET_SIZE_MIN +12 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +12 * 8);
case XXH3_SECRET_SIZE_MIN +13 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +13 * 8);
case XXH3_SECRET_SIZE_MIN +14 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +14 * 8);
case XXH3_SECRET_SIZE_MIN +15 * 8: return XXH3_hashLong_64b_internal(input, len, secret, XXH3_SECRET_SIZE_MIN +15 * 8);
}
return XXH3_hashLong_64b_internal(input, len, secret, secretSize);
}This improves scalar, sse2, avx2 and avx512. The result for avx512: ./xxhsum 0.7.4 (64-bit x86_64 + AVX512 little endian), Clang 9.0.0 (tags/RELEASE_900/final), by Yann Collet
Sample of 100 KB...
XXH3_64b : 102400 -> 565861 it/s (55259.8 MB/s)
XXH3_64b unaligned : 102400 -> 565829 it/s (55256.8 MB/s)
XXH3_64b w/seed : 102400 -> 561965 it/s (54879.4 MB/s)
XXH3_64b w/seed unaligned : 102400 -> 561832 it/s (54866.4 MB/s)
XXH3_64b w/secret : 102400 -> 502027 it/s (49026.0 MB/s)
XXH3_64b w/secret unaligned : 102400 -> 502387 it/s (49061.3 MB/s)
XXH128 : 102400 -> 507926 it/s (49602.2 MB/s)
XXH128 unaligned : 102400 -> 507856 it/s (49595.3 MB/s)
XXH128 w/seed : 102400 -> 505029 it/s (49319.2 MB/s)
XXH128 w/seed unaligned : 102400 -> 504918 it/s (49308.4 MB/s)
XXH128 w/secret : 102400 -> 452006 it/s (44141.2 MB/s)
XXH128 w/secret unaligned : 102400 -> 451734 it/s (44114.7 MB/s) |
|
What about this? I don't have access to a CPU with AVX512, so I can't test. #if XXH_VECTOR == XXH_AVX512 && !XXH_REROLL
if (XXH_likely(nbStripes % 4 == 0)) {
for (n = 0; n < nbStripes; n+=4 ) {
const xxh_u8* const in = input + n*STRIPE_LEN;
if (accWidth == XXH3_acc_64bits) XXH_PREFETCH(in + XXH_PREFETCH_DIST_AVX512_64);
else XXH_PREFETCH(in + XXH_PREFETCH_DIST_AVX512_128);
XXH3_accumulate_512(acc,
in,
secret + n*XXH_SECRET_CONSUME_RATE,
accWidth);
XXH3_accumulate_512(acc,
in + 1 * STRIPE_LEN,
secret + (n+1)*XXH_SECRET_CONSUME_RATE,
accWidth);
XXH3_accumulate_512(acc,
in + 2 * STRIPE_LEN,
secret + (n+2)*XXH_SECRET_CONSUME_RATE,
accWidth);
XXH3_accumulate_512(acc,
in + 3 * STRIPE_LEN,
secret + (n+3)*XXH_SECRET_CONSUME_RATE,
accWidth);
}
} else
#endif
for (n = 0; n < nbStripes; n++) {
... |
|
Thanks for this great submission @gzm55 ! I understand it noticeably improves speed when using the default I also understand that optimizing the Before merge, I merely have a small question on the new prefetch distance. |
Was it tested with |
|
@Cyan4973 agree to separate the the distance of prefetching is got from profiling each one byte for 100K length input (xxhsum default). The benchmark machine is a ECS server, not a desktop machine. Do you have some experience or environments to settle down a better distance? |
There will never be a "one size fits all" best prefetch distance for all systems. Some generic rules of thumb to establish a distance :
|
|
@Cyan4973 the prefetch distances are updated to 64 aligned for clang and gcc. When do benchmark, start offset % 64 == 16. The results: Clang./xxhsum 0.7.4 (64-bit x86_64 + AVX512 little endian), Clang 9.0.0 (tags/RELEASE_900/final), by Yann Collet
Sample of 100 KB...
XXH3_64b : 102400 -> 564617 it/s (55138.4 MB/s)
XXH3_64b unaligned : 102400 -> 564709 it/s (55147.3 MB/s)
XXH3_64b w/seed : 102400 -> 561776 it/s (54860.9 MB/s)
XXH3_64b w/seed unaligned : 102400 -> 561770 it/s (54860.4 MB/s)
XXH3_64b w/secret : 102400 -> 332788 it/s (32498.8 MB/s)
XXH3_64b w/secret unaligned : 102400 -> 332700 it/s (32490.2 MB/s)
XXH128 : 102400 -> 508311 it/s (49639.7 MB/s)
XXH128 unaligned : 102400 -> 508230 it/s (49631.9 MB/s)
XXH128 w/seed : 102400 -> 505657 it/s (49380.5 MB/s)
XXH128 w/seed unaligned : 102400 -> 505754 it/s (49390.1 MB/s)
XXH128 w/secret : 102400 -> 346498 it/s (33837.7 MB/s)
XXH128 w/secret unaligned : 102400 -> 346553 it/s (33843.1 MB/s)Gcc./xxhsum 0.7.4 (64-bit x86_64 + AVX512 little endian), GCC 9.2.1 20191102, by Yann Collet
Sample of 100 KB...
XXH3_64b : 102400 -> 528095 it/s (51571.8 MB/s)
XXH3_64b unaligned : 102400 -> 528163 it/s (51578.4 MB/s)
XXH3_64b w/seed : 102400 -> 528725 it/s (51633.3 MB/s)
XXH3_64b w/seed unaligned : 102400 -> 528844 it/s (51645.0 MB/s)
XXH3_64b w/secret : 102400 -> 341283 it/s (33328.4 MB/s)
XXH3_64b w/secret unaligned : 102400 -> 341335 it/s (33333.5 MB/s)
XXH128 : 102400 -> 472524 it/s (46144.9 MB/s)
XXH128 unaligned : 102400 -> 472535 it/s (46146.0 MB/s)
XXH128 w/seed : 102400 -> 465276 it/s (45437.1 MB/s)
XXH128 w/seed unaligned : 102400 -> 465038 it/s (45413.8 MB/s)
XXH128 w/secret : 102400 -> 404590 it/s (39510.7 MB/s)
XXH128 w/secret unaligned : 102400 -> 404518 it/s (39503.7 MB/s) |
|
After some testing, I don't think prefetching on aarch64 is worth it. It ends up being slower. It also crashes the (albeit experimental) AVR and MSP430 Clang backends. Perhaps we might want to only enable it on x86. |
This is a surprising outcome, though one certainly worth investigating. I don't know how Anyway, selecting different prefetching policy depending on target hardware is certainly within the scope of this project. |
While searching the prefetch dist for avx512, I think we should define a matrix over (target, compiler, accWidth) for the dist values or disable prefetch. |
on avx512 targets, variant with default secrets will improve 45%. The following result is compiled with
-Ofast -mavx512f, and there is also no need to add-mno-avx256-split-unaligned-loadwith gcc.compiling option:
-Ofast -mavx512f-Ofast -mavx2 -mno-avx256-split-unaligned-load-Ofast -mavx2compiled with Clang 9
compiled with GCC 9