日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux中memcpy实现分析,ARM64 的 memcpy 优化与实现

發布時間:2023/12/2 linux 67 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux中memcpy实现分析,ARM64 的 memcpy 优化与实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如何優化 memcpy 函數

Linux 內核用到了許多方式來加強性能以及穩定性,本文探討的 memcpy 的匯編實現方式就是其中的一種,memcpy 的性能是否強大,拷貝延遲是否足夠低都直接影響著整個系統性能。通過對拷貝函數的理解可以加深對整個系統設計的一個理解,同時提升自身技術實力。

羅馬不是一天建設而成的,Linux 內核的拷貝函數也不是一開始就是那么優秀,在 3.14 之前(具體多少版本忘記了),Linux 尚且沒有完善對 ARM64 架構的支持,系統的內存拷貝函數就是一個簡單的 c 語言版本,也就是目前內核中的通用拷貝函數。

#ifndef __HAVE_ARCH_MEMCPY

/**

* memcpy - Copy one area of memory to another

* @dest: Where to copy to

* @src: Where to copy from

* @count: The size of the area.

*

* You should not use this function to access IO space, use memcpy_toio()

* or memcpy_fromio() instead.

*/

void *memcpy(void *dest, const void *src, size_t count)

{

char *tmp = dest;

const char *s = src;

while (count--)

*tmp++ = *s++;

return dest;

}

EXPORT_SYMBOL(memcpy);

#endif

在沒有定義 __HAVE_ARCH_MEMCPY 之前,內核就會采用最簡單的逐字節拷貝,我相信一個剛入學的大學生也能寫得出一個這樣的代碼,完全不需要考慮對齊,不需要考慮性能等等,就是這么直白,這么暴力的拷貝數據。

當然,我們不可能真的采用這樣的代碼來運轉系統,不然再好的硬件能力也會被粗糙的代碼毀掉,那么不如一起來做一個簡單的優化?

現代計算機已經不再是 20 世紀時代的 16 位機甚至更早的 8 位機,一個寄存器寬度已經達到了驚人的 64 位(32 位機器也會在這兩年被主流淘汰掉,大部分的操作系統已經不再提供 32 位支持),既然如此,何不將這個一個特性利用起來。

void *memcpy(void *d, void *s, size_t count)

{

int i;

for (i = 0; i < count / sizeof(int64_t); i++) {

(int64_t *)d++ = (int64_t *)s++;

}

return d;

}

這樣是不是舒服多了(代碼沒有考慮 count 不能被整除的情況,僅僅做一個演示),一條指令下去就可以完成 8 個字節的拷貝,這樣整個循環體直接縮減為原來的 1/8,效率是上一版本的 8 倍之多。那么僅此而已嗎?

不然,在 CPU 的指令上,跳轉指令的耗時是很高的,軟件應該盡可能的減少 CPU 跳轉,上面的代碼沒做完一次 8 字節的拷貝之后就需要完成一個跳轉,那么是不是可以減少一些跳轉呢?當然,那就是循環展開:

void *memcpy(void *d, void *s, size_t count)

{

int i;

for (i = 0; i < count / sizeof(int) / 4; i++) {

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

(int *)d++ = (int *)s++;

}

return d;

}

循環展開也做了,有沒有其他的方式可以繼續優化呢?當然有,盡管 ARM64 的機器指令寬度為 64 位,最多一次能存儲 8 個字節,但是他還有更為高級的寄存器,那就是向量寄存器,通過 NEON 指令處理,可以一次性搬移 128 位數據,也就是 16個字節,這樣效率又提升一倍,通過代碼演示一下:

#include

void *memcpy_128(void *dest, void *src, size_t count)

{

int i;

unsigned long *s = (unsigned long *)src;

unsigned long *d = (unsigned long *)dest;

for (i = 0; i < count / 64; i++) {

vst1q_u64(&d[0], vld1q_u64(&s[0]));

vst1q_u64(&d[2], vld1q_u64(&s[2]));

vst1q_u64(&d[4], vld1q_u64(&s[4]));

vst1q_u64(&d[6], vld1q_u64(&s[6]));

d += 8; s += 8;

}

return dest;

}

上面的代碼通過 NEON 改造之后,一次循環體可以處理 64 字節的數據,大大的加快了拷貝效率。還有沒有更好的優化方式?當然是有的,那就是用匯編來寫

當前 ARM64 構架的實現方式

熟悉 Linux 內核的都知道,Linus 為了讓 kernel 跑得更快,更健壯,代碼能夠重復利用就一定重復利用,不但可以減少生成的二進制 bin 文件大小,而且能減少維護成本,arch/arm64/lib/memcpy.S 就是這樣的例子。

ENTRY(__memcpy)

ENTRY(memcpy)

#include "copy_template.S"

ret

ENDPIPROC(memcpy)

ENDPROC(__memcpy)

memcpy.S 直接 include 了一個 copy_template.S 的文件,其實就是直接貼上了這樣的一份代碼,這個 copy_template.S 不僅僅只是在 memcpy.S 中用到,在其他的類似 copy_to_user.S 和 copy_from_user.S 中也被包含。

既然如此,我們只需要深入分析 copy_template.S 即可。這里不貼代碼進行逐行分析,因為也沒有什么好分析的,當你完全理解設計思想,再對著代碼你主需要理解每一行的匯編是什么意思即可。

從上圖可以看出,拷貝算法將數據分為 3 個大的部分,第一個部分就是不對齊部分,通過對傳入的 src 地址進行分析,首先處理掉不能被 16 整除的前面不對齊數據,然后處理對齊的數據。

對齊的數據以 128 為一個界限,每一個 128 字節數據都能通過大塊拷貝直接計算完畢,一直循環到最后剩余的尾部 128 以下的字節。

整體設計邏輯流程圖如下:

大體思想很簡單,那就是首先處理不對齊,之后處理大拷貝部分,然后細分到最小的各個部分,通過利用寄存器寬度來減少拷貝次數。

比如最后的 120 個字節會被分為:120 = 64 + 32 + 16 + 8,這樣處理可以得到最佳的性能。

memcpy 拷貝性能測試

編寫一個新的算法當然需要對他進行性能測試,那么該如何做性能測試呢?當然是需要編寫一個內核驅動,可以隨意百度一個 HelloWorld 的模塊,參考其邏輯編寫一個簡單的模塊,在 module_init 的函數中寫入這樣的一段測試代碼,等模塊加載完畢之后,會附帶打印當前輸入的測試的 memcpy 算法的性能。

typedef void *(*memcpy_t)(void *, void *, size_t);

void memcpy_speed_test(memcpy_t __memcpy, void *b1, void *b2)

{

int speed;

unsigned long now, j;

int i, count, max;

preempt_disable();

max = 0;

for (i = 0; i < 5; i++) {

j = jiffies;

count = 0;

while ((now = jiffies) == j)

cpu_relax();

while (time_before(jiffies, now + 1)) {

mb(); /* prevent loop optimzation */

__memcpy(bench_size, b1, b2);

mb();

count++;

mb();

}

if (count > max)

max = count;

}

preempt_enable();

speed = max * (HZ * bench_size / 1024);

printk(KERN_INFO "memcpy_test: %5d.%03d MB/sec\n", speed / 1000, speed % 1000);

}

其實還有一個優化的點就是注意 L1 Cache 的對齊,這個在匯編代碼中有體現,C 語言版本就不提及???

總結

以上是生活随笔為你收集整理的linux中memcpy实现分析,ARM64 的 memcpy 优化与实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。