| 870 | } |
| 871 | |
| 872 | SZ_PUBLIC void sz_move_skylake(sz_ptr_t target, sz_cptr_t source, sz_size_t length) { |
| 873 | if (target == source) return; // Don't be silly, don't move the data if it's already there. |
| 874 | |
| 875 | // On very short buffers, that are one cache line in width or less, we don't need any loops. |
| 876 | // We can also avoid any data-dependencies between iterations, assuming we have 32 registers |
| 877 | // to pre-load the data, before writing it back. |
| 878 | if (length <= 64) { |
| 879 | __mmask64 mask = sz_u64_mask_until_(length); |
| 880 | _mm512_mask_storeu_epi8(target, mask, _mm512_maskz_loadu_epi8(mask, source)); |
| 881 | } |
| 882 | else if (length <= 128) { |
| 883 | sz_size_t last_length = length - 64; |
| 884 | __mmask64 mask = sz_u64_mask_until_(last_length); |
| 885 | __m512i source0 = _mm512_loadu_epi8(source); |
| 886 | __m512i source1 = _mm512_maskz_loadu_epi8(mask, source + 64); |
| 887 | _mm512_storeu_epi8(target, source0); |
| 888 | _mm512_mask_storeu_epi8(target + 64, mask, source1); |
| 889 | } |
| 890 | else if (length <= 192) { |
| 891 | sz_size_t last_length = length - 128; |
| 892 | __mmask64 mask = sz_u64_mask_until_(last_length); |
| 893 | __m512i source0 = _mm512_loadu_epi8(source); |
| 894 | __m512i source1 = _mm512_loadu_epi8(source + 64); |
| 895 | __m512i source2 = _mm512_maskz_loadu_epi8(mask, source + 128); |
| 896 | _mm512_storeu_epi8(target, source0); |
| 897 | _mm512_storeu_epi8(target + 64, source1); |
| 898 | _mm512_mask_storeu_epi8(target + 128, mask, source2); |
| 899 | } |
| 900 | else if (length <= 256) { |
| 901 | sz_size_t last_length = length - 192; |
| 902 | __mmask64 mask = sz_u64_mask_until_(last_length); |
| 903 | __m512i source0 = _mm512_loadu_epi8(source); |
| 904 | __m512i source1 = _mm512_loadu_epi8(source + 64); |
| 905 | __m512i source2 = _mm512_loadu_epi8(source + 128); |
| 906 | __m512i source3 = _mm512_maskz_loadu_epi8(mask, source + 192); |
| 907 | _mm512_storeu_epi8(target, source0); |
| 908 | _mm512_storeu_epi8(target + 64, source1); |
| 909 | _mm512_storeu_epi8(target + 128, source2); |
| 910 | _mm512_mask_storeu_epi8(target + 192, mask, source3); |
| 911 | } |
| 912 | |
| 913 | // If the regions don't overlap at all, just use "copy" and save some brain cells thinking about corner cases. |
| 914 | else if (target + length < source || target >= source + length) { sz_copy_skylake(target, source, length); } |
| 915 | |
| 916 | // When the buffer is over 64 bytes, it's guaranteed to touch at least two cache lines - the head and tail, |
| 917 | // and may include more cache-lines in-between. Knowing this, we can avoid expensive unaligned stores |
| 918 | // by computing 2 masks - for the head and tail, using masked stores for the head and tail, and unmasked |
| 919 | // for the body. |
| 920 | else { |
| 921 | sz_size_t head_length = (64 - ((sz_size_t)target % 64)) % 64; // 63 or less. |
| 922 | sz_size_t tail_length = (sz_size_t)(target + length) % 64; // 63 or less. |
| 923 | sz_size_t body_length = length - head_length - tail_length; // Multiple of 64. |
| 924 | __mmask64 head_mask = sz_u64_mask_until_(head_length); |
| 925 | __mmask64 tail_mask = sz_u64_mask_until_(tail_length); |
| 926 | |
| 927 | // The absolute most common case of using "moves" is shifting the data within a continuous buffer |
| 928 | // when adding a removing some values in it. In such cases, a typical shift is by 1, 2, 4, 8, 16, |
| 929 | // or 32 bytes, rarely larger. For small shifts, under the size of the ZMM register, we can use shuffles. |
no test coverage detected
searching dependent graphs…