我正在运行面向数学的计算,花费大量时间进行memcpy,始终将80个字节从20个32位int的数组从一个位置复制到另一个位置。使用i7的两个内核,整个计算大约需要4-5天,因此即使将速度提高1%也可以节省约一个小时。速度提高了25%左右,并且还删除了size参数,只声明了内部变量似乎没有什么效果。但是,我觉得我没有利用我的复制操作总是相同大小的事实。也就是说,我找不到更好的方法。

void *memcpyi80(void* __restrict b, const void* __restrict a){
    size_t n = 80;
    char *s1 = b;
    const char *s2 = a;
    for(; 0<n; --n)*s1++ = *s2++;
    return b;
}


其他一些可能对优化有用的东西: br />我使用基于Sandy Bridge的Intel Core i7-2620M。我根本不关心可移植性。
我只关心每个int的16个最低有效位。其他16个对我无用,并且被永久清零。
即使我为每个memcpy调用复制20个32位int,但我只关心前17个。我添加了3个,因为它有助于对齐并因此提高了速度。
我在Windows 7上使用GCC 4.6。

有什么想法吗?

UPDATE:

我认为这是程序集的输出(以前从未做过,可能还有更多需求):

memcpyi80:
    pushq   %r12
    .seh_pushreg    %r12
    pushq   %rbp
    .seh_pushreg    %rbp
    pushq   %rdi
    .seh_pushreg    %rdi
    pushq   %rsi
    .seh_pushreg    %rsi
    pushq   %rbx
    .seh_pushreg    %rbx
    .seh_endprologue
    movq    %rdx, %r9
    movq    %rcx, %rax
    negq    %r9
    andl    , %r9d
    je  .L165
    movzbl  (%rdx), %ecx
    leaq    -1(%r9), %r10
    movl    , %esi
    andl    , %r10d
    cmpq    , %r9
    movl    , %ebx
    leaq    1(%rdx), %r8
    movl    , %r11d
    movb    %cl, (%rax)
    leaq    1(%rax), %rcx
    jbe .L159
    testq   %r10, %r10
    je  .L160
    cmpq    , %r10
    je  .L250
    cmpq    , %r10
    je  .L251
    cmpq    , %r10
    je  .L252
    cmpq    , %r10
    je  .L253
    cmpq    , %r10
    je  .L254
    cmpq    , %r10
    je  .L255
    movzbl  (%r8), %r8d
    movl    , %r11d
    movb    %r8b, (%rcx)
    leaq    2(%rax), %rcx
    leaq    2(%rdx), %r8
.L255:
    movzbl  (%r8), %ebx
    addq    , %r11
    addq    , %r8
    movb    %bl, (%rcx)
    addq    , %rcx
.L254:
    movzbl  (%r8), %r10d
    addq    , %r11
    addq    , %r8
    movb    %r10b, (%rcx)
    addq    , %rcx
.L253:
    movzbl  (%r8), %edi
    addq    , %r11
    addq    , %r8
    movb    %dil, (%rcx)
    addq    , %rcx
.L252:
    movzbl  (%r8), %ebp
    addq    , %r11
    addq    , %r8
    movb    %bpl, (%rcx)
    addq    , %rcx
.L251:
    movzbl  (%r8), %r12d
    addq    , %r11
    addq    , %r8
    movb    %r12b, (%rcx)
    addq    , %rcx
.L250:
    movzbl  (%r8), %ebx
    addq    , %r8
    movb    %bl, (%rcx)
    movq    %rsi, %rbx
    addq    , %rcx
    subq    %r11, %rbx
    addq    , %r11
    cmpq    %r11, %r9
    jbe .L159
    .p2align 4,,10
.L160:
    movzbl  (%r8), %r12d
    movb    %r12b, (%rcx)
    movzbl  1(%r8), %ebp
    movb    %bpl, 1(%rcx)
    movzbl  2(%r8), %edi
    movb    %dil, 2(%rcx)
    movzbl  3(%r8), %ebx
    movb    %bl, 3(%rcx)
    leaq    7(%r11), %rbx
    addq    , %r11
    movzbl  4(%r8), %r10d
    movb    %r10b, 4(%rcx)
    movq    %rsi, %r10
    movzbl  5(%r8), %r12d
    subq    %rbx, %r10
    movq    %r10, %rbx
    movb    %r12b, 5(%rcx)
    movzbl  6(%r8), %ebp
    movb    %bpl, 6(%rcx)
    movzbl  7(%r8), %edi
    addq    , %r8
    movb    %dil, 7(%rcx)
    addq    , %rcx
    cmpq    %r11, %r9
    ja  .L160
.L159:
    movl    , %r12d
    subq    %r9, %r12
    movq    %r12, %rsi
    shrq    , %rsi
    movq    %rsi, %rbp
    salq    , %rbp
    testq   %rbp, %rbp
    je  .L161
    leaq    (%rdx,%r9), %r10
    addq    %rax, %r9
    movl    , %r11d
    leaq    -1(%rsi), %rdi
    vmovdqa (%r10), %xmm0
    movl    , %edx
    andl    , %edi
    cmpq    , %rsi
    vmovdqu %xmm0, (%r9)
    jbe .L256
    testq   %rdi, %rdi
    je  .L162
    cmpq    , %rdi
    je  .L244
    cmpq    , %rdi
    je  .L245
    cmpq    , %rdi
    je  .L246
    cmpq    , %rdi
    je  .L247
    cmpq    , %rdi
    je  .L248
    cmpq    , %rdi
    je  .L249
    vmovdqa 16(%r10), %xmm3
    movl    , %r11d
    movl    , %edx
    vmovdqu %xmm3, 16(%r9)
.L249:
    vmovdqa (%r10,%rdx), %xmm4
    addq    , %r11
    vmovdqu %xmm4, (%r9,%rdx)
    addq    , %rdx
.L248:
    vmovdqa (%r10,%rdx), %xmm5
    addq    , %r11
    vmovdqu %xmm5, (%r9,%rdx)
    addq    , %rdx
.L247:
    vmovdqa (%r10,%rdx), %xmm0
    addq    , %r11
    vmovdqu %xmm0, (%r9,%rdx)
    addq    , %rdx
.L246:
    vmovdqa (%r10,%rdx), %xmm1
    addq    , %r11
    vmovdqu %xmm1, (%r9,%rdx)
    addq    , %rdx
.L245:
    vmovdqa (%r10,%rdx), %xmm2
    addq    , %r11
    vmovdqu %xmm2, (%r9,%rdx)
    addq    , %rdx
.L244:
    vmovdqa (%r10,%rdx), %xmm3
    addq    , %r11
    vmovdqu %xmm3, (%r9,%rdx)
    addq    , %rdx
    cmpq    %r11, %rsi
    jbe .L256
    .p2align 4,,10
.L162:
    vmovdqa (%r10,%rdx), %xmm2
    addq    , %r11
    vmovdqu %xmm2, (%r9,%rdx)
    vmovdqa 16(%r10,%rdx), %xmm1
    vmovdqu %xmm1, 16(%r9,%rdx)
    vmovdqa 32(%r10,%rdx), %xmm0
    vmovdqu %xmm0, 32(%r9,%rdx)
    vmovdqa 48(%r10,%rdx), %xmm5
    vmovdqu %xmm5, 48(%r9,%rdx)
    vmovdqa 64(%r10,%rdx), %xmm4
    vmovdqu %xmm4, 64(%r9,%rdx)
    vmovdqa 80(%r10,%rdx), %xmm3
    vmovdqu %xmm3, 80(%r9,%rdx)
    vmovdqa 96(%r10,%rdx), %xmm2
    vmovdqu %xmm2, 96(%r9,%rdx)
    vmovdqa 112(%r10,%rdx), %xmm1
    vmovdqu %xmm1, 112(%r9,%rdx)
    subq    $-128, %rdx
    cmpq    %r11, %rsi
    ja  .L162
.L256:
    addq    %rbp, %rcx
    addq    %rbp, %r8
    subq    %rbp, %rbx
    cmpq    %rbp, %r12
    je  .L163
.L161:
    movzbl  (%r8), %edx
    leaq    -1(%rbx), %r9
    andl    , %r9d
    movb    %dl, (%rcx)
    movl    , %edx
    cmpq    %rbx, %rdx
    je  .L163
    testq   %r9, %r9
    je  .L164
    cmpq    , %r9
    je  .L238
    cmpq    , %r9
    je  .L239
    cmpq    , %r9
    je  .L240
    cmpq    , %r9
    je  .L241
    cmpq    , %r9
    je  .L242
    cmpq    , %r9
    je  .L243
    movzbl  1(%r8), %edx
    movb    %dl, 1(%rcx)
    movl    , %edx
.L243:
    movzbl  (%r8,%rdx), %esi
    movb    %sil, (%rcx,%rdx)
    addq    , %rdx
.L242:
    movzbl  (%r8,%rdx), %r11d
    movb    %r11b, (%rcx,%rdx)
    addq    , %rdx
.L241:
    movzbl  (%r8,%rdx), %r10d
    movb    %r10b, (%rcx,%rdx)
    addq    , %rdx
.L240:
    movzbl  (%r8,%rdx), %edi
    movb    %dil, (%rcx,%rdx)
    addq    , %rdx
.L239:
    movzbl  (%r8,%rdx), %ebp
    movb    %bpl, (%rcx,%rdx)
    addq    , %rdx
.L238:
    movzbl  (%r8,%rdx), %r12d
    movb    %r12b, (%rcx,%rdx)
    addq    , %rdx
    cmpq    %rbx, %rdx
    je  .L163
    .p2align 4,,10
.L164:
    movzbl  (%r8,%rdx), %r9d
    movb    %r9b, (%rcx,%rdx)
    movzbl  1(%r8,%rdx), %r12d
    movb    %r12b, 1(%rcx,%rdx)
    movzbl  2(%r8,%rdx), %ebp
    movb    %bpl, 2(%rcx,%rdx)
    movzbl  3(%r8,%rdx), %edi
    movb    %dil, 3(%rcx,%rdx)
    movzbl  4(%r8,%rdx), %r10d
    movb    %r10b, 4(%rcx,%rdx)
    movzbl  5(%r8,%rdx), %r11d
    movb    %r11b, 5(%rcx,%rdx)
    movzbl  6(%r8,%rdx), %esi
    movb    %sil, 6(%rcx,%rdx)
    movzbl  7(%r8,%rdx), %r9d
    movb    %r9b, 7(%rcx,%rdx)
    addq    , %rdx
    cmpq    %rbx, %rdx
    jne .L164
.L163:
    popq    %rbx
    popq    %rsi
    popq    %rdi
    popq    %rbp
    popq    %r12
    ret
.L165:
    movq    %rdx, %r8
    movl    , %ebx
    jmp .L159
    .seh_endproc
    .p2align 4,,15
    .globl  memcpyi
    .def    memcpyi;    .scl    2;  .type   32; .endef
    .seh_proc   memcpyi


更新:

基于Peter Alexander的解决方案并将其组合结合线程的想法,我得出了这样的结果:现在,我想我的下一个诱惑是找到如何使用__m256 AVX类型,因此我可以分3步而不是5做到这一点。在32位屏障上,这会使事情变慢,因此__m128似乎是最佳选择。

评论

是否可以优化其他东西(除了memcpy之外)?我经常看到的是,人们认为问题出在这里,而事实并非如此。在那里。

您是否尝试展开循环?用17代替20?您还可以同时处理条件2,因为这些值已经在寄存器中了。 int应该4字节对齐,用int代替char?保存您对英特尔SSE说明的了解吗?

如果仅关心16个最低有效位,为什么不使用短裤?

MikeDunlavey-一年多来,我一直在优化这种算法。有更好的方法吗?也许吧,但这并不明显。一旦完成本主题中的建议,我将在此处发布其其他部分以供审核。 @qwert-使用短裤显然不能使编译器满意-似乎会使速度变慢。不过,我会再试一次,我之前已经试过了。

您能否确认您正确地告诉了GCC为您的目标进行优化?仅发布您的编译标志可能就足够了。我期望像-O3 -march = sandybridge这样的东西。

#1 楼

最快的方法是将数据对齐16字节边界,然后整个副本通过XMM寄存器变为5个副本。

速度是您计算机上版本的两倍以上。

像这样存储数据: />
组装输出:

#include <xmmintrin.h>
struct Data
{
    union
    {
        int i[20];
        __m128 v[5];
    };
};


评论


\ $ \ begingroup \ $
大量使用硬件,尤其是因为不考虑可移植性。
\ $ \ endgroup \ $
–杰夫·梅卡多(Jeff Mercado)
11-10-23在0:49

\ $ \ begingroup \ $
不好看。只需将我的代码转换为使用结构,即可将其从<15秒更改为> 40秒。使用新的memcpy函数将其花费37.5秒。因此功能更好,但使用结构会杀死程序。我将寻找一种无需结构即可使用xmmintrin命令的方法,以查看是否有任何更改并返回。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
11-10-23在16:29

\ $ \ begingroup \ $
通过手动对齐数据(检查编译器文档)并将其强制转换为(__m128 *),可以避免使用结构。
\ $ \ endgroup \ $
– Peter Alexander
11-10-23在16:50

\ $ \ begingroup \ $
请参阅更新,我制作了一个可以使速度提高1%的版本。非常感谢您的回答。知道如何使用__m256向量吗?干杯。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
11-10-23在19:43

\ $ \ begingroup \ $
@AlexandrosMarinos:无论如何,Sandybridge会在2个周期内进行256b加载/存储。它们仍然是单uup指令,但是使用Haswell的速度要快得多,它具有往返L1缓存的256b数据路径。如果您的数据是32B对齐的,那么SnB可能会获得很小的收益,但是如果您遇到存储转发停顿,则不会有什么收获,因为它是最近写入16B的。同样,在CPU停止数千个循环以为执行单元中的较高128b通道加电之前,256b ops比128b还要慢。如果闲置约1 ms,它会掉电。 agner.org/optimize/blog/read.php?i=142#378
\ $ \ endgroup \ $
– Peter Cordes
2015年9月18日23:38在

#2 楼

充分利用乱序执行引擎
,您还可以在《英特尔®64和IA-32架构优化参考手册》中阅读有关乱序执行引擎的信息。 > http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf第2.1.2节,并从
例如,在Intel SkyLake处理器系列(于2015年推出)中,它具有:

4个用于算术逻辑单元(ALU)的执行单元(add,and,cmp或,test,xor,movzx,movsx,mov,(v)movdqu,(v)movdqa,(v)movap *,(v)movup),
矢量ALU的3个执行单元((v)pand,( v)por,(v)pxor,(v)movq,(v)movq,(v)movap *,(v)movup *,(v)andp *,(v)orp *,(v)paddb / w / d / q,(v)blendv *,(v)blendp *,(v)pblendd)

因此,如果使用仅寄存器操作,则可以并行占用上述单元(3 + 4)。我们不能并行使用3 + 4指令进行内存复制。即使我们正在使用Level-1缓存,我们最多可以同时使用最多两个32字节指令从内存中加载和一个32字节指令从内存中存储。
请再次参阅英特尔手册了解如何执行最快的memcpy实施:http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf
第2.2.2节(哈斯韦尔微体系结构上的乱序引擎):“调度程序控制微操作在调度端口上的调度。有八个调度端口可支持故障调度顺序执行核心。八个端口中的四个为计算操作提供了执行资源,另外四个端口支持一个周期内最多执行两个256位加载和一个256位存储操作的内存操作。 .4(高速缓存和内存子系统)具有以下注释:“第一级数据高速缓存每个周期支持两个加载微操作;每个微操作最多可获取32个加载微操作。字节的数据。“
第2.2.4.1节(增强的加载和存储操作)具有以下信息:L1数据高速缓存可以在每个周期处理两个256位(32字节)加载和一个256位(32字节)存储操作。统一的L2每个周期可以服务一个高速缓存行(64字节)。此外,还有72个加载缓冲区和42个存储缓冲区可用来支持微操作的执行。
其他部分(2.3等,专用于Sandy Bridge和其他微体系结构)基本上重申了上述信息。 br />第2.3.4节(执行内核)提供了更多详细信息。
调度程序每个周期最多可以调度六个微操作,每个端口一个。下表总结了可以在哪个端口上调度哪些操作。

端口0:ALU,Shift,Mul,STTNI,Int-Div,128b-Mov,Blend,256b-Mov
端口1:ALU,快速LEA,慢LEA,MUL,Shuf,Blend,128bMov,Add,CVT
端口2和端口3:Load_Addr,Store_addr
端口4:Store_data
端口5:ALU ,Shift,Branch,Fast LEA,Shuf,Blend,128b-Mov,256b-Mov

2.3.5.1节(“加载和存储操作概述”)也可能有助于理解如何快速制作内存副本,以及第2.4.4.1节(加载和存储)。对于其他处理器体系结构,它又是-两个加载单元和一个存储单元。表2-4(Skylake微体系结构的缓存参数)具有以下信息:峰值带宽(字节/ cyc): 32B存储)
二级缓存:64字节
第三级缓存:32字节。

我还在具有DDR4内存的Intel Core i5 6600 CPU(Skylake,14nm,于2015年9月发布)上进行了速度测试,这证实了这一理论。例如,我的测试表明,与较大的寄存器(XMM)相比,使用通用的64位寄存器进行内存复制,甚至并行使用许多寄存器也会降低性能。另外,仅使用2个XMM寄存器就足够了-添加第3个不会增加性能。
如果您的CPU具有AVX CPUID位,则可以利用大型256位(32字节)YMM寄存器进行复制内存,占用两个满载单元。 AVX支持最初由Intel引入Sandy Bridge处理器,于2011年第一季度发布,随后由AMD引入Bulldozer处理器,于2011年第三季度发布。字节(对于XMM寄存器)和32字节(对于YMM寄存器),否则将发生访问冲突错误。如果数据未对齐,请使用未对齐的命令:分别使用vmovdqu和movups。
如果幸运的是拥有AVX-512处理器,则可以在四个指令中复制80个字节:
// first cycle - use two load  units
vmovdqa  ymm0, ymmword ptr [esi+0]       // load first part (32 bytes)
vmovdqa  ymm1, ymmword ptr [esi+32]      // load 2nd part (32 bytes)

// second cycle - use one load unit and one store unit
vmovdqa  xmm2, xmmword ptr [esi+64]      // load 3rd part (16 bytes)
vmovdqa  ymmword ptr [edi+0],  ymm0      // store first part

// third cycle - use one store unit
vmovdqa  ymmword ptr [edi+32], ymm1      // store 2nd part

// fourth cycle - use one store unit
vmovdqa  xmmword ptr [edi+64], xmm2      // store 3rd part

我们在这里使用寄存器30和31不会进入高位256脏状态,这是全局状态,而且可能会导致SSE / AVX过渡性罚款,此外,在某些CPU型号上,vzerouppervzeroall是退出该状态的唯一方法,或者甚至在弄脏ZMM寄存器后恢复max-turbo。但是,对于写入(x / y / z)mm16-31-SSE / AVX1 / AVX2上不存在的寄存器,CPU不会进入此状态。
进一步读取-ERMSB(不需要精确复制80个字节,但要占用更大的块)
如果您的CPU具有CPUID ERMSB(增强型REP MOVSB)位,则rep movsb命令的执行方式与旧处理器不同,并且比rep movsdmovsq)更快,但是这样做的好处rep movsb的值仅在大块上才可见。
rep movsb比仅从256字节块开始的简单普通“循环中的mov rax”复制要快,并且比从2048字节块开始的AVX复制要快。不会给您带来任何好处。
获取Microsoft Visual Studio,然后寻找memcpy.asm-它具有适用于不同处理器和不同块大小的不同方案-因此您将能够找出最适合您使用的方法处理器和您的块大小。
同时,我可以考虑英特尔ERMSB是“半熟”的,因为ERMSB内部启动非常频繁-大约35个周期,并且由于其他限制。
请参阅英特尔优化手册第3.7.6节“增强的REP MOVSB和STOSB操作(ERMSB)” http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32 -architectures-optimization-manual.pdf

启动成本为35个周期;
源地址和目标地址都必须与16字节边界对齐ary;
源区域不应与目标区域重叠;
长度必须是64的倍数才能产生更高的性能;
方向必须向前(CLD)。 br />
我希望英特尔将来能够消除如此高的启动成本。

#3 楼

如果您确实确实需要尽可能快地使用该零件,那么一种明显的方法就是用汇编语言编写它。您发布的汇编语言看起来有点疯狂(至少对我而言)。在给定大小的情况下,明显的路由将类似于:

; warning: I haven't written a lot of assembly code recently -- I could have 
; some of the syntax a bit wrong.
;
memcpyi80 proc dest:ptr byte src:ptr byte
    mov esi, src
    mov edi, dest
    mov ecx, 20    ; 80/4
    rep movsd
memcpyi80 endp


通过使用(例如)在SSE寄存器中移动绝对可以改善这一点,我将其留给其他人玩。不过,改进的幅度很小:最近的处理器有一条专门用于存储副本的特殊路径,可以使用它,因此尽管简单,但它还是很有竞争力的。当人们认为他们需要更快的内存副本时,他们确实需要重新考虑他们的代码以简单地避免使用它。

评论


\ $ \ begingroup \ $
答案确实很老,但是直到IvyBridge才引入了快速字符串操作。 (用于rep movsb的更智能的微代码,具有较低的启动开销,而且我认为可以更好地处理未对齐问题)。 intel.com/content/dam/doc/manual/…第3.7.7节。由于即使在IvB上,矢量复制也能获得小于128字节的常规memcpy大小,并且在这种情况下,大小是矢量宽度的精确倍数,因此即使在IvB上使用矢量也将是更好的选择,后来使用快速movsb。
\ $ \ endgroup \ $
– Peter Cordes
2015年9月18日23:45

\ $ \ begingroup \ $
还请考虑添加“ CLD”指令以清除“方向”标记,以防万一最终使用“ STD”进行了先前的移动后仍未清除该指令
\ $ \ endgroup \ $
– Maxim Masiutin
20-10-29在22:44

#4 楼

生成的程序集是什么?

我记得发现使用结构可以加快处理速度:

typedef struct {
  int x[17] __attribute__ ((packed));
  int padding __attribute__ ((packed, unused));
} cbytes __attribute__ ((packed));


void *memcpyi80(cbytes* __restrict b, const cbytes* __restrict a){
    size_t n = 80 / sizeof(cbytes);
    cbytes *s1 = b;
    const cbytes *s2 = a;
    for(; 0<n; --n)*s1++ = *s2++;
    return b;
}


评论


\ $ \ begingroup \ $
谢谢。添加了asm输出。将尝试使用struct方法,并让您知道。 (很遗憾,接下来20小时上午还是上午)
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
2011年10月22日10:59

#5 楼

以下代码已优化:


 void *memcpyi72(void* __restrict b, const void * __restrict a)
{
  return memcpy(b,a, 18*sizeof(int));
}
 



带有-O3的GCC生成相同的代码此函数的汇编程序与Pubby8代码相同。无需使用结构。

评论


\ $ \ begingroup \ $
使用此功能会使我的计算速度降低大约5%。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
2011年10月23日在16:20

#6 楼

您知道尺寸是多少,并且它是整数,所以请进行一些内幕交易:

void myCopy(int* dest, int* src){
    dest[ 0] = src[ 0];
    dest[ 1] = src[ 1];
    dest[ 2] = src[ 2];
    ...
    dest[19] = src[19];
}


评论


\ $ \ begingroup \ $
这大约放慢15%。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
2011-10-23 18:44

\ $ \ begingroup \ $
@Alex:嗯...接下来,我要做的是拍摄20张stackshots,因此我将确认/取消对我可能真正发生的一切猜测。
\ $ \ endgroup \ $
–迈克·邓拉维(Mike Dunlavey)
2011年10月23日在22:25

#7 楼

编译器无法向量化您的版本。如果仅更改要索引而不是取消引用的for循环,则将看到巨大的速度改进。我的速度提高了10倍以上:

void *memcpyi80(void* __restrict b, const void* __restrict a) {
    size_t n = 80;
    char *s1 = b;
    const char *s2 = a;
    for(; 0 < n; --n) {
      s1[n] = s2[n];
    }
    return b;
}


评论


\ $ \ begingroup \ $
这似乎不能正确复制。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
11-10-23在16:16

\ $ \ begingroup \ $
-1:数组索引从0开始,您的代码假定它们从1开始
\ $ \ endgroup \ $
–汤姆·克纳彭(Tom Knapen)
2011年10月23日18:00

\ $ \ begingroup \ $
使用Tom的修复程序,与我当前的实现相比,它的运行时间大约长80%。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
2011-10-23 18:39

#8 楼

您正在逐字节复制,因此用int复制int会更快。同样,展开循环应该会有所帮助:

void *memcpyi80(void* __restrict b, const void* __restrict a){
  int* s1 = b;
  int* s2 = a;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  *s1++ = *s2++;
  // *s1++ = *s2++; *s1++ = *s2++; *s1++ = *s2++;
  return b;
}


在C#中,我发现将访问和增量分开比较快,因此值得尝试: />
void *memcpyi80(void* __restrict b, const void* __restrict a){
  int* s1 = b;
  int* s2 = a;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++;
  // *s1 = *s2; s1++; s2++; *s1 = *s2; s1++; s2++; *s1 = *s2;
  return b;
}


评论


\ $ \ begingroup \ $
这会使我的速度降低> 13%。
\ $ \ endgroup \ $
–亚历山大·马里诺斯(Alexandros Marinos)
2011年10月23日在16:18

\ $ \ begingroup \ $
手动展开没有意义。编译器知道如何为您执行此操作。
\ $ \ endgroup \ $
– Reinderien
20年7月25日在16:44

#9 楼

用c或c ++编写的任何解决方案都没有比汇编更好的方法(当然,除非编写得很糟)。在我看来,除非可以使用较小数量的较大操作数,否则无法改进上面Jerry Coffin的汇编语言的答案。自然,内存地址需要正确对齐。 rep movsd指令是代码中唯一可以完成任何工作的部分,它会自动递增计数寄存器,直到操作完成。将数据分为核心部分,然后为每个部分使用单独的线程调用该函数。

评论


\ $ \ begingroup \ $
哎呀-程序集的格式在那里被破坏了...
\ $ \ endgroup \ $
–弗雷德
2011-10-23 9:04

\ $ \ begingroup \ $
另一件事-如果在64位操作系统上运行并使用64位汇编,则还应该能够使用64位操作数-即一次使用8个字节,而不是使用32-bt时仅使用4个字节。
\ $ \ endgroup \ $
–弗雷德
2011年10月23日在9:12

\ $ \ begingroup \ $
您想对80字节的副本进行多线程处理???即使使用不同的线程,整个副本也会对性能造成巨大影响,因为高速缓存行最终将在另一个内核的L1高速缓存中处于“修改”状态,并且必须转移回运行主线程的内核线。更不用说没有比复制80个字节更短的时间将请求发送到另一个线程的方法了。 rep movsd并不可怕,但是微代码的启动开销很高。对于短拷贝,完全展开的SSE / AVX更快。
\ $ \ endgroup \ $
– Peter Cordes
2015年9月19日0:00,