天天看點

Linux記憶體uncache區域拷貝優化

轉自:Linux記憶體uncache區域拷貝優化
Linux記憶體uncache區域拷貝優化
https://blog.csdn.net/u011037593/article/details/115024275

1.概述

記憶體非cache區域拷貝速度很慢,嚴重影響了系統性能,是以采用多種方法進行優化,主要有對齊拷貝、批量拷貝、減少循環次數、NEON拷貝方法。

2.進階SIMD和浮點寄存器介紹

Linux記憶體uncache區域拷貝優化

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, %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.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, %function
    memcpy_neon_64:
        @ 儲存傳回值
        mov		r3, r0
    0:
        PLD( pld  [r1, #256] )
        subs    r2,  r2, #64
        vldmia.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, %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)

Linux記憶體uncache區域拷貝優化

 3.2.非對齊拷貝測試(機關:MiB/s)

Linux記憶體uncache區域拷貝優化

4.影響拷貝速度的因素

(1)一次循環拷貝資料的位元組數

(2)位址是否對齊

(3)pld預取對uncache拷貝影響很小

5.結論

(1)大批量資料拷貝時,應将源位址和目的位址按32位元組、64位元組或128位元組對齊

(2)按32位元組、64位元組或128位元組對齊的資料,使用neon寄存器進行批量拷貝,若無neon寄存器,則使用arm寄存器進行批量拷貝

(3)若拷貝的位元組數小于要求的位元組數,則使用通用的方法進行拷貝

(4)uncache區域拷貝,預加載pld指令對拷貝速度的提升有限

繼續閱讀