1.概述

内存非cache区域拷贝速度很慢,严重影响了系统性能,因此采用多种方法进行优化,主要有对齐拷贝、批量拷贝、减少循环次数、NEON拷贝方法。

2.高级SIMD和浮点寄存器介绍

2.NEON指令

2.1 VLDR

VLDR指令可从内存中将数据加载到扩展寄存器中。

VLDR{<c>}{<q>}{.64} <Dd>, [<Rn> {, #+/-<imm>}]   Encoding T1/A1, immediate form
VLDR{<c>}{<q>}{.64} <Dd>, <label>                Encoding T1/A1, normal literal form
VLDR{<c>}{<q>}{.64} <Dd>, [PC, #+/-<imm>]        Encoding T1/A1, alternative literal form
VLDR{<c>}{<q>}{.32} <Sd>, [<Rn> {, #+/-<imm>}]   Encoding T2/A2, immediate form
VLDR{<c>}{<q>}{.32} <Sd>, <label>                Encoding T2/A2, normal literal form
VLDR{<c>}{<q>}{.32} <Sd>, [PC, #+/-<imm>]        Encoding T2/A2, alternative literal form

<c>,<q>:是一个可选的条件代码。
.32,.64:是一个可选的数据大小说明符。如果是单精度VFP寄存器,则必须为32;否则必须为64。
Dd:双字(64位)加载的目标寄存器。对于NEON指令,它必须为D寄存器。对于VFP指令,它可以为D或S寄存器。
Sd:单字(32位)加载的目标寄存器。对于NEON指令,它必须为D寄存器。对于VFP指令,它可以为D或S寄存器。
Rn:存放要传送的基址的ARM寄存器,SP可使用。
+/-:偏移量相对于基地址的运算方式,+表示基地址和偏移量相加,-表示基地址和偏移量相减,+可以省略,#0和#-0生成不同的指令。
imm:是一个可选的数值表达式。在汇编时,该表达式的值必须为一个数字常数。 该值必须是4的倍数,并在0 - 1020的范围内。该值与基址相加得到用于传送的地址。
label:要加载数据项的标签。编译器自动计算指令的Align(PC, 4)值到此标签的偏移量。允许的值是4的倍数,在-1020到1020的范围内。

2.2 VLDM

VLDM指令可以将连续内存地址中的数据加载到扩展寄存器中。

VLDM{<mode>}{<c>}{<q>}{.<size>} <Rn>{!}, <list>

<mode>:IA Increment After,连续地址的起始地址在Rn寄存器中,先加载数据,然后Rn寄存器中的地址在增大,这是缺省的方式,DB Decrement Before,连续地址的起始地址在Rn寄存器中,Rn寄存器中的地址先减小,再加载数据
<c>,<q>:是一个可选的条件代码。
<size>:是一个可选的数据大小说明符。取32或64,和<list>中寄存器的位宽一致。
Rn:存放要传送的基址的ARM寄存器,由ARM指令设置,SP可使用。
!:表示Rn寄存器中的内容变化时要将变化的值写入Rn寄存器中。
<list>:加载的扩展寄存器列表。至少包含一个寄存器,如果包含了64位寄存器,最多不超过16个寄存器。

2.3 VSTR

VSTR指令将扩展寄存器中的数据保存到内存中。

VSTR{<c>}{<q>}{.64} <Dd>, [<Rn>{, #+/-<imm>}] Encoding T1/A1
VSTR{<c>}{<q>}{.32} <Sd>, [<Rn>{, #+/-<imm>}] Encoding T2/A2

<c>,<q>:是一个可选的条件代码。
.32,.64:是一个可选的数据大小说明符。如果是单精度VFP寄存器,则必须为32;否则必须为64。
Dd:双字(64位)加载的目标寄存器。对于NEON指令,它必须为D寄存器。对于VFP指令,它可以为D或S寄存器。
Sd:但字(32位)加载的目标寄存器。对于NEON指令,它必须为D寄存器。对于VFP指令,它可以为D或S寄存器。
Rn:存放要传送的基址的ARM寄存器,SP可使用。
+/-:偏移量相对于基地址的运算方式,+表示基地址和偏移量相加,-表示基地址和偏移量相减,+可以省略,#0和#-0生成不同的指令。
imm:是一个可选的数值表达式。在汇编时,该表达式的值必须为一个数字常数。 该值必须是4的倍数,并在0 - 1020的范围内。该值与基址相加得到用于传送的地址。

2.4 VSTM

VSTM指令可将扩展寄存器列表中的数据保存到连续的内存中。

VSTM{<mode>}{<c>}{<q>}{.<size>} <Rn>{!}, <list>

<mode>:IA Increment After,连续地址的起始地址在Rn寄存器中,先保存数据,然后Rn寄存器中的地址在增大,这是缺省的方式,DB Decrement Before,连续地址的起始地址在Rn寄存器中,Rn寄存器中的地址先减小,再保存数据
<c>,<q>:是一个可选的条件代码。
<size>:是一个可选的数据大小说明符。取32或64,和<list>中寄存器的位宽一致。
Rn:存放要传送的基址的ARM寄存器,由ARM指令设置,SP可使用。
!:表示Rn寄存器中的内容变化时要将变化的值写入Rn寄存器中。
<list>:保存的扩展寄存器列表。至少包含一个寄存器,如果包含了64位寄存器,最多不超过16个寄存器。

3.ARM架构程序调用寄存器使用规则

3.1.ARM寄存器使用规则

(1)子程序间通过寄存器R0-R3传递参数,被调用的子程序在返回前无须恢复寄存器R0-R3的内容,参数多余4个时,使用栈传递,参数入栈的顺序与参数顺序相反。
(2)在子程序中,使用寄存器R4-R11保存局部变量,如果子程序中使用了R4-R11的寄存器,则必须在使用之前保存这些寄存器,子程序返回之前恢复这些寄存器,在子程序中没有使用这些寄存器,则无须保存。
(3)寄存器R12用作子程序间的scratch寄存器,记作IP,在子程序间的链接代码段中常有这种规则。
(4)寄存器R13用作数据栈指针,记作SP。在子程序中,寄存器R13不能用作其它用途。
(5)寄存器R14用作连接寄存器,记作IR。用于保存子程序的返回地址,如果在子程序中保存了返回地址,寄存器R14可用于其他用途。
(6)寄存器R15是程序计数器,记作PC。不能用作其他用途。

3.2.NEON寄存器使用规则

(1)NEON S16-S31(D8-D15,Q4-Q7)寄存器在子程序中必须保存,S0-S15(D0-D7,Q4-Q7)和Q8-Q15在子程序中无须保存

3.3.子程序返回寄存器使用规则

(1)结果为一个32位整数时,可以通过寄存器R0返回。
(2)结果为一个64位整数时,可通过寄存器R0和R1返回,依次类推。
(3)结果为一个浮点数时,可以通过浮点运算部件的寄存器F0、D0或者S0来返回。
(4)结果为复合型的浮点数时,可以通过寄存器F0-Fn或者D0-Dn返回。
(5)对于位数更多的结果,需要内存来传递。

3.优化代码

PLD为arm预加载执行的宏定义,下面汇编编写的函数中都使用到了。

    #if 1#define PLD(code...)    code#else#define PLD(code...)#endif

3.1.memcpy_libc

memcpy_libc为libc的库函数memcpy。

3.2.memcpy_1

一次循环只拷贝一个字节,可用于对齐拷贝和非对齐拷贝。

    void *memcpy_1(void *dest, const void *src, size_t count){char *tmp = dest;const char *s = src;while (count--)*tmp++ = *s++;return dest;}

3.3.memcpy_32

memcpy_32一次循环拷贝32字节,适用于32字节对齐拷贝。

    @ void* memcpy_32(void*, const void*, size_t);@ 声明符号为全局作用域.global memcpy_32@ 4字节对齐.align 4@ 声明memcpy_32类型为函数.type memcpy_32, %functionmemcpy_32:@ r4-r12, lr寄存器入栈push        {r4-r11, lr}@ memcpy的返回值为目标存储区域的首地址,即第一个参数值,将返回值保存到r3寄存器中mov         r3, r00:@ 数据预取指令,会将数据提前加载到cache中PLD( pld  [r1, #128] )@ r2为memcpy的第3个参数,表示拷贝多少个字节数,首先减去32,@ s:决定指令的操作是否影响CPSR的值subs        r2, r2, #32@ r1为memcpy的第2个参数,表示源数据的地址,将源地址中的数据加载到r4-r11寄存器中@ 每加载一个寄存器,r1中的地址增加4,总共8个寄存器,共32字节数据ldmia       r1!, {r4-r11}@ 将r4-r11中保存的源数据加载到r1寄存器指向的内存地址,每加载一个寄存器,r1指向的地址加4stmia       r0!, {r4-r11}@stmia       r0!, {r8-r11}@ gt为条件码,带符号数大于,Z=0且N=Vbgt         0b@ 函数退出时将返回值保存到r0中mov         r0, r3pop         {r4-r11, pc}.type memcpy_32, %function@函数体的大小,.-memcpy_32中的.代表当前指令的地址,@即点.减去标号memcpy_32,标号代表当前指令的地址.size memcpy_32, .-memcpy_32

3.4.memcpy_64

memcpy_64一次循环拷贝64字节,适用于64字节对齐拷贝。

    @ void* memcpy_64(void*, const void*, size_t);.global memcpy_64.align 4.type memcpy_64, %functionmemcpy_64:push        {r4-r11, lr}mov         r3, r00:PLD( pld  [r1, #256] )subs        r2, r2, #64ldmia       r1!, {r4-r11}PLD( pld  [r1, #256] )stmia       r0!, {r4-r7}stmia       r0!, {r8-r11}ldmia       r1!, {r4-r11}stmia       r0!, {r4-r7}stmia       r0!, {r8-r11}bgt         0bmov         r0, r3pop         {r4-r11, pc}.type memcpy_64, %function.size memcpy_64, .-memcpy_64

3.5.memcpy_gen

memcpy_gen是通用的内存拷贝函数,可根据源地址和目的地址是否对齐,采用不同的拷贝方法,适用于对齐和非对齐拷贝。此代码参考自Linux内核。
(1)判断拷贝的字节数是否小于4字节,若小于4字节,则直接进行单字节拷贝
(2)判断目的地址是否时按4字节对齐,若没有,则进入目的地址未对齐的处理逻辑
(3)判断源地址是否按4字节对齐,若没有,则进入源地址未对齐的处理逻辑
(4)若目的地址和源地址按4字节对齐,则进入目的地址和源地址对齐的处理逻辑
目的地址和源地址对齐的处理逻辑:
(1)若拷贝的字节数大于等于32字节,则将超过32字节的数据进行批量拷贝,每次拷贝32字节
(2)若剩下的数据小于32字节,则进行4字节拷贝
(3)若剩下的数据小于4字节,则进行单字节拷贝
目的地址未对齐的处理逻辑:
(1)先将未对齐的字节进行单字节拷贝,使目的地址按4字节对齐
(2)若剩余的数据小于4字节,则进行单字节拷贝
(3)此时若源地址也按4字节对齐,则进入目的地址和源地址对齐的处理逻辑
(4)若源地址未按4字节对齐,则进入源地址未对齐的处理逻辑
源地址未对齐的处理逻辑:
(1)将源地址中的数据加载的寄存器中,进行逻辑移位
(2)将低地址的源数据逻辑左移,移到寄存器的低位,将高地址的数据逻辑右移,移到寄存器的高位
(3)将两个寄存器的数据进行或操作,实现了低地址和高地址数据在寄存器中的拼接
(4)将拼接的数据加载到目的地址中

 #define LDR1W_SHIFT 0#define STR1W_SHIFT    0#if __BYTE_ORDER == __BIG_ENDIAN  // 大端#define lspull          lsl#define lspush          lsr#elif __BYTE_ORDER == __LITTLE_ENDIAN // 小端,一般都是小端#define lspull          lsr#define lspush          lsl#else#error "unknow byte order"#endif.macro ldr1w ptr reg abortldr \reg, [\ptr], #4.endm.macro ldr4w ptr reg1 reg2 reg3 reg4 abortldmia \ptr!, {\reg1, \reg2, \reg3, \reg4}.endm.macro ldr8w ptr reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 abortldmia \ptr!, {\reg1, \reg2, \reg3, \reg4, \reg5, \reg6, \reg7, \reg8}.endm.macro ldr1b ptr reg cond=al abortldr\cond\()b \reg, [\ptr], #1.endm.macro str1w ptr reg abortstr \reg, [\ptr], #4.endm.macro str8w ptr reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 abortstmia \ptr!, {\reg1, \reg2, \reg3, \reg4, \reg5, \reg6, \reg7, \reg8}.endm.macro str1b ptr reg cond=al abortstr\cond\()b \reg, [\ptr], #1.endm.macro enter reg1 reg2stmdb sp!, {r0, \reg1, \reg2}.endm.macro exit reg1 reg2ldmfd sp!, {r0, \reg1, \reg2}.endm@ void* memcpy_gen(void*, const void*, size_t);@ 声明符号为全局作用域.global memcpy_gen@ 4字节对齐.align 4@ 声明memcpy_gen类型为函数.type memcpy_gen, %functionmemcpy_gen:@ 将r0 r4 lr寄存器保存到栈中,r0是函数的返回值enter  r4, lr@ 拷贝的字节数减4并保存到r2中,运算结果影响CPSR中的标志位subs r2, r2, #4@ blt:带符号数小于@ 如果拷贝的字节数小于4,则跳转到标号8处blt 8f@ r0保存的是目的地址,r0和3与,判断r0的低两位是否是0,不是0,则是未对齐的地址@ s:决定指令的操作是否影响CPSR的值ands    ip, r0, #3  @ 测试目的地址是否是按4字节对齐PLD( pld  [r1, #0] )bne   9f  @ 目的地址没有按4字节对齐,则跳转到标号9处ands ip, r1, #3  @ 测试源地址是否是按4字节对齐bne    10f  @ 源地址没有按4字节对齐,则跳转到标号10处1:  subs    r2, r2, #(28)stmfd  sp!, {r5 - r8}blt   5fPLD( pld  [r1, #0] )2:    subs    r2, r2, #96PLD( pld  [r1, #28] )blt 4fPLD( pld  [r1, #60] )PLD( pld  [r1, #92] )3:  PLD( pld  [r1, #124] )4:    ldr8w   r1, r3, r4, r5, r6, r7, r8, ip, lr, abort=20fsubs  r2, r2, #32str8w    r0, r3, r4, r5, r6, r7, r8, ip, lr, abort=20fbge   3bcmn   r2, #96bge  4b5:    ands    ip, r2, #28rsb  ip, ip, #32#if LDR1W_SHIFT > 0lsl    ip, ip, #LDR1W_SHIFT#endifaddne pc, pc, ip      @ C is always clear hereb  7f6:@ .rept:重复定义伪操作, 格式如下:@ .rept     重复次数@ 数据定义@ .endr     结束重复定义@ 例如:@  .rept 3@  .byte 0x23@  .endr.rept (1 << LDR1W_SHIFT)nop.endrldr1w   r1, r3, abort=20fldr1w r1, r4, abort=20fldr1w r1, r5, abort=20fldr1w r1, r6, abort=20fldr1w r1, r7, abort=20fldr1w r1, r8, abort=20fldr1w r1, lr, abort=20f#if LDR1W_SHIFT < STR1W_SHIFTlsl   ip, ip, #STR1W_SHIFT - LDR1W_SHIFT#elif LDR1W_SHIFT > STR1W_SHIFTlsr ip, ip, #LDR1W_SHIFT - STR1W_SHIFT#endifadd pc, pc, ipnop.rept  (1 << STR1W_SHIFT)nop.endrstr1w   r0, r3, abort=20fstr1w r0, r4, abort=20fstr1w r0, r5, abort=20fstr1w r0, r6, abort=20fstr1w r0, r7, abort=20fstr1w r0, r8, abort=20fstr1w r0, lr, abort=20f7:    ldmfd   sp!, {r5 - r8}8:    @ 处理拷贝的字节数小于4字节,此时r2中的值为负数,可能取值为-1 -2 -3@ lsl 逻辑左移,将r2左移31位保存带r2中movs    r2, r2, lsl #31@ ne 不相等,Z=0,r2为-1或-3时拷贝数据ldr1b  r1, r3, ne, abort=21f@ cs 无符号数大于等于,C=1@ 对于包含移位操作的非加/减法运算指令,C中包含最后一次被溢出的位的数值ldr1b  r1, r4, cs, abort=21fldr1b r1, ip, cs, abort=21fstr1b r0, r3, ne, abort=21fstr1b r0, r4, cs, abort=21fstr1b r0, ip, cs, abort=21fexit  r4, pc9:  @ 处理目的地址未按4字节对齐的情况@ rsb 逆向减法指令,等价于ip=4-ip,同时根据操作结果更新CPSR中相应的条件标志@ 当rsb的运算结果为负数时,N=1,为正数或0时,N=0@ 当rsb的运算结果符号位溢出时,V=1rsb  ip, ip, #4  @ 当地址不按4字节对齐的时候,ip的值取值可能为1、2、3@ 对于cmp指令,Z=1表示进行比较的两个数大小相等cmp   ip, #2  @ cmp影响@ gt:带符号数大于,Z=0且N=Vldr1b   r1, r3, gt, abort=21f  @ ip为3时将数据加载到r3寄存器中,同时r1=r1+1@ ge:带符号数大于等于,N=1且V=1或N=0且V=0ldr1b    r1, r4, ge, abort=21f  @ ip为2时将数据加载到r4寄存器中,同时r1=r1+1ldr1b    r1, lr, abort=21f      @ ip为1时将数据加载到lr寄存器中,同时r1=r1+1str1b    r0, r3, gt, abort=21fstr1b r0, r4, ge, abort=21fsubs  r2, r2, ip  @ 更新拷贝的字节数str1b    r0, lr, abort=21f@ 带符号数小于,N=1且V=0或N=0且V=1blt   8bands  ip, r1, #3  @ 测试源地址是否是4字节对齐的情况@ eq 相等,Z=1,r1中的地址已按4字节对齐,则跳转到标号1处,否则继续向下执行beq 1b10:   @ 处理源地址未按4字节对齐的情况@ bic指令用于清除操作数1的某些位,并把结果放置到目的寄存器中@ 将r1的低2位清0bic  r1, r1, #3@ ip保存了r1的低两位,源地址的低2位与2比较cmp  ip, #2@ 无条件执行,将r1寄存器指向的4字节数据加载到lr寄存器中,拷贝完r1=r1+4ldr1w  r1, lr, abort=21f@ eq 相等,Z=1@ ip寄存器和2相等beq   17f@ gt:带符号数大于,Z=0且N=V@ ip寄存器大于2bgt   18f.macro   forward_copy_shift pull push@ r2寄存器减去28subs    r2, r2, #28@ 带符号数小于,N=1且V=0或N=0且V=1,r2小于28跳转到14处blt  14f11:  @ 保存r5-r9寄存器,同时更新sp寄存器,fd:满递减stmfd    sp!, {r5 - r9}PLD( pld  [r1, #0] )subs  r2, r2, #96  @ r2减去96PLD( pld  [r1, #28] )blt  13f  @ 带符号数小于,N=1且V=0或N=0且V=1,r2小于96跳转到13处PLD( pld  [r1, #60] )PLD( pld  [r1, #92] )12:  PLD( pld  [r1, #124] )13:   @ lspull(小端) = lsr:逻辑右移@ lspush(小端) = lsr:逻辑左移ldr4w   r1, r4, r5, r6, r7, abort=19f@ lr逻辑右移pull位并存储到r3寄存器中mov   r3, lr, lspull #\pullsubs   r2, r2, #32ldr4w    r1, r8, r9, ip, lr, abort=19f@ r4逻辑左移push位,然后和r3或,最后将结果保存到r3中@ 将r4的push位保存到r3的(32-push)位orr    r3, r3, r4, lspush #\pushmov    r4, r4, lspull #\pullorr    r4, r4, r5, lspush #\pushmov    r5, r5, lspull #\pullorr    r5, r5, r6, lspush #\pushmov    r6, r6, lspull #\pullorr    r6, r6, r7, lspush #\pushmov    r7, r7, lspull #\pullorr    r7, r7, r8, lspush #\pushmov    r8, r8, lspull #\pullorr    r8, r8, r9, lspush #\pushmov    r9, r9, lspull #\pullorr    r9, r9, ip, lspush #\pushmov    ip, ip, lspull #\pullorr    ip, ip, lr, lspush #\pushstr8w  r0, r3, r4, r5, r6, r7, r8, r9, ip, , abort=19fbge 12bcmn  r2, #96bge  13bldmfd    sp!, {r5 - r9}14:   ands    ip, r2, #28beq  16f15:  mov r3, lr, lspull #\pullldr1w  r1, lr, abort=21fsubs  ip, ip, #4orr   r3, r3, lr, lspush #\pushstr1w  r0, r3, abort=21fbgt   15b16:  sub r1, r1, #(\push / 8)b   8b.endmforward_copy_shift   pull=8 push=2417: forward_copy_shift  pull=16    push=1618: forward_copy_shift  pull=24    push=8.macro   copy_abort_preamble19:  ldmfd   sp!, {r5 - r9}b 21f20:  ldmfd   sp!, {r5 - r8}21:.endm.macro    copy_abort_endldmfd sp!, {r4, pc}.endm.type memcpy_gen, %function@函数体的大小,.-memcpy_gen中的.代表当前指令的地址,@即点.减去标号memcpy_gen,标号代表当前指令的地址.size memcpy_gen, .-memcpy_gen

3.6.memcpy_neon_64

memcpy_neon_64使用NEON寄存器进行加速拷贝,一次拷贝64字节,适用于64字节的对齐拷贝。

    @ void* memcpy_neon_64(void*, const void*, size_t);@ 声明符号为全局作用域.global memcpy_neon_64@ 4字节对齐.align 4@ 声明memcpy_neno类型为函数.type memcpy_neon_64, %functionmemcpy_neon_64:@ 保存返回值mov        r3, r00:PLD( pld  [r1, #256] )subs    r2,  r2, #64vldmia.64  r1!,  {d0-d7}vstmia.64  r0!,  {d0-d7}bgt     0b@ 函数退出时将返回值保存到r0中mov       r0, r3@ 将函数的返回地址保存搭配pc中,退出函数mov     pc, lr .type memcpy_neon_64, %function@函数体的大小,.-memcpy_neon_64中的.代表当前指令的地址,@即点.减去标号memcpy_neon_64,标号代表当前指令的地址.size memcpy_neon_64, .-memcpy_neon_64

3.7.memcpy_neon_128

memcpy_neon_128使用NEON寄存器进行加速拷贝,一次拷贝128字节,适用于128字节的对齐拷贝。

    @ void* memcpy_neon_128(void*, const void*, size_t);@ 声明符号为全局作用域.global memcpy_neon_128@ 4字节对齐.align 4@ 声明memcpy_neno类型为函数.type memcpy_neon_128, %functionmemcpy_neon_128:@ 保存neon寄存器vpush.64 {d8-d15}@ 保存返回值mov       r3, r00:PLD( pld  [r1, #512] )  subs    r2,  r2, #128vldmia.64  r1!,  {d0-d15}vstmia.64  r0!,  {d0-d15}bgt     0b@ 函数退出时将返回值保存到r0中mov      r0, r3vpop.64 {d8-d15}@ 将函数的返回地址保存搭配pc中,退出函数mov     pc, lr .type memcpy_neon_128, %function@函数体的大小,.-memcpy_neon_128中的.代表当前指令的地址,@即点.减去标号memcpy_neon_128,标号代表当前指令的地址.size memcpy_neon_128, .-memcpy_neon_128

3.速度测试

3.1.对齐拷贝测试(单位:MiB/s)

128B 256B 512B 1KiB 4KiB 16KiB 64KiB 256KiB 1MiB 4MiB 16MiB
memcpy_libc 15.606 18.942 20.351 20.778 21.134 21.158 21.206 21.226 21.214 21.211 21.330
memcpy_1 2.640 2.698 2.719 2.725 2.738 2.737 2.740 2.738 2.733 2.751 2.756
memcpy_32 40.114 47.503 57.514 63.705 68.641 70.515 70.648 70.833 70.865 71.153 71.228
memcpy_64 35.095 47.236 58.640 66.230 70.706 72.478 73.028 73.134 73.146 73.837 73.787
memcpy_gen 39.928 47.410 58.167 63.039 68.635 70.147 70.628 70.837 70.861 71.173 71.145
memcpy_neon_64 46.143 68.012 82.576 99.937 114.627 118.747 119.676 119.780 120.150 119.967 120.945
memcpy_neon_128 54.501 75.651 106.915 122.173 153.705 156.774 159.407 159.909 160.227 162.181 161.864

3.2.非对齐拷贝测试(单位:MiB/s)

128B 129B 130B 131B 256B 257B 258B 259B 512B 513B 514B 515B
memcpy_libc 13.942 13.926 13.373 13.083 17.038 16.824 16.473 16.259 19.140 19.001 18.863 18.703
memcpy_1 2.739 2.744 2.748 2.747 2.771 2.772 2.769 2.771 2.784 2.783 2.783 2.782
memcpy_gen 24.875 27.249 29.352 30.648 33.055 35.132 36.880 38.121 39.951 41.221 42.332 43.316
1024B 1025B 1026B 1027B 4096B 4097B 4098B 4099B 16384B 16385B 16386B 16387B
memcpy_libc 20.476 20.386 20.309 20.233 21.339 21.317 21.306 21.249 21.605 21.613 21.568 21.575
memcpy_1 2.789 2.791 2.791 2.790 2.795 2.796 2.797 2.796 2.798 2.797 2.797 2.796
memcpy_gen 44.988 45.677 46.418 47.149 49.470 49.784 50.022 50.113 50.883 50.868 50.949 50.992
65536B 65537B 65538B 65539B
memcpy_libc 21.668 21.677 21.653 21.679
memcpy_1 2.797 2.796 2.796 2.796
memcpy_gen 51.163 51.168 51.162 51.215

4.影响拷贝速度的因素

(1)一次循环拷贝数据的字节数
(2)地址是否对齐
(3)pld预取对uncache拷贝影响很小

5.结论

(1)大批量数据拷贝时,应将源地址和目的地址按32字节、64字节或128字节对齐
(2)按32字节、64字节或128字节对齐的数据,使用neon寄存器进行批量拷贝,若无neon寄存器,则使用arm寄存器进行批量拷贝
(3)若拷贝的字节数小于要求的字节数,则使用通用的方法进行拷贝
(4)uncache区域拷贝,预加载pld指令对拷贝速度的提升有限

Linux内存uncache区域拷贝优化相关推荐

  1. 《嵌入式Linux内存使用与性能优化》笔记

    <嵌入式Linux内存使用与性能优化>笔记 这本书有两个关切点:系统内存(用户层)和性能优化. 这本书和Brendan Gregg的<Systems Performance>相 ...

  2. 《嵌入式linux内存使用与性能优化》读书笔记

    <嵌入式linux内存使用与性能优化>读书笔记 前言 本书的重点分为系统内存和性能优化,前4章着重内存使用,尽量减少进程的内存使用量,定位和发现内存泄漏:后5章着重与如何让系统性能优化,加 ...

  3. 嵌入式linux内存使用和性能优化

    这本书有两个关切点:系统内存(用户层)和性能优化. 这本书和Brendan Gregg的<Systems Performance>相比,无论是技术层次还是更高的理论都有较大差距.但是这不影 ...

  4. Linux内存管理:内存描述之内存区域zone

    目录 1 前景回顾 1.1 UMA和NUMA两种模型 1.2 (N)UMA模型中linux内存的机构 1.3 Linux如何描述物理内存 1.4 用pd_data_t描述内存节点node 1.5 今日 ...

  5. Linux内存管理:memblock(引导期间管理内存区域)

    目录 介绍 内存块 内存块初始化 Memblock API 获取有关内存区域的信息 Memblock调试 链接 相关阅读 看原文:<Linux内存管理:memblock> 介绍 内存管理是 ...

  6. 【Linux 内核 内存管理】memblock 分配器 ③ ( memblock_region 内存块区域 | memblock_region 结构体成员分析 | memblock 分配器标志位 )

    文章目录 一.memblock_region 内存块区域 二.memblock_region 结构体成员分析 1.base 成员 2.size 成员 3.flags 成员 4.nid 成员 三.mem ...

  7. linux内存管理子系统采用基于内存区域,Linux 内存管理之highmem简介

    一.Linux内核地址空间 一般来说Linux 内核按照 3:1 的比率来划分虚拟内存(X86等):3 GB 的虚拟内存用于用户空间,1GB 的内存用于内核空间.当然有些体系结构如MIPS使用2:2 ...

  8. linux 内存管理优化,Linux性能优化实战 内存篇 阅读笔记

    第十五讲 基础篇:Linux内存是怎么工作的(2020.6.8) 这一讲相关的内容正好之前看csapp的时候总结了一下,可以直接贴出来作为总结了. Linux的内存工作原理,这又是一个特别大的话题.一 ...

  9. Linux文件读写机制及优化方式

    本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只是做了不同封装.以下所有测试均使用open, read ...

最新文章

  1. Wannafly挑战赛29题解
  2. python学习详解_深入解析Python小白学习【操作列表】
  3. EXCEL VBA 导入图片自适应大小
  4. JZOJ 5267. 费马点问题
  5. android getid,Process.myTid()和Thread.currentThread().getId()区别
  6. NUC1178 Kickdown【水题】
  7. Python制作彩色二维码
  8. mysql 修改密码
  9. form表单提交数据编码方式和tomcat接受数据解码方式的思考
  10. 获取当前日期上周的周一和周日日期
  11. MySQL5.6建索引时遇到 Specified key was too long; max key length is 767 bytes错误提示解决办法
  12. 一文深刻解析UWB是什么技术?
  13. 2023年Python数据分析有什么好的课程推荐吗?
  14. 思博伦Avalanche中的Load类型定义_双极未来
  15. GRE配置详解和路由黑洞及检测机制
  16. 游戏开发的HelloWorld,快速入门,新手上路,使用CocosCreator+JS,flyBird(飞翔小鸟)
  17. 关于5位编程高手涉嫌侵犯腾讯著作权被受审的一些看法
  18. 找谷歌地图上任意点的经纬度
  19. 粗读《Python 深度学习》(7)
  20. 学员_国培阶段性学习总结

热门文章

  1. 浅谈数学在计算机科学中的应用,浅谈计算机科学技术在数学思想中的应用(原稿)...
  2. vue如何判断iOS与Android系统
  3. 小米计算机弹歌曲,趣味冷知识!小米的T9拨号键盘可以弹乐曲,没想到吧!快来试试!...
  4. spark学习基础篇1--spark概述与入门
  5. 英文面试二——why you are looking for a new job
  6. java路上偶遇占小狼
  7. TF内存卡(SDHC)的传输规范
  8. linux中dd命令详解,Linux dd命令详解
  9. ardruino控制继电器_Arduino基础入门篇24—继电器控制
  10. 三维空间 点线面解析