[PATCH] ntdll: Optimize memcpy for x86-64.
Jinoh Kang
jinoh.kang.kr at gmail.com
Wed Mar 30 10:27:42 CDT 2022
On 3/23/22 10:33, Elaine Lefler wrote:
> Signed-off-by: Elaine Lefler <elaineclefler at gmail.com>
> ---
>
> New vectorized implementation improves performance up to 65%.
MSVCRT has one. Maybe deduplicate?
> ---
> dlls/ntdll/string.c | 162 +++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 159 insertions(+), 3 deletions(-)
>
> diff --git a/dlls/ntdll/string.c b/dlls/ntdll/string.c
> index 0fa83821d21..443fc98418a 100644
> --- a/dlls/ntdll/string.c
> +++ b/dlls/ntdll/string.c
> @@ -33,6 +33,16 @@
> #include "winternl.h"
> #include "ntdll_misc.h"
>
> +#ifdef __x86_64__
> +
> +#include <x86intrin.h>
> +
> +/* Enable vectorized memcpy implementation (all x86-64 CPUs have SSE2).
> + * TODO: This could be enabled for x86 with a cpuid check. */
> +#define SSE2_MEMCPY
> +
> +#endif
> +
>
> /* same as wctypes except for TAB, which doesn't have C1_BLANK for some reason... */
> static const unsigned short ctypes[257] =
> @@ -96,10 +106,154 @@ int __cdecl memcmp( const void *ptr1, const void *ptr2, size_t n )
>
> /*********************************************************************
> * memcpy (NTDLL.@)
> - *
> - * NOTES
> - * Behaves like memmove.
> */
> +#ifdef SSE2_MEMCPY
> +
> +#define declare_fastcpy(n) \
> +static void fastcpy_ ## n \
> +( uintptr_t as, const uintptr_t as_end, uintptr_t d ) \
> +{ \
> + __m128i x, y; \
> + x = *(const __m128i*)as; \
> + /* Read 32 bytes in, 16 bytes out. Shuffle variables when done so we don't
> + * re-read the first part. */ \
> + while (as < as_end) \
> + { \
> + /* Prefetch hint improves performance by minimizing cache pollution */ \
> + _mm_prefetch((const void*)(as + 16), _MM_HINT_NTA); \
> + _mm_prefetch((const void*)d, _MM_HINT_NTA); \
> + y = *(const __m128i*)(as + 16); \
> + /* (n) is the number of bytes in *as that don't go to *d. Little endian
> + * means the first bytes appear on the right, so srl to remove them */ \
> + x = _mm_srli_si128(x, (n)); \
> + /* Take same number of bytes from *(as + 16) and push them to the upper
> + * part of the register */ \
> + x = _mm_or_si128(x, _mm_slli_si128(y, 16 - (n))); \
> + *(__m128i*)d = x; \
> + d += 16; \
> + as += 16; \
> + x = y; \
> + } \
> +}
> +
> +declare_fastcpy(1)
> +declare_fastcpy(2)
> +declare_fastcpy(3)
> +declare_fastcpy(4)
> +declare_fastcpy(5)
> +declare_fastcpy(6)
> +declare_fastcpy(7)
> +declare_fastcpy(8)
> +declare_fastcpy(9)
> +declare_fastcpy(10)
> +declare_fastcpy(11)
> +declare_fastcpy(12)
> +declare_fastcpy(13)
> +declare_fastcpy(14)
> +declare_fastcpy(15)
> +
> +typedef void (*fastcpy_ptr) ( uintptr_t, const uintptr_t, uintptr_t );
> +
> +static const fastcpy_ptr fastcpy_table[16] = {
> + NULL, /* special case, different code path */
> + fastcpy_1,
> + fastcpy_2,
> + fastcpy_3,
> + fastcpy_4,
> + fastcpy_5,
> + fastcpy_6,
> + fastcpy_7,
> + fastcpy_8,
> + fastcpy_9,
> + fastcpy_10,
> + fastcpy_11,
> + fastcpy_12,
> + fastcpy_13,
> + fastcpy_14,
> + fastcpy_15
> +};
> +
> +void * __cdecl memcpy( void *dst, const void *src, size_t n )
> +{
> + uintptr_t s = (uintptr_t)src;
> + uintptr_t d = (uintptr_t)dst;
> + uintptr_t as;
> +
> + _mm_prefetch((const void*)s, _MM_HINT_NTA);
> + _mm_prefetch((const void*)d, _MM_HINT_NTA);
> +
> + /* Ensure aligned destination */
> + while (d & 15)
> + {
> + if (n-- == 0)
> + return dst;
> + *(BYTE*)d++ = *(const BYTE*)s++;
> + }
> +
> + if (n < 16)
> + {
> + /* Too small to vectorize */
> + while (n--) *(BYTE*)d++ = *(const BYTE*)s++;
> + return dst;
> + }
> +
> + as = s & ~15;
> + if (as == s)
> + {
> + /* Fastest path: both pointers aligned */
> + while (n >= 16)
> + {
> + _mm_prefetch((const void*)s, _MM_HINT_NTA);
> + _mm_prefetch((const void*)d, _MM_HINT_NTA);
> + *(__m128i*)d = *(const __m128i*)s;
> +
> + d += 16;
> + s += 16;
> + n -= 16;
> + }
> + }
> + else
> + {
> + /* Read from aligned s by rounding down. If as < src, we need to slow
> + * copy another 16 bytes to avoid OOB reads. */
> + ptrdiff_t shift = s - as;
> + uintptr_t as_end = ((s + n) & ~15) - 16;
> +
> + if (as < (uintptr_t)src)
> + {
> + uintptr_t target_n = n - 16;
> + while (n > target_n)
> + {
> + if (n-- == 0)
> + return dst;
> + *(BYTE*)d++ = *(const BYTE*)s++;
> + }
> +
> + as += 16;
> + }
> +
> + /* Copy 16-byte chunks if any are possible. Since s is misaligned, we
> + * need to read one chunk ahead of what we're writing, which means
> + * as_end must point to the _beginning_ of the last readable chunk.
> + * This also guarantees there is no overrun, since delta < n - 16. */
> + if (as_end > as)
> + {
> + ptrdiff_t delta = as_end - as;
> + fastcpy_table[shift](as, as_end, d);
> + s += delta;
> + d += delta;
> + n -= delta;
> + }
> + }
> +
> + /* Slow copy anything that remains */
> + while (n--) *(BYTE*)d++ = *(const BYTE*)s++;
> + return dst;
> +}
> +
> +#else /* defined(SSE2_MEMCPY) */
> +
> +/* Note: Behaves like memmove */
> void * __cdecl memcpy( void *dst, const void *src, size_t n )
> {
> volatile unsigned char *d = dst; /* avoid gcc optimizations */
> @@ -118,6 +272,8 @@ void * __cdecl memcpy( void *dst, const void *src, size_t n )
> return dst;
> }
>
> +#endif /* !defined(SSE2_MEMCPY) */
> +
>
> /*********************************************************************
> * memmove (NTDLL.@)
--
Sincerely,
Jinoh Kang
More information about the wine-devel
mailing list