MCPcopy Create free account
hub / github.com/ashvardanian/StringZilla / sz_move_skylake

Function sz_move_skylake

include/stringzilla/memory.h:872–975  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

870}
871
872SZ_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.

Callers 1

sz_moveFunction · 0.85

Calls 2

sz_u64_mask_until_Function · 0.85
sz_copy_skylakeFunction · 0.85

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…