国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux內核快速處理路徑盡量多用kmem_cache而慎用kmalloc

Linux閱碼場 ? 來源:Linuxer ? 2020-04-30 15:35 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

題目是一個典型《Effective C++》的風格。

事情是這樣的,我大致說一下。

我在開發一個Netfilter模塊,在PREROUTING匹配一些數據包,顯而易見,都能想到使用哈希表hlist作為數據結構的容器,其中裝有下面的結構體:

struct item {

struct hlist_node hnode;

char padding[16];

};

生成item的時候,我先用kmalloc接口分配內存:

item_nd = (struct item *)kmalloc(sizeof(struct item), GFP_KERNEL);

然后我用hlist_add/del接口將分配好的結構體插入到hlist中。

僅僅為了測試是否會宕機,所以我的所有的數據結構的hash值均是一樣的,這樣插入200個項的話,它們會hash沖突,從而僅僅添加到同一個hlist鏈表中,這樣整個匹配過程就退化成了遍歷200個項的鏈表。

雖然是萬惡的遍歷操作,但200個項一切還OK,性能幾乎是無損的,無論是吞吐,還是pps。

這個時候,我想擴充一些功能,于是乎為item結構體增加了一個字段:

struct item {

struct hlist_node hnode;

char padding[16];

void *private;

};

僅僅增加了一個private,其它均和之前完全一致,同樣的200個項插入同一條hlist,同樣遍歷,吞吐和pps下降達到15%~20%!

為什么增加了一個指針變量,就出現了如此巨大的性能差異?!

事情的端倪就隱藏在kmalloc接口中!

事情的真相是,在不添加private指針時,item結構的大小是32,添加一個指針,其大小變成了40,別小看這8個字節:

32字節大小的所有200個item在內存中幾乎都是連續的。

40字節大小的所有200個item在內存中幾乎都是不連續的。

為什么會造成這個結果?32和40有什么特殊性嗎?

我們還要繼續向下看。

kmalloc的背后其實是一系列的kmem_cache:

8字節的kmem_cache

16字節的kmem_cache

32字節的kmem_cache

64字節的kmem_cache

92字節的kmem_cache

128字節的kmem_cache

...

我們從/proc/slabinfo里可以一窺究竟:

[root@localhost test]# cat /proc/slabinfo |grep ^kmalloc

kmalloc-8192 52 52 8192 4 8 : tunables 0 0 0 : slabdata 13 13 0

kmalloc-4096 274 288 4096 8 8 : tunables 0 0 0 : slabdata 36 36 0

kmalloc-2048 578 608 2048 16 8 : tunables 0 0 0 : slabdata 38 38 0

kmalloc-1024 1105 1120 1024 16 4 : tunables 0 0 0 : slabdata 70 70 0

kmalloc-512 1466 1584 512 16 2 : tunables 0 0 0 : slabdata 99 99 0

kmalloc-256 2289 2560 256 16 1 : tunables 0 0 0 : slabdata 160 160 0

kmalloc-192 1630 1785 192 21 1 : tunables 0 0 0 : slabdata 85 85 0

kmalloc-128 1632 1632 128 32 1 : tunables 0 0 0 : slabdata 51 51 0

kmalloc-96 1344 1344 96 42 1 : tunables 0 0 0 : slabdata 32 32 0

kmalloc-64 25408 25408 64 64 1 : tunables 0 0 0 : slabdata 397 397 0

kmalloc-32 3072 3072 32 128 1 : tunables 0 0 0 : slabdata 24 24 0

kmalloc-16 3072 3072 16 256 1 : tunables 0 0 0 : slabdata 12 12 0

kmalloc-8 5120 5120 8 512 1 : tunables 0 0 0 : slabdata 10 10 0

當你調用kmalloc(size, flags)申請內存時,系統會根據你的size向上尋找一個最接近的kmem_cache,然后在其中為你分配所需的內存。

我們知道kmemcache是針對特定數據結構的獨享內存池子,它以*最小化碎片*的原則為特定的場合提供*可高效訪問*的內存,比如sock,skbuff這些。

然而kmalloc接口所依托的kmem_cache則是全局(同一個NUMA node)共享的內存池子,它并不針對特定場合,僅僅針對特定大小!也即是說,最小化碎片是針對所有調用kmalloc接口的線程的。

我們回頭看上面的slabinfo,可以注意到,64字節大小的kmem_cache,即kmalloc-64已經包含了非常多的object,因此如果你調用kmalloc申請40字節的內存,其實你是在kmalloc-64里分配。

其實32和40沒有什么特殊性,32字節大小的item之所以還可以保持連續,那是因為kmalloc-32幾乎沒有被重度使用,而kmalloc-64則已經被其它使用者打散。

我們可以試一下,看看分別申請32字節和40字節的效果:

#include

struct stub32 {

unsigned char m[32];

};

struct stub40 {

unsigned char m[40];

};

#define SIZE 20

struct stub32 *array32[SIZE] = {NULL};

struct stub40 *array40[SIZE] = {NULL};

%}

function alloc_test()

%{

int i;

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

array32[i] = kmalloc(sizeof(struct stub32), GFP_KERNEL);

printk("32bytes [%d]:%p ", i, array32[i]);

if (i > 0) {

unsigned long hi = (unsigned long)array32[i];

unsigned long lo = (unsigned long)array32[i - 1];

signed long delta = hi - lo;

if (delta < 0)

delta = lo - hi;

printk("delta [%lx] ", delta);

} else

printk("delta [0] ");

}

printk("------------------ ");

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

array40[i] = kmalloc(sizeof(struct stub40), GFP_KERNEL);

printk("40bytes [%d]:%p ", i, array40[i]);

if (i > 0) {

unsigned long hi = (unsigned long)array40[i];

unsigned long lo = (unsigned long)array40[i - 1];

signed long delta = hi - lo;

if (delta < 0)

delta = lo - hi;

printk("delta [%lx] ", delta);

} else

printk("delta [0] ");

}

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

kfree(array32[i]);

kfree(array40[i]);

}

%}

probe begin

{

alloc_test();

exit(); // oneshot模式

}

以下是結果:

[ 466.933100] 32bytes [1]:ffff881f9649caa0 delta [20]

[ 466.938206] 32bytes [2]:ffff881f9649cac0 delta [20]

[ 466.943314] 32bytes [3]:ffff881f9649cae0 delta [20]

[ 466.948586] 32bytes [4]:ffff881f9649cb00 delta [20]

[ 466.953732] 32bytes [5]:ffff881f9649cb20 delta [20]

[ 466.958863] 32bytes [6]:ffff881f9649cb40 delta [20]

[ 466.963977] 32bytes [7]:ffff881f9649cb60 delta [20]

[ 466.969095] 32bytes [8]:ffff881f9649cb80 delta [20]

[ 466.974222] 32bytes [9]:ffff881f9649cba0 delta [20]

[ 466.979329] 32bytes [10]:ffff881f9649cbc0 delta [20]

[ 466.984731] 32bytes [11]:ffff881f9649cbe0 delta [20]

[ 466.990124] 32bytes [12]:ffff881f9649cc00 delta [20]

[ 466.995510] 32bytes [13]:ffff881f9649cc20 delta [20]

[ 467.000907] 32bytes [14]:ffff881f9649cc40 delta [20]

[ 467.006294] 32bytes [15]:ffff881f9649cc60 delta [20]

[ 467.011685] 32bytes [16]:ffff881f9649cc80 delta [20]

[ 467.017086] 32bytes [17]:ffff881f9649cca0 delta [20]

[ 467.022483] 32bytes [18]:ffff881f9649ccc0 delta [20]

[ 467.027881] 32bytes [19]:ffff881f9649cce0 delta [20]

[ 467.033286] ------------------

[ 467.036610] 40bytes [0]:ffff881d0c904d40 delta [0]

[ 467.041828] 40bytes [1]:ffff881d0c904680 delta [6c0]

[ 467.047216] 40bytes [2]:ffff881d0c904140 delta [540]

[ 467.052607] 40bytes [3]:ffff881d0c904d00 delta [bc0]

[ 467.058001] 40bytes [4]:ffff881d0c9043c0 delta [940]

[ 467.063399] 40bytes [5]:ffff881d0c904940 delta [580]

[ 467.068801] 40bytes [6]:ffff881d0c9048c0 delta [80]

[ 467.074107] 40bytes [7]:ffff881d0c904e80 delta [5c0]

[ 467.079496] 40bytes [8]:ffff881d0c904200 delta [c80]

[ 467.084888] 40bytes [9]:ffff881d0c904980 delta [780]

[ 467.090282] 40bytes [10]:ffff881fcd725dc0 delta [2c0e21440]

[ 467.096280] 40bytes [11]:ffff881fcd7250c0 delta [d00]

[ 467.101763] 40bytes [12]:ffff881fcd725440 delta [380]

[ 467.107235] 40bytes [13]:ffff881fcd725340 delta [100]

[ 467.112722] 40bytes [14]:ffff881f8398ee80 delta [49d964c0]

[ 467.118633] 40bytes [15]:ffff881f8398ecc0 delta [1c0]

[ 467.124110] 40bytes [16]:ffff881f8398e100 delta [bc0]

[ 467.129589] 40bytes [17]:ffff881f8398ed40 delta [c40]

[ 467.135062] 40bytes [18]:ffff881f8398efc0 delta [280]

[ 467.140542] 40bytes [19]:ffff881f8398e700 delta [8c0]

我們可以看到,32字節的結構體,kmalloc分配的完全都是連續的,而40字節的結構體,完全就散亂碎片化了。

如果以上的這些地址是需要在網絡協議棧的Netfilter hook中被遍歷的,可想而知,如果地址非連續且布局無跡可尋,cache miss將會非常高。

值得一提的是,并不是說32字節的結構體分配就一定會獲得連續的內存,而64字節的就不會,這完全取決于你的系統當前的整體kmalloc使用情況。

kmalloc并不適合快速路徑的內存分配,它只適合穩定的,離散的管理結構體的內存分配。

大家之所以普遍喜歡用kmalloc,因為它簡單,快捷,少了kmem_cache的create和destroy的維護操作。

kmalloc有個副作用,就是它只有固定的大小,比如你分配一個24字節大小的結構體,事實上系統會給你32字節。具體的細節就參考kmalloc的kmem_cache數組吧。

在諸如網絡協議棧處理這種相對快速的路徑中,比如skbuff,sock,nfconntrack等結構體均是在自行維護的獨享kmem_cache中被管理的,這保證了內存分配的盡可能的連續性,盡可能的最少碎片。

這是通過kmem_cache的棧式管理實現的:

kmem_cache的obj可以隨意釋放。

kmem_cache的obj按照釋放的逆序進行分配。

kmem_cache的free相當于push操作,而alloc相當于pop操作。

我再用例子給出直觀的效果,依然采用專家模式的stap:

// alloc_free.stp

%{

#include

struct stub {

unsigned char m[40];

};

%}

function kmemcache_stack_test()

%{

int i;

struct kmem_cache *memcache;

struct stub *array[10];

struct stub *new[10] = {NULL};

memcache = kmem_cache_create("test_", sizeof(struct stub), 0, 0, NULL);

if (!memcache)

return;

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

array[i] = kmem_cache_alloc(memcache, GFP_KERNEL);

STAP_PRINTF("[%d]:%llx ", i, array[i]);

}

STAP_PRINTF("Let's play ");

kmem_cache_free(memcache, array[4]);

STAP_PRINTF("free [4]:%llx ", array[4]);

array[4] = NULL;

new[0] = kmem_cache_alloc(memcache, GFP_KERNEL);

STAP_PRINTF("new [x]:%llx ", new[0]);

kmem_cache_free(memcache, array[1]);

STAP_PRINTF("free [1]:%llx ", array[1]);

array[1] = NULL;

kmem_cache_free(memcache, array[8]);

STAP_PRINTF("free [8]:%llx ", array[8]);

array[8] = NULL;

new[1] = kmem_cache_alloc(memcache, GFP_KERNEL);

STAP_PRINTF("new [x]:%llx ", new[1]);

new[2] = kmem_cache_alloc(memcache, GFP_KERNEL);

STAP_PRINTF("new [x]:%llx ", new[2]);

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

if (new[i]) {

kmem_cache_free(memcache, new[i]);

new[i] = NULL;

}

}

STAP_PRINTF("Batch free ");

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

if (array[i]) {

kmem_cache_free(memcache, array[i]);

STAP_PRINTF("free [i]:%llx ", array[i]);

array[i] = NULL;

}

}

STAP_PRINTF("Batch alloc ");

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

new[i] = kmem_cache_alloc(memcache, GFP_KERNEL);

STAP_PRINTF("new [%d]:%llx ", i, new[i]);

}

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

if (new[i]) {

kmem_cache_free(memcache, new[i]);

new[i] = NULL;

}

}

kmem_cache_destroy(memcache);

%}

probe begin

{

kmemcache_stack_test();

exit(); // oneshot模式

}

很簡單的實驗,就是分配,釋放的操作,我們運行一下:

[root@localhost test]# stap -g ./alloc_free.stp

[0]:ffff88003bc4bf28

[1]:ffff88003bc4bf00

[2]:ffff88003bc4beb0

[3]:ffff88003bc4be38

[4]:ffff88003bc4be88

[5]:ffff88003bc4be60

[6]:ffff88003bc4bdc0

[7]:ffff88003bc4be10

[8]:ffff88003bc4bde8

[9]:ffff88003bc4bd48

Let's play

free [4]:ffff88003bc4be88

new [x]:ffff88003bc4be88

free [1]:ffff88003bc4bf00

free [8]:ffff88003bc4bde8

new [x]:ffff88003bc4bde8

new [x]:ffff88003bc4bf00

Batch free

free [i]:ffff88003bc4bf28

free [i]:ffff88003bc4beb0

free [i]:ffff88003bc4be38

free [i]:ffff88003bc4be60

free [i]:ffff88003bc4bdc0

free [i]:ffff88003bc4be10

free [i]:ffff88003bc4bd48

Batch alloc

new [0]:ffff88003bc4bd48

new [1]:ffff88003bc4be10

new [2]:ffff88003bc4bdc0

new [3]:ffff88003bc4be60

new [4]:ffff88003bc4be38

new [5]:ffff88003bc4beb0

new [6]:ffff88003bc4bf28

new [7]:ffff88003bc4bf00

new [8]:ffff88003bc4bde8

new [9]:ffff88003bc4be88

[root@localhost test]#

從地址上可以看出,kmem_cache就是按照一個棧的形式進行管理的,即便由于隨機的free操作造成了空洞,后續的alloc會盡快將其填充。這樣的結果如下:

盡可能節省內存,保持內存的緊湊。

提高CPU dcache的命中率,最大化preload效果。

即便我們使用自行維護的kmem_cache slab,當從中分配的對象插入鏈表時,也要盡量按照其內存地址的升序插入鏈表確定的位置,這樣在遍歷鏈表時可以達到最大化預取的效果。實測過程這里從略。

一個事實是:

在連續的內存上進行遍歷,其性能遠超在離散的內存上進行遍歷!

這是因為CPU在訪問內存地址P時,會把一個cacheline的數據預取到cache,在連續的內存上,隨著遍歷的進行,鏈表項的訪問和預取將形成一個流水化作業,這個流水線只要不被打斷,遍歷就好像在cache中進行一樣。

我建議,根據slab對象的內存使用hlistaddbefore[rcu],hlistaddbebind[rcu]將對象插入hlist的特定位置,而不是簡單使用hlistaddhead。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    88

    文章

    11758

    瀏覽量

    219005
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    41584
  • malloc
    +關注

    關注

    0

    文章

    53

    瀏覽量

    383

原文標題:Linux內核快速處理路徑盡量多用kmem_cache而慎用kmalloc

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    Linux內核伙伴系統內存申請函數詳解:從原理到實戰

    Linux 內核中,內存管理是整個系統穩定運行的基石,伙伴系統(Buddy System) 作為內核物理內存分配的核心機制,更是驅動開發、內核
    的頭像 發表于 02-10 16:58 ?3630次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>伙伴系統內存申請函數詳解:從原理到實戰

    Linux內核bug狩獵指南:從棧跟蹤到修復,官方文檔教你搞定系統核心故障

    內核Linux 系統的 “心臟”—— 一旦它出 bug,小則功能異常,大則系統崩潰、死機。但內核 bug 往往藏在百萬行代碼中,想快速定位、修復絕非易事。
    的頭像 發表于 02-06 16:59 ?3116次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>bug狩獵指南:從棧跟蹤到修復,官方文檔教你搞定系統核心故障

    Linux內核的“心跳”:jiffies如何為系統計時?

    Linux 內核的世界里,有一個默默工作的 "計時器"——jiffies。它不像我們手機上的時鐘那樣顯示年月日,卻掌控著內核中絕大多數時間相關的操作:從進程調度到設備驅動的定時檢查,都離不開它的身影。
    的頭像 發表于 02-04 16:27 ?813次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>的“心跳”:jiffies如何為系統計時?

    深入RK3588內核:rockchip_linux_defconfig的作用與調試價值

    在 RK3588 芯片的 Linux 開發中,有一個文件始終是開發者繞不開的核心 ——kernel/arch/arm64/configs/rockchip_linux_defconfig。無論是首次
    的頭像 發表于 02-03 15:56 ?1154次閱讀
    深入RK3588<b class='flag-5'>內核</b>:rockchip_<b class='flag-5'>linux</b>_defconfig的作用與調試價值

    Linux系統內核參數調優實戰指南

    Linux 內核參數調優是系統性能優化的核心環節。隨著云原生架構的普及和硬件性能的飛速提升,默認的內核參數配置往往無法充分發揮系統潛力。在高并發 Web 服務、大數據處理、容器化部署等
    的頭像 發表于 01-28 14:27 ?425次閱讀

    【「Linux 設備驅動開發(第 2 版)」閱讀體驗】+讀深入理解Linux內核內存分配

    ,目前4KB是廣泛使用的頁大小。在Linux操作系統中,每個進程甚至內核本身都被分配了地址空間,這是處理器的虛擬地址空間的一部分,內核和進程都不處理
    發表于 01-16 20:05

    【「Linux 設備驅動開發(第 2 版)」閱讀體驗】Linux內核開發基礎

    ,本文介紹Linux內核開發基礎 處理內核的核心輔助函數 Linux內核加鎖機制和共享資源 無論
    發表于 01-12 22:45

    【「Linux 設備驅動開發(第 2 版)」閱讀體驗】+讀內核處理的核心輔助函數

    處理內核的核心輔助函數”進行學習。 第3章又是以5個主題展開討論學習,①、Linux內核加鎖機制和共享資源;②、處理
    發表于 01-10 22:08

    深入Linux內核:進程調度的核心邏輯與實現細節

    ,背后都離不開內核調度算法的精準操控。今天,我們就從優先級、調度算法、時間片分配到底層實現,全方位拆解Linux內核進程調度的核心邏輯。 一、進程調度的“身份標識”:優先級與分類 要理解調度邏輯,首先得搞懂:進程憑什么“插隊”?
    的頭像 發表于 12-24 07:05 ?4299次閱讀
    深入<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>:進程調度的核心邏輯與實現細節

    Linux內核模塊的加載機制

    使用insmod或modprobe命令來加載模塊。insmod是直接加載,modprobe會處理依賴關系。 2、如何工作 那內核模塊具體是怎么工作的呢?當執行insmod時,會調用系統調用
    發表于 11-25 06:59

    Linux內核printk日志級別全解析:從參數解讀到實操配置

    一、開篇:一個命令引出的核心問題 在?Linux?終端執行?cat /proc/sys/kernel/printk,你可能會看到這樣的輸出: 這串數字不是隨機的,而是內核日志系統的“核心配置開關
    的頭像 發表于 11-20 15:54 ?1696次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>printk日志級別全解析:從參數解讀到實操配置

    deepin亮相2025中國Linux內核開發者大會

    11 月 1 日,第二十屆中國 Linux 內核開發者大會(CLK)在深圳舉辦。CLK 作為國內 Linux 內核領域極具影響力的峰會,由清華大學、Intel、華為、阿里云、富士通南大
    的頭像 發表于 11-05 17:59 ?815次閱讀

    Linux內核參數調優方案

    在高并發微服務環境中,網絡性能往往成為K8s集群的瓶頸。本文將深入探討如何通過精細化的Linux內核參數調優,讓你的K8s節點網絡性能提升30%以上。
    的頭像 發表于 08-06 17:50 ?945次閱讀

    如何配置和驗證Linux內核參數

    Linux系統運維和性能優化中,內核參數(sysctl)的配置至關重要。合理的參數調整可以顯著提升網絡性能、系統穩定性及資源利用率。然而,僅僅修改參數是不夠的,如何驗證這些參數是否生效同樣關鍵。
    的頭像 發表于 05-29 17:40 ?1148次閱讀

    樹莓派4 性能大比拼:標準Linux與實時Linux 4.19內核的延遲測試

    引言本文是對我之前關于RaspberryPi3同一主題的帖子的更新。與之前的帖子一樣,我使用的是隨Raspbian鏡像提供的標準內核,以及應用了RT補丁的相似內核版本。對于實時版,我
    的頭像 發表于 03-25 09:39 ?809次閱讀
    樹莓派4 性能大比拼:標準<b class='flag-5'>Linux</b>與實時<b class='flag-5'>Linux</b> 4.19<b class='flag-5'>內核</b>的延遲測試