| 253 | } |
| 254 | |
| 255 | SZ_PUBLIC sz_bool_t sz_equal_haswell(sz_cptr_t a, sz_cptr_t b, sz_size_t length) { |
| 256 | |
| 257 | if (length < 8) { |
| 258 | sz_cptr_t const a_end = a + length; |
| 259 | while (a != a_end && *a == *b) a++, b++; |
| 260 | return (sz_bool_t)(a_end == a); |
| 261 | } |
| 262 | // We can use 2x 64-bit interleaving loads for each string, and then compare them for equality. |
| 263 | // The same approach is used in GLibC and was suggest by Denis Yaroshevskiy. |
| 264 | // https://codebrowser.dev/glibc/glibc/sysdeps/x86_64/multiarch/memcmp-avx2-movbe.S.html#518 |
| 265 | // It shouldn't improve performance on microbenchmarks, but should be better in practice. |
| 266 | else if (length <= 16) { |
| 267 | sz_u64_t a_first_word = sz_u64_load(a).u64; |
| 268 | sz_u64_t b_first_word = sz_u64_load(b).u64; |
| 269 | sz_u64_t a_second_word = sz_u64_load(a + length - 8).u64; |
| 270 | sz_u64_t b_second_word = sz_u64_load(b + length - 8).u64; |
| 271 | return (sz_bool_t)((a_first_word == b_first_word) & (a_second_word == b_second_word)); |
| 272 | } |
| 273 | // We can use 2x 128-bit interleaving loads for each string, and then compare them for equality. |
| 274 | else if (length <= 32) { |
| 275 | sz_u128_vec_t a_first_vec, b_first_vec, a_second_vec, b_second_vec; |
| 276 | a_first_vec.xmm = _mm_lddqu_si128((__m128i const *)(a)); |
| 277 | b_first_vec.xmm = _mm_lddqu_si128((__m128i const *)(b)); |
| 278 | a_second_vec.xmm = _mm_lddqu_si128((__m128i const *)(a + length - 16)); |
| 279 | b_second_vec.xmm = _mm_lddqu_si128((__m128i const *)(b + length - 16)); |
| 280 | return (sz_bool_t)(_mm_movemask_epi8(_mm_and_si128( // |
| 281 | _mm_cmpeq_epi8(a_first_vec.xmm, b_first_vec.xmm), |
| 282 | _mm_cmpeq_epi8(a_second_vec.xmm, b_second_vec.xmm))) == 0xFFFF); |
| 283 | } |
| 284 | // We can use 2x 256-bit interleaving loads for each string, and then compare them for equality. |
| 285 | else if (length <= 64) { |
| 286 | sz_u256_vec_t a_first_vec, b_first_vec, a_second_vec, b_second_vec; |
| 287 | a_first_vec.ymm = _mm256_lddqu_si256((__m256i const *)(a)); |
| 288 | b_first_vec.ymm = _mm256_lddqu_si256((__m256i const *)(b)); |
| 289 | a_second_vec.ymm = _mm256_lddqu_si256((__m256i const *)(a + length - 32)); |
| 290 | b_second_vec.ymm = _mm256_lddqu_si256((__m256i const *)(b + length - 32)); |
| 291 | return (sz_bool_t)(_mm256_movemask_epi8(_mm256_and_si256( // |
| 292 | _mm256_cmpeq_epi8(a_first_vec.ymm, b_first_vec.ymm), |
| 293 | _mm256_cmpeq_epi8(a_second_vec.ymm, b_second_vec.ymm))) == (int)0xFFFFFFFF); |
| 294 | } |
| 295 | else { |
| 296 | sz_size_t i = 0; |
| 297 | sz_u256_vec_t a_vec, b_vec; |
| 298 | do { |
| 299 | a_vec.ymm = _mm256_lddqu_si256((__m256i const *)(a + i)); |
| 300 | b_vec.ymm = _mm256_lddqu_si256((__m256i const *)(b + i)); |
| 301 | // One approach can be to use "movemasks", but we could also use a bitwise |
| 302 | // matching like `_mm256_testnzc_si256`. |
| 303 | if (_mm256_movemask_epi8(_mm256_cmpeq_epi8(a_vec.ymm, b_vec.ymm)) != (int)0xFFFFFFFF) return sz_false_k; |
| 304 | i += 32; |
| 305 | } while (i + 32 <= length); |
| 306 | a_vec.ymm = _mm256_lddqu_si256((__m256i const *)(a + length - 32)); |
| 307 | b_vec.ymm = _mm256_lddqu_si256((__m256i const *)(b + length - 32)); |
| 308 | return (sz_bool_t)(_mm256_movemask_epi8(_mm256_cmpeq_epi8(a_vec.ymm, b_vec.ymm)) == (int)0xFFFFFFFF); |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | #if defined(__clang__) |
no outgoing calls
no test coverage detected
searching dependent graphs…