| 430 | } |
| 431 | |
| 432 | SZ_PUBLIC void sz_copy_haswell(sz_ptr_t target, sz_cptr_t source, sz_size_t length) { |
| 433 | // The naive implementation of this function is very simple. |
| 434 | // It assumes the CPU is great at handling unaligned "stores" and "loads". |
| 435 | // |
| 436 | // for (; length >= 32; target += 32, source += 32, length -= 32) |
| 437 | // _mm256_storeu_si256((__m256i *)target, _mm256_lddqu_si256((__m256i const *)source)); |
| 438 | // sz_copy_serial(target, source, length); |
| 439 | // |
| 440 | // A typical AWS Skylake instance can have 32 KB x 2 blocks of L1 data cache per core, |
| 441 | // 1 MB x 2 blocks of L2 cache per core, and one shared L3 cache buffer. |
| 442 | // For now, let's avoid the cases beyond the L2 size. |
| 443 | int is_huge = length > 1ull * 1024ull * 1024ull; |
| 444 | if (length < 8) { |
| 445 | while (length--) *(target++) = *(source++); |
| 446 | } |
| 447 | // The next few sections are identical here and in the `sz_move_haswell` function. |
| 448 | // We can use 2x 64-bit interleaving loads for each string, and then compare them for equality. |
| 449 | // The same approach is used in GLibC and was suggest by Denis Yaroshevskiy. |
| 450 | // https://codebrowser.dev/glibc/glibc/sysdeps/x86_64/multiarch/memcmp-avx2-movbe.S.html#518 |
| 451 | // It shouldn't improve performance on microbenchmarks, but should be better in practice. |
| 452 | else if (length <= 16) { |
| 453 | sz_u64_t source_first_word = *(sz_u64_t const *)(source); |
| 454 | sz_u64_t source_second_word = *(sz_u64_t const *)(source + length - 8); |
| 455 | sz_u64_t *target_first_word_ptr = (sz_u64_t *)(target); |
| 456 | sz_u64_t *target_second_word_ptr = (sz_u64_t *)(target + length - 8); |
| 457 | *target_first_word_ptr = source_first_word; |
| 458 | *target_second_word_ptr = source_second_word; |
| 459 | } |
| 460 | // We can use 2x 128-bit interleaving loads for each string, and then compare them for equality. |
| 461 | else if (length <= 32) { |
| 462 | sz_u128_vec_t source_first_vec, source_second_vec; |
| 463 | sz_u128_vec_t *target_first_word_ptr, *target_second_word_ptr; |
| 464 | source_first_vec.xmm = _mm_lddqu_si128((__m128i const *)(source)); |
| 465 | source_second_vec.xmm = _mm_lddqu_si128((__m128i const *)(source + length - 16)); |
| 466 | target_first_word_ptr = (sz_u128_vec_t *)(target); |
| 467 | target_second_word_ptr = (sz_u128_vec_t *)(target + length - 16); |
| 468 | _mm_storeu_si128(&target_first_word_ptr->xmm, source_first_vec.xmm); |
| 469 | _mm_storeu_si128(&target_second_word_ptr->xmm, source_second_vec.xmm); |
| 470 | } |
| 471 | // We can use 2x 256-bit interleaving loads for each string, and then compare them for equality. |
| 472 | else if (length <= 64) { |
| 473 | sz_u256_vec_t source_first_vec, source_second_vec; |
| 474 | sz_u256_vec_t *target_first_word_ptr, *target_second_word_ptr; |
| 475 | source_first_vec.ymm = _mm256_lddqu_si256((__m256i const *)(source)); |
| 476 | source_second_vec.ymm = _mm256_lddqu_si256((__m256i const *)(source + length - 32)); |
| 477 | target_first_word_ptr = (sz_u256_vec_t *)(target); |
| 478 | target_second_word_ptr = (sz_u256_vec_t *)(target + length - 32); |
| 479 | _mm256_storeu_si256(&target_first_word_ptr->ymm, source_first_vec.ymm); |
| 480 | _mm256_storeu_si256(&target_second_word_ptr->ymm, source_second_vec.ymm); |
| 481 | } |
| 482 | // When dealing with larger arrays, the optimization is not as simple as with the `sz_fill_haswell` function, |
| 483 | // as both buffers may be unaligned. If we are lucky and the requested operation is some huge page transfer, |
| 484 | // we can use aligned loads and stores, and the performance will be great. |
| 485 | else if ((sz_size_t)target % 32 == 0 && (sz_size_t)source % 32 == 0 && !is_huge) { |
| 486 | for (; length >= 32; target += 32, source += 32, length -= 32) |
| 487 | _mm256_store_si256((__m256i *)target, _mm256_load_si256((__m256i const *)source)); |
| 488 | if (length) sz_copy_serial(target, source, length); |
| 489 | } |
no test coverage detected
searching dependent graphs…