Linux内核memcpy的不同实现_libc memcpy-程序员宅基地

技术标签: Linux嵌入式系统专栏  memcpy  linux  内核  

目录

1.概述

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

2.NEON指令

2.1 VLDR

2.2 VLDM

2.3 VSTR

2.4 VSTM

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

3.1.ARM寄存器使用规则

3.2.NEON寄存器使用规则

3.优化代码

3.1.memcpy_libc

3.2.memcpy_1

3.3.memcpy_32

3.4.memcpy_64

3.5.memcpy_gen

3.7.memcpy_neon_128

3.速度测试

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

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

4.影响拷贝速度的因素

5.结论


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指令可将扩展寄存器列表中的数据保存到连续的内存中。

<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, %function
    memcpy_32:
        @ r4-r12, lr寄存器入栈
        push        {r4-r11, lr}
        @ memcpy的返回值为目标存储区域的首地址,即第一个参数值,将返回值保存到r3寄存器中
        mov         r3, r0
    0:
        @ 数据预取指令,会将数据提前加载到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指向的地址加4
        stmia       r0!, {r4-r11}
        @stmia       r0!, {r8-r11}
        @ gt为条件码,带符号数大于,Z=0且N=V
        bgt         0b
        @ 函数退出时将返回值保存到r0中
        mov         r0, r3
        pop         {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, %function
    memcpy_64:
        push        {r4-r11, lr}
        mov         r3, r0
    0:
        PLD( pld  [r1, #256] )
        subs        r2, r2, #64
        ldmia       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         0b
        mov         r0, r3
        pop         {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 abort
		ldr \reg, [\ptr], #4
		.endm
	
		.macro ldr4w ptr reg1 reg2 reg3 reg4 abort
		ldmia \ptr!, {\reg1, \reg2, \reg3, \reg4}
		.endm
	
		.macro ldr8w ptr reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 abort
		ldmia \ptr!, {\reg1, \reg2, \reg3, \reg4, \reg5, \reg6, \reg7, \reg8}
		.endm
	
		.macro ldr1b ptr reg cond=al abort
		ldr\cond\()b \reg, [\ptr], #1
		.endm
	
		.macro str1w ptr reg abort
		str \reg, [\ptr], #4
		.endm
	
		.macro str8w ptr reg1 reg2 reg3 reg4 reg5 reg6 reg7 reg8 abort
		stmia \ptr!, {\reg1, \reg2, \reg3, \reg4, \reg5, \reg6, \reg7, \reg8}
		.endm
	
		.macro str1b ptr reg cond=al abort
		str\cond\()b \reg, [\ptr], #1
		.endm
	
		.macro enter reg1 reg2
		stmdb sp!, {r0, \reg1, \reg2}
		.endm
	
		.macro exit reg1 reg2
		ldmfd sp!, {r0, \reg1, \reg2}
		.endm
	
	@ void* memcpy_gen(void*, const void*, size_t);
	@ 声明符号为全局作用域
	.global memcpy_gen
	@ 4字节对齐
	.align 4
	@ 声明memcpy_gen类型为函数
	.type memcpy_gen, %function
	memcpy_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	5f
		PLD( pld  [r1, #0] )
	
	2:	subs	r2, r2, #96
		PLD( pld  [r1, #28] )
		blt	4f
		PLD( pld  [r1, #60] )
		PLD( pld  [r1, #92] )
	
	3:	PLD( pld  [r1, #124] )
	4:	ldr8w	r1, r3, r4, r5, r6, r7, r8, ip, lr, abort=20f
		subs	r2, r2, #32
		str8w	r0, r3, r4, r5, r6, r7, r8, ip, lr, abort=20f
		bge	3b
		cmn	r2, #96
		bge	4b
	
	5:	ands	ip, r2, #28
		rsb	ip, ip, #32
	#if LDR1W_SHIFT > 0
			lsl	ip, ip, #LDR1W_SHIFT
	#endif
		addne	pc, pc, ip		@ C is always clear here
		b	7f
	6:
	    @ .rept:重复定义伪操作, 格式如下:
	    @ .rept     重复次数
	    @ 数据定义
	    @ .endr     结束重复定义
	    @ 例如:
	    @  .rept 3
	    @  .byte 0x23
	    @  .endr
		.rept	(1 << LDR1W_SHIFT)
		nop
		.endr
	
		ldr1w	r1, r3, abort=20f
		ldr1w	r1, r4, abort=20f
		ldr1w	r1, r5, abort=20f
		ldr1w	r1, r6, abort=20f
		ldr1w	r1, r7, abort=20f
		ldr1w	r1, r8, abort=20f
		ldr1w	r1, lr, abort=20f
	
	#if LDR1W_SHIFT < STR1W_SHIFT
			lsl	ip, ip, #STR1W_SHIFT - LDR1W_SHIFT
	#elif LDR1W_SHIFT > STR1W_SHIFT
			lsr	ip, ip, #LDR1W_SHIFT - STR1W_SHIFT
	#endif
		add	pc, pc, ip
		nop
		.rept	(1 << STR1W_SHIFT)
		nop
		.endr
	
		str1w	r0, r3, abort=20f
		str1w	r0, r4, abort=20f
		str1w	r0, r5, abort=20f
		str1w	r0, r6, abort=20f
		str1w	r0, r7, abort=20f
		str1w	r0, r8, abort=20f
		str1w	r0, lr, abort=20f
	
	7:	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=21f
		ldr1b	r1, ip, cs, abort=21f
		str1b	r0, r3, ne, abort=21f
		str1b	r0, r4, cs, abort=21f
		str1b	r0, ip, cs, abort=21f
		exit	r4, pc
	
	9:  @ 处理目的地址未按4字节对齐的情况
	
		@ rsb 逆向减法指令,等价于ip=4-ip,同时根据操作结果更新CPSR中相应的条件标志
		@ 当rsb的运算结果为负数时,N=1,为正数或0时,N=0
		@ 当rsb的运算结果符号位溢出时,V=1
		rsb	ip, ip, #4  @ 当地址不按4字节对齐的时候,ip的值取值可能为1、2、3
		@ 对于cmp指令,Z=1表示进行比较的两个数大小相等
		cmp	ip, #2  @ cmp影响
		@ gt:带符号数大于,Z=0且N=V
		ldr1b	r1, r3, gt, abort=21f  @ ip为3时将数据加载到r3寄存器中,同时r1=r1+1
		@ ge:带符号数大于等于,N=1且V=1或N=0且V=0
		ldr1b	r1, r4, ge, abort=21f  @ ip为2时将数据加载到r4寄存器中,同时r1=r1+1
		ldr1b	r1, lr, abort=21f      @ ip为1时将数据加载到lr寄存器中,同时r1=r1+1
		str1b	r0, r3, gt, abort=21f
		str1b	r0, r4, ge, abort=21f
		subs	r2, r2, ip  @ 更新拷贝的字节数
		str1b	r0, lr, abort=21f
		@ 带符号数小于,N=1且V=0或N=0且V=1
		blt	8b
		ands	ip, r1, #3  @ 测试源地址是否是4字节对齐的情况
		@ eq 相等,Z=1,r1中的地址已按4字节对齐,则跳转到标号1处,否则继续向下执行
		beq	1b
	
	10:	@ 处理源地址未按4字节对齐的情况
	
		@ bic指令用于清除操作数1的某些位,并把结果放置到目的寄存器中
		@ 将r1的低2位清0
		bic	r1, r1, #3
		@ ip保存了r1的低两位,源地址的低2位与2比较
		cmp	ip, #2
		@ 无条件执行,将r1寄存器指向的4字节数据加载到lr寄存器中,拷贝完r1=r1+4
		ldr1w	r1, lr, abort=21f
		@ eq 相等,Z=1
		@ ip寄存器和2相等
		beq	17f
		@ gt:带符号数大于,Z=0且N=V
		@ ip寄存器大于2
		bgt	18f
	
		.macro	forward_copy_shift pull push
		@ r2寄存器减去28
		subs	r2, r2, #28
		@ 带符号数小于,N=1且V=0或N=0且V=1,r2小于28跳转到14处
		blt	14f
	11:	@ 保存r5-r9寄存器,同时更新sp寄存器,fd:满递减
		stmfd	sp!, {r5 - r9}
		PLD( pld  [r1, #0] )
		subs	r2, r2, #96  @ r2减去96
		PLD( 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 #\pull
		subs	r2, r2, #32
		ldr4w	r1, r8, r9, ip, lr, abort=19f
		@ r4逻辑左移push位,然后和r3或,最后将结果保存到r3中
		@ 将r4的push位保存到r3的(32-push)位
		orr	r3, r3, r4, lspush #\push
		mov	r4, r4, lspull #\pull
		orr	r4, r4, r5, lspush #\push
		mov	r5, r5, lspull #\pull
		orr	r5, r5, r6, lspush #\push
		mov	r6, r6, lspull #\pull
		orr	r6, r6, r7, lspush #\push
		mov	r7, r7, lspull #\pull
		orr	r7, r7, r8, lspush #\push
		mov	r8, r8, lspull #\pull
		orr	r8, r8, r9, lspush #\push
		mov	r9, r9, lspull #\pull
		orr	r9, r9, ip, lspush #\push
		mov	ip, ip, lspull #\pull
		orr	ip, ip, lr, lspush #\push
		str8w	r0, r3, r4, r5, r6, r7, r8, r9, ip, , abort=19f
		bge	12b
		cmn	r2, #96
		bge	13b
		ldmfd	sp!, {r5 - r9}
	
	14:	ands	ip, r2, #28
		beq	16f
	
	15:	mov	r3, lr, lspull #\pull
		ldr1w	r1, lr, abort=21f
		subs	ip, ip, #4
		orr	r3, r3, lr, lspush #\push
		str1w	r0, r3, abort=21f
		bgt	15b
	
	16:	sub	r1, r1, #(\push / 8)
		b	8b
		.endm
	
		forward_copy_shift	pull=8	push=24
	
	17:	forward_copy_shift	pull=16	push=16
	
	18:	forward_copy_shift	pull=24	push=8
	
		.macro	copy_abort_preamble
	19:	ldmfd	sp!, {r5 - r9}
		b	21f
	20:	ldmfd	sp!, {r5 - r8}
	21:
		.endm
	
		.macro	copy_abort_end
		ldmfd	sp!, {r4, pc}
		.endm
	
	.type memcpy_gen, %function
	 @函数体的大小,.-memcpy_gen中的.代表当前指令的地址,
	 @即点.减去标号memcpy_gen,标号代表当前指令的地址
	.size memcpy_gen, .-memcpy_gen

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, %function
    memcpy_neon_128:
        @ 保存neon寄存器
        vpush.64 {d8-d15}
        @ 保存返回值
        mov		r3, r0
    0:
        PLD( pld  [r1, #512] )	
        subs    r2,  r2, #128
        vldmia.64  r1!,  {d0-d15}
        vstmia.64  r0!,  {d0-d15}
        bgt     0b
        @ 函数退出时将返回值保存到r0中
        mov		r0, r3
        vpop.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)

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

4.影响拷贝速度的因素

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

5.结论

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/smartvxworks/article/details/125621901

智能推荐

软件测试流程包括哪些内容?测试方法有哪些?_测试过程管理中包含哪些过程-程序员宅基地

文章浏览阅读2.9k次,点赞8次,收藏14次。测试主要做什么?这完全都体现在测试流程中,同时测试流程是面试问题中出现频率最高的,这不仅是因为测试流程很重要,而是在面试过程中这短短的半小时到一个小时的时间,通过测试流程就可以判断出应聘者是否合适,故在测试流程中包含了测试工作的核心内容,例如需求分析,测试用例的设计,测试执行,缺陷等重要的过程。..._测试过程管理中包含哪些过程

政府数字化政务的人工智能与机器学习应用:如何提高政府工作效率-程序员宅基地

文章浏览阅读870次,点赞16次,收藏19次。1.背景介绍政府数字化政务是指政府利用数字技术、互联网、大数据、人工智能等新技术手段,对政府政务进行数字化改革,提高政府工作效率,提升政府服务质量的过程。随着人工智能(AI)和机器学习(ML)技术的快速发展,政府数字化政务中的人工智能与机器学习应用也逐渐成为政府改革的重要内容。政府数字化政务的人工智能与机器学习应用涉及多个领域,包括政策决策、政府服务、公共安全、社会治理等。在这些领域,人工...

ssm+mysql+微信小程序考研刷题平台_mysql刷题软件-程序员宅基地

文章浏览阅读219次,点赞2次,收藏4次。系统主要的用户为用户、管理员,他们的具体权限如下:用户:用户登录后可以对管理员上传的学习视频进行学习。用户可以选择题型进行练习。用户选择小程序提供的考研科目进行相关训练。用户可以进行水平测试,并且查看相关成绩用户可以进行错题集的整理管理员:管理员登录后可管理个人基本信息管理员登录后可管理个人基本信息管理员可以上传、发布考研的相关例题及其分析,并对题型进行管理管理员可以进行查看、搜索考研题目及错题情况。_mysql刷题软件

根据java代码描绘uml类图_Myeclipse8.5下JAVA代码导成UML类图-程序员宅基地

文章浏览阅读1.4k次。myelipse里有UML1和UML2两种方式,UML2功能更强大,但是两者生成过程差别不大1.建立Test工程,如下图,uml包存放uml类图package com.zz.domain;public class User {private int id;private String name;public int getId() {return id;}public void setId(int..._根据以下java代码画出类图

Flume自定义拦截器-程序员宅基地

文章浏览阅读174次。需求:一个topic包含很多个表信息,需要自动根据json字符串中的字段来写入到hive不同的表对应的路径中。发送到Kafka中的数据原本最外层原本没有pkDay和project,只有data和name。因为担心data里面会空值,所以根同事商量,让他们在最外层添加了project和pkDay字段。pkDay字段用于表的自动分区,proejct和name合起来用于自动拼接hive表的名称为 ..._flume拦截器自定义开发 kafka

java同时输入不同类型数据,Java Spring中同时访问多种不同数据库-程序员宅基地

文章浏览阅读380次。原标题:Java Spring中同时访问多种不同数据库 多样的工作要求,可以使用不同的工作方法,只要能获得结果,就不会徒劳。开发企业应用时我们常常遇到要同时访问多种不同数据库的问题,有时是必须把数据归档到某种数据仓库中,有时是要把数据变更推送到第三方数据库中。使用Spring框架时,使用单一数据库是非常容易的,但如果要同时访问多个数据库的话事件就变得复杂多了。本文以在Spring框架下开发一个Sp..._根据输入的不同连接不同的数据库

随便推点

EFT试验复位案例分析_eft电路图-程序员宅基地

文章浏览阅读3.6k次,点赞9次,收藏25次。本案例描述了晶振屏蔽以及开关电源变压器屏蔽对系统稳定工作的影响, 硬件设计时应考虑。_eft电路图

MR21更改价格_mr21 对于物料 zba89121 存在一个当前或未来标准价格-程序员宅基地

文章浏览阅读1.1k次。对于物料价格的更改,可以采取不同的手段:首先,我们来介绍MR21的方式。 需要说明的是,如果要对某一产品进行价格修改,必须满足的前提条件是: ■ 1、必须对价格生效的物料期间与对应会计期间进行开启; ■ 2、该产品在该物料期间未发生物料移动。执行MR21,例如更改物料1180051689的价格为20000元,系统提示“对于物料1180051689 存在一个当前或未来标准价格”,这是因为已经对该..._mr21 对于物料 zba89121 存在一个当前或未来标准价格

联想启天m420刷bios_联想启天M420台式机怎么装win7系统(完美解决usb)-程序员宅基地

文章浏览阅读7.4k次,点赞3次,收藏13次。[文章导读]联想启天M420是一款商用台式电脑,预装的是win10系统,用户还是喜欢win7系统,该台式机采用的intel 8代i5 8500CPU,在安装安装win7时有很多问题,在安装win7时要在BIOS中“关闭安全启动”和“开启兼容模式”,并且安装过程中usb不能使用,要采用联想win7新机型安装,且默认采用的uefi+gpt模式,要改成legacy+mbr引导,那么联想启天M420台式电..._启天m420刷bios

冗余数据一致性,到底如何保证?-程序员宅基地

文章浏览阅读2.7k次,点赞2次,收藏9次。一,为什么要冗余数据互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量。水平切分会有一个patition key,通过patition key的查询能..._保证冗余性

java 打包插件-程序员宅基地

文章浏览阅读88次。是时候闭环Java应用了 原创 2016-08-16 张开涛 你曾经因为部署/上线而痛苦吗?你曾经因为要去运维那改配置而烦恼吗?在我接触过的一些部署/上线方式中,曾碰到过以下一些问题:1、程序代码和依赖都是人工上传到服务器,不是通过工具进行部署和发布;2、目录结构没有规范,jar启动时通过-classpath任意指定;3、fat jar,把程序代码、配置文件和依赖jar都打包到一个jar中,改配置..._那么需要把上面的defaultjavatyperesolver类打包到插件中

VS2015,Microsoft Visual Studio 2005,SourceInsight4.0使用经验,Visual AssistX番茄助手的安装与基本使用9_番茄助手颜色-程序员宅基地

文章浏览阅读909次。1.得下载一个番茄插件,按alt+g才可以有函数跳转功能。2.不安装番茄插件,按F12也可以有跳转功能。3.进公司的VS工程是D:\sync\build\win路径,.sln才是打开工程的方式,一个是VS2005打开的,一个是VS2013打开的。4.公司库里的线程接口,在CmThreadManager.h 里,这个里面是我们的线程库,可以直接拿来用。CreateUserTaskThre..._番茄助手颜色

推荐文章

热门文章

相关标签