> From: Morten Brørup
> Sent: Tuesday, 18 August 2026 11.13
> 
> > From: Konstantin Ananyev [mailto:[email protected]]
> > Sent: Tuesday, 18 August 2026 10.15
> >
> > > > > > > > > > > > > > > + /* Common way for small copy size of 64-
> > byte
> > > > > > blocks.
> > > > > > > > > > > > Unlikely, so
> > > > > > > > > > > > > > constant size only */
> > > > > > > > > > > > > > > + if (__rte_constant(n) && (n & 63) == 0 &&
> > n <=
> > > > > > > > > > > > > > RTE_MEMCPY_BLOCK_64_MAX) {
> > > > > > > > > > > > > > > +         void *ret = dst;
> > > > > > > > > > > > > > > +
> > > > > > > > > > > > > >
> > > > > > > > > > > > > > Maybe just let compiler decide, it will
> > generate
> > > > vector
> > > > > > > > > > > > instructions in
> > > > > > > > > > > > > > most cases.
> > > > > > > > > > > > > >
> > > > > > > > > > > > > >     if (__rte_constant(n))
> > > > > > > > > > > > > >             return mempcpy(dst, src, n);
> > > > > > > > > > > > >
> > > > > > > > > > > > > Maybe in most, but not in all:
> > > > > > > > > > > > > https://godbolt.org/z/KvdKqT5rY
> > > > > > > > > > > >
> > > > > > > > > > > > With '-mavx' or '-mavx512f' it looks like it does
> > for
> > > > your
> > > > > > > > sample
> > > > > > > > > > code.
> > > > > > > > > > >
> > > > > > > > > > > It also does with -msse4.2 when SZ is reduced to
> 256
> > > > bytes.
> > > > > > > > > > > Clang switches to inline when SZ is reduced to 128
> > bytes.
> > > > > > > > > > >
> > > > > > > > > > > It seems the compiler has a threshold for when to
> > inline
> > > > and
> > > > > > when
> > > > > > > > to
> > > > > > > > > > call the C
> > > > > > > > > > > library's memcpy subroutine.
> > > > > > > > > > > The threshold depends on both copy size and vector
> > > > register
> > > > > > size.
> > > > > > > > > > > And it is compiler dependent.
> > > > > > > > > >
> > > > > > > > > > I think there are compiler options to specify desired
> > > > threshold
> > > > > > > > values.
> > > > > > > > > > Let say for gcc there is  ' -mmemcpy-
> > strategy=strategy'.
> > > > > > > > > > For that example in that particular case
> > > > > > > > > > -mmemcpy-strategy=vector_loop:512:align,loop:-1:align
> > > > > > > > > > generates sse loads/stores.
> > > > > > > > > > Might be we can exploit it somehow?
> > > > > > > > >
> > > > > > > > > That could give us higher granularity/control over
> memcpy
> > for
> > > > > > > > individual
> > > > > > > > > memcpy instances; might be useful for hot code paths
> > where we
> > > > > > have
> > > > > > > > more
> > > > > > > > > knowledge about the copy operation than the compiler
> can
> > > > infer.
> > > > > > > > > However, pragmas are discouraged in DPDK, and this
> looks
> > like
> > > > a
> > > > > > very
> > > > > > > > similar
> > > > > > > > > path.
> > > > > > > >
> > > > > > > > Well, right now  rte_memcpy.h is 700+ lines and keeps
> > growing.
> > > > > > > > Considering that probably pragmas are not that bad.
> > > > > > > > Of course, pragmas have their own issues and it is hard
> to
> > > > ensure
> > > > > > that
> > > > > > > > they will produce same code between different
> > > > compilers/versions,
> > > > > > etc.
> > > > > > > >
> > > > > > > > > > I am not really happy that our home-brewed memcpy
> code-
> > > > block
> > > > > > keeps
> > > > > > > > > > growing,
> > > > > > > > > > while we keep talking that it would be good to
> > eliminate it
> > > > > > > > completely.
> > > > > > > > >
> > > > > > > > > I agree in principle.
> > > > > > > > > However, this rte_memcpy() optimization is for the
> > > > pile/mempool
> > > > > > > > optimizations
> > > > > > > > > I'm working on, so there is a specific use case
> > motivating
> > > > the
> > > > > > added
> > > > > > > > code.
> > > > > > > >
> > > > > > > > I understand that you probably have some specific use-
> case
> > in
> > > > mind.
> > > > > > > > BTW for this optimization you mentioned above: what is
> the
> > gain
> > > > > > with
> > > > > > > > these changes?
> > > > > > >
> > > > > > > IMO, the primary benefit is the much simpler (and smaller)
> > > > assembly
> > > > > > output due
> > > > > > > to avoiding the address alignment check (and the resulting
> > > > duplicated
> > > > > > code).
> > > > > >
> > > > > > I think that should be measurable too: whole binary  and/or
> hot
> > > > path
> > > > > > function size reduction, etc.
> > > > >
> > > > > The size of the generated code for the copy operation is
> reduced
> > to
> > > > slightly less
> > > > > than half (only one instance of the copy operation instead of
> > two,
> > > > and the
> > > > > address alignment comparison is omitted).
> > > > >
> > > > > >
> > > > > > > I haven't measured the performance gain.
> > > > > > > Based on the perf gain in a previous mempool optimization
> > patch
> > > > [1],
> > > > > > it seems
> > > > > > > avoiding the address alignment check shaves ~2 cycles off
> the
> > > > copy
> > > > > > operation (for
> > > > > > > cache-to-cache copy).
> > > > > > > I expect that the same gain (from avoiding the address
> > alignment
> > > > > > check) applies
> > > > > > > here.
> > > > > >
> > > > > > Ok, then I suggest we do some measurements first, before
> going
> > > > forward
> > > > > > with it.
> > > > >
> > > > > I tried memcpy_perf_autotest, but the branch predictor kicks in
> > and
> > > > eliminates
> > > > > the cost of the address alignment comparison. So the results
> are
> > very
> > > > similar.
> > > >
> > > > Indeed, they are the same.
> > > >
> > > > > BTW, that's a general issue with our perf tests: Repeated
> testing
> > > > doesn't show
> > > > > the cost of branches, because they are always eliminated by the
> > > > branch
> > > > > predictor.
> > > >
> > > > Might be...
> > > > But we do need some measurable evidence that such change improve
> > > > things,
> > > > otherwise - why to bother?
> > > > If that perf test is not goof enough - let's try to extend it or
> > use
> > > > different one.
> > > > BTW, in your mempool pile RFC, I see you also use rte_memcpy over
> > fixed
> > > > sized buffers.
> > > > Do you see any improvement there (wit/without rte_memcpy
> > optimixation)?
> > >
> > > Looking at the generated assembly (objdump -S
> > > build/drivers/librte_mempool_stack.so)...
> > >
> > > This is the optimized copy loop in pile_dequeue:
> > >
> > >     1650: 89 ca                   mov    %ecx,%edx
> > >     1652: c5 fe 6f 40 40          vmovdqu 0x40(%rax),%ymm0
> > >     1657: ff c1                   inc    %ecx
> > >     1659: c1 e2 05                shl    $0x5,%edx
> > >     165c: 49 8d 14 d4             lea    (%r12,%rdx,8),%rdx
> > >     1660: c5 fe 7f 02             vmovdqu %ymm0,(%rdx)
> > >     1664: c5 fe 6f 48 60          vmovdqu 0x60(%rax),%ymm1
> > >     1669: c5 fe 7f 4a 20          vmovdqu %ymm1,0x20(%rdx)
> > >     166e: c5 fe 6f 90 80 00 00    vmovdqu 0x80(%rax),%ymm2
> > >     1675: 00
> > >     1676: c5 fe 7f 52 40          vmovdqu %ymm2,0x40(%rdx)
> > >     167b: c5 fe 6f 98 a0 00 00    vmovdqu 0xa0(%rax),%ymm3
> > >     1682: 00
> > >     1683: c5 fe 7f 5a 60          vmovdqu %ymm3,0x60(%rdx)
> > >     1688: c5 fe 6f a0 c0 00 00    vmovdqu 0xc0(%rax),%ymm4
> > >     168f: 00
> > >     1690: c5 fe 7f a2 80 00 00    vmovdqu %ymm4,0x80(%rdx)
> > >     1697: 00
> > >     1698: c5 fe 6f a8 e0 00 00    vmovdqu 0xe0(%rax),%ymm5
> > >     169f: 00
> > >     16a0: c5 fe 7f aa a0 00 00    vmovdqu %ymm5,0xa0(%rdx)
> > >     16a7: 00
> > >     16a8: c5 fe 6f b0 00 01 00    vmovdqu 0x100(%rax),%ymm6
> > >     16af: 00
> > >     16b0: c5 fe 7f b2 c0 00 00    vmovdqu %ymm6,0xc0(%rdx)
> > >     16b7: 00
> > >     16b8: c5 fe 6f b8 20 01 00    vmovdqu 0x120(%rax),%ymm7
> > >     16bf: 00
> > >     16c0: c5 fe 7f ba e0 00 00    vmovdqu %ymm7,0xe0(%rdx)
> > >     16c7: 00
> > >     16c8: 48 8b 40 08             mov    0x8(%rax),%rax
> > >     16cc: 39 f1                   cmp    %esi,%ecx
> > >     16ce: 75 80                   jne    1650
> <pile_dequeue+0xe0>
> > >     16d0:
> > >
> > > This is without the rte_memcpy optimization:
> > >
> > >     1650: 89 ca                   mov    %ecx,%edx
> > >     1652: c5 fe 6f 40 40          vmovdqu 0x40(%rax),%ymm0
> > >     1657: 49 89 c1                mov    %rax,%r9
> > >     165a: ff c1                   inc    %ecx
> > >     165c: c1 e2 05                shl    $0x5,%edx
> > >     165f: 49 8d 14 d4             lea    (%r12,%rdx,8),%rdx
> > >     1663: c5 fe 7f 02             vmovdqu %ymm0,(%rdx)
> > >     1667: c5 fe 6f 48 60          vmovdqu 0x60(%rax),%ymm1
> > >     166c: 49 09 d1                or     %rdx,%r9
> > >     166f: 41 83 e1 1f             and    $0x1f,%r9d
> > >     1673: c5 fe 7f 4a 20          vmovdqu %ymm1,0x20(%rdx)
> > >     1678: c5 fe 6f 90 80 00 00    vmovdqu 0x80(%rax),%ymm2
> > >     167f: 00
> > >     1680: c5 fe 7f 52 40          vmovdqu %ymm2,0x40(%rdx)
> > >     1685: c5 fe 6f 98 a0 00 00    vmovdqu 0xa0(%rax),%ymm3
> > >     168c: 00
> > >     168d: c5 fe 7f 5a 60          vmovdqu %ymm3,0x60(%rdx)
> > >     1692: c5 fe 6f a0 c0 00 00    vmovdqu 0xc0(%rax),%ymm4
> > >     1699: 00
> > >     169a: c5 fe 7f a2 80 00 00    vmovdqu %ymm4,0x80(%rdx)
> > >     16a1: 00
> > >     16a2: c5 fe 6f a8 e0 00 00    vmovdqu 0xe0(%rax),%ymm5
> > >     16a9: 00
> > >     16aa: c5 fe 7f aa a0 00 00    vmovdqu %ymm5,0xa0(%rdx)
> > >     16b1: 00
> > >     16b2: c5 fe 6f b0 00 01 00    vmovdqu 0x100(%rax),%ymm6
> > >     16b9: 00
> > >     16ba: c5 fe 7f b2 c0 00 00    vmovdqu %ymm6,0xc0(%rdx)
> > >     16c1: 00
> > >     16c2: c5 fe 6f b8 20 01 00    vmovdqu 0x120(%rax),%ymm7
> > >     16c9: 00
> > >     16ca: c5 fe 7f ba e0 00 00    vmovdqu %ymm7,0xe0(%rdx)
> > >     16d1: 00
> > >     16d2: 48 8b 40 08             mov    0x8(%rax),%rax
> > >     16d6: 0f 85 c4 00 00 00       jne    17a0
> <pile_dequeue+0x230>
> > >     16dc: 39 ce                   cmp    %ecx,%esi
> > >     16de: 0f 85 6c ff ff ff       jne    1650
> <pile_dequeue+0xe0>
> > >     16e4:
> > >     [...]
> > >     17a0: 39 f1                   cmp    %esi,%ecx
> > >     17a2: 0f 85 a8 fe ff ff       jne    1650
> <pile_dequeue+0xe0>
> > >     17a8: e9 37 ff ff ff          jmp    16e4
> <pile_dequeue+0x174>
> > >
> > > Here, the alignment comparison I have optimized away is performed
> > using
> > > register %r9.
> > >
> > > With the optimization, the generated assembly is less cluttered,
> and
> > thus easier
> > > to review.
> > > (It seems the compiler is clever enough to not duplicate the two
> > instances of the
> > > code, but reuse the aligned instance for the unaligned case too. So
> > the reduction
> > > in code size is not as great as I previously claimed.)
> > >
> > > I agree the performance benefit in CPU cycles is probably
> > insignificant.
> > > But the generated assembly is cleaner.
> > > And if pressured for CPU registers, the optimization also frees up
> > one CPU
> > > register for other purposes.
> > >
> > > Code size reduction:
> > > The optimized loop is 0x80 bytes of instructions.
> > > The non-optimized is 0x8e bytes, 14 bytes more, in the loop, plus
> 13
> > bytes
> > > outside the loop.
> >
> > Honestly, with such insignificant gains, I'd either leave it alone,
> > or look more closely at compiler pragmas option we discussed before.
> 
> Compiled code is slightly smaller, performance is slightly better.
> But source code having slightly more lines of code is more important?
> 
> If the source code added was difficult to read, complexity was
> increased, or had side effects or spillover to other modules, I might
> buy that argument.
> But the addition is very simple and completely isolated.
> 
> If the objection is about source code readability, I could add more
> inline comments to the added code, elaborating that the added code path
> is the same for all CPU vector sizes, regardless of address alignment.
> But I suspect such comments would confuse more than they would help.
> And it would add even more lines to the file size.
> 
> Adding compiler pragmas should probably be done in source code calling
> rte_memcpy(), like __rte_assume() and the coming
> __rte_assume_cache_aligned() [1].
> 
> [1]:
> https://patchwork.dpdk.org/project/dpdk/patch/20260812090723.1771628-1-
> [email protected]/
> 
> If you try experimenting with compiler pragmas, I'm open for reviewing
> an RFC!
> For now, let's take this small step.
> 

I seems nobody want more code in the rte_memcpy() implementation, so I have 
marked this patch as Rejected, and instead posted a separate patch with the 
cleanups [2].

[2]: 
https://patchwork.dpdk.org/project/dpdk/patch/[email protected]/

I had an improved version in the works, not requiring constant size; but I 
assume it will be rejected too, so I'm not pursuing it.
For anyone interested, it had this criteria:

        if (__rte_constant((n & 63) == 0 && n <= RTE_MEMCPY_BLOCK_64_MAX) &&
                 (n & 63) == 0 && n <= RTE_MEMCPY_BLOCK_64_MAX) {

The full diff:

@@ -175,2 +175,3 @@ rte_mov256(uint8_t *__rte_restrict dst,
 #define RTE_MEMCPY_ALIGNMENT_MASK 0x3F
+#define RTE_MEMCPY_BLOCK_64_MAX 512
 
@@ -321,2 +322,3 @@ COPY_BLOCK_128_BACK63:
 #define RTE_MEMCPY_ALIGNMENT_MASK 0x1F
+#define RTE_MEMCPY_BLOCK_64_MAX 256
 
@@ -432,2 +434,3 @@ COPY_BLOCK_128_BACK31:
 #define RTE_MEMCPY_ALIGNMENT_MASK 0x0F
+#define RTE_MEMCPY_BLOCK_64_MAX 512
 
@@ -675,2 +678,36 @@ rte_memcpy(void *__rte_restrict dst, con
 {
+       /*
+        * Common way for small copy size of 64-byte blocks,
+        * independent of alignment with vector register size.
+        * Note:
+        * If known at compile time that the criteria are fulfilled,
+        * the compiler will use this, and omit everything else;
+        * otherwise, the compiler will omit this.
+        */
+       static_assert(RTE_MEMCPY_BLOCK_64_MAX <= 512,
+                       "64-byte block copy max size too big for implementation 
below");
+       if (__rte_constant((n & 63) == 0 && n <= RTE_MEMCPY_BLOCK_64_MAX) &&
+                       (n & 63) == 0 && n <= RTE_MEMCPY_BLOCK_64_MAX) {
+               void *ret = dst;
+
+               if (!(RTE_MEMCPY_BLOCK_64_MAX < 512) && n == 512) {
+                       rte_mov256((uint8_t *)dst + 0 * 256, (const uint8_t 
*)src + 0 * 256);
+                       rte_mov256((uint8_t *)dst + 1 * 256, (const uint8_t 
*)src + 1 * 256);
+               } else {
+                       if (n & 256) {
+                               rte_mov256((uint8_t *)dst, (const uint8_t 
*)src);
+                               src = (const uint8_t *)src + 256;
+                               dst = (uint8_t *)dst + 256;
+                       }
+                       if (n & 128) {
+                               rte_mov128((uint8_t *)dst, (const uint8_t 
*)src);
+                               src = (const uint8_t *)src + 128;
+                               dst = (uint8_t *)dst + 128;
+                       }
+                       if (n & 64)
+                               rte_mov64((uint8_t *)dst, (const uint8_t *)src);
+               }
+               return ret;
+       }
+
        /* Fast way when copy size doesn't exceed 64 bytes. */
@@ -715,2 +752,3 @@ rte_memcpy(void *__rte_restrict dst, con
 #undef RTE_MEMCPY_ALIGNMENT_MASK
+#undef RTE_MEMCPY_BLOCK_64_MAX


Reply via email to