[PATCH] ntdll: Optimize memcpy for x86-64.

Elaine Lefler elaineclefler at gmail.com
Tue Mar 22 20:33:33 CDT 2022


Signed-off-by: Elaine Lefler <elaineclefler at gmail.com>
---

New vectorized implementation improves performance up to 65%.
---
 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.@)
-- 
2.32.0 (Apple Git-132)




More information about the wine-devel mailing list