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似乎是最佳选择。
#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型号上,
vzeroupper
或vzeroall
是退出该状态的唯一方法,或者甚至在弄脏ZMM寄存器后恢复max-turbo。但是,对于写入(x / y / z)mm16-31-SSE / AVX1 / AVX2上不存在的寄存器,CPU不会进入此状态。进一步读取-ERMSB(不需要精确复制80个字节,但要占用更大的块)
如果您的CPU具有CPUID ERMSB(增强型REP MOVSB)位,则
rep movsb
命令的执行方式与旧处理器不同,并且比rep movsd
(movsq
)更快,但是这样做的好处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,
评论
是否可以优化其他东西(除了memcpy之外)?我经常看到的是,人们认为问题出在这里,而事实并非如此。在那里。您是否尝试展开循环?用17代替20?您还可以同时处理条件2,因为这些值已经在寄存器中了。 int应该4字节对齐,用int代替char?保存您对英特尔SSE说明的了解吗?
如果仅关心16个最低有效位,为什么不使用短裤?
MikeDunlavey-一年多来,我一直在优化这种算法。有更好的方法吗?也许吧,但这并不明显。一旦完成本主题中的建议,我将在此处发布其其他部分以供审核。 @qwert-使用短裤显然不能使编译器满意-似乎会使速度变慢。不过,我会再试一次,我之前已经试过了。
您能否确认您正确地告诉了GCC为您的目标进行优化?仅发布您的编译标志可能就足够了。我期望像-O3 -march = sandybridge这样的东西。