日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

調用malloc分配內存大概是微秒級別,高并發低延遲系統的關鍵路徑上,要慎用malloc/new,特別是在線程數量很大的情況下。

 

給一個測試數據:linux 64位系統,標準庫malloc,單線程,gcc開O3優化,分配的size在4M以下隨機,平均每次分配大概0.1-3微秒,具體數值跟分配行為有關,跟分配后是否free有關。

 

多線程下的malloc性能開銷,我沒有測,應該會比單線程下差很多很多。

 

微秒級的執行時間是什么概念?一般而言,簡單的函數調用,里面做個加減乘除+拷貝幾十個字節+邏輯判斷,應該是幾十個納秒級別,由此可見,malloc/new調用是比較慢的。

 

我們來看看doris是怎么做內存管理的,推測這個方案是從某個開源庫借鑒(chao)過來的,any way,性能不錯,值得研究。

 

Doris內存管理分三層

系統配置器(system_allocator)

  • 封裝系統/標準接口,提供allocate/free接口
  • allocate(size)根據size調用posix_memalign()或者mmap()
  • free()接口調用munmap()或者free()
  • 會在分配的時候做內存對齊。

塊配置器(chunk_allocator)

  • 塊(Chunk):通過system_allocator::allocate接口分配的內存塊,包含內存塊首地址指針、尺寸、core_id等信息
  • 為每個CPU core維護一個chunk_arena
  • 每個chunk_arena包含一個chunk_list
  • chunk_list為每個size維護一個該size的chunk集合
  • 為了減少各種size的數量,只維護固定size的chunk集合,比如8、16、32、64、128、256...,所以如果分配請求的大小是34字節,那么會向上圓整到64
  • 塊配置器會使用系統配置器分配/回收內存
  • 塊配置器是單件(唯一實例)

內存池(MemPool)

  • 對外提供allocate()、clear()、free_all()等接口
  • 維護通過allocate接口分配的ChunkInfo的列表,ChunkInfo在Chunk上增加了一個已分配字節數
  • 內存池會通過塊分配器分配大塊,每次分配的大塊的大小會按X2(策略決定)增加,從而確保不會頻繁調用塊分配器的allocate接口
  • 通過內存池的allocate接口分配的內存,不支持單個塊free,只支持統一釋放:free_all()
  • clear()接口支持內存復用

 

三者之間的關系如下

 

system_allocator的作用

屏蔽了動態內存管理相關的底層系統調用和標準C/Posix編程接口

  • 如果單次申請的chunk size大于某個閾值,那就調用mmap/munmap
  • 否則調用posix_memalign
  • 上層應用不再直接調用底層API,而是調用system_allocator封裝的編程接口:allocate/free

 

chunk_allocator是怎么工作的?

chunk_allocator是system_allocator的上層,會使用system_allocator的allocate/free接口申請和回收內存塊。

 

chunk_allocator是MemPool的下層,提供allocate和free接口供MemPool使用。

 

chunk_allocator主要是減少了多線程競爭,chunk_allocator維護core_num個ChunkArena對象,該對象內維護一個chunk_list,為size=2^n的每個塊維護一個free list,內存申請的時候,會對請求的size向上圓整。

 

因為每個core都有一個ChunkArena對象,所以上層應用代碼申請內存的時候,先獲取代碼正在哪個核上執行,從而找到對應的ChunkArena對象,再通過size找到對應的free列表,再從該free list上摘除一個塊。

 

多個邏輯線程依然可能調度到同一個核上執行,雖然多個線程不會在一個核上同時執行申請動態內存,但多個線程在一個核上交錯執行(申請內存)的情況,依然會引發對free list的數據競爭(雖然這種情況出現的概率很小),這時候只需要用test_and_swap原子操作不停嘗試就行了,如果嘗試一定次數還不成功,則執行線程主動yield,讓出CPU,從而讓另一個在該核上執行內存分配的線程有機會繼續執行,進而修改atomic_flag,然后之前yield CPU的線程被重新調度執行。

 

TAS(test and swap)是很快的,且沖突概率變得非常小(因為每個核都有一個atomic_flag,不會所有線程競爭一個鎖),這樣的免鎖設計,讓分配內存變得很高效。

 

chunk_allocator也做了一層cache,通過chunk_allocator::free釋放的內存塊,并不一定會真正調用底層的free,只在預留size超過限額的情況下,才會調用system_allocator的free(),這樣進一步減少了對系統底層動態內存管理相關API的調用。

 

chunk_allocator是單件,唯一實例。

 

MemPool設計

咱們進一部分分析MemPool的設計,先給一張MemPool的圖:

MemPool設計

MemPool的作用

內存池在system_allocator/chunk_allocator/MemPool的層次結構中,位于頂層,它依賴于下層chunk_allocator,間接依賴system_allocator,下層的類不反向依賴于MemPool。

 

先說Chunk和ChunkInfo。

 

Chunk就是底層接口單次分配的內存塊,Chunk持有內存塊首地址data,內存塊大小size,以及分配的時候執行線程在哪個core上執行。

 

ChunkInfo包含Chunk,同時多了一個int allocated_size,這是因為,為了減少對
system_allocator::allocate()的調用次數,所以單次分配的chunk會比較大,幾K,幾十K,甚至XX M(兆),這個大的size記錄在chunk->size上。但是,上層應用一次分配的內存可能比較小,幾十字節之類,所以,該chunk還有多少字節可用(已經使用了多少字節),需要有一個記錄,這就是allocated_size,相當于一個游標,每次從該chunk分配x字節,那就把allocated_size這個游標往增長的方向移動x字節(實際上會考慮到對齊)。

 

所以,對
system_allocator::allocate()的調用,相當于批發進貨。對MemPool::allocate()的調用,相當于零售。效果上,就是減少了底層API的調用頻率,減少了多線程競爭

 

MemPool持有一個next_chunk_size,它表示下次調用ChunkAllocator分配接口allocator的時候,需要分配多大,它被初始化為4K,下次分配的時候,會增加到8K,當然如果下次申請的size大于8K,則會取max。

 

next_chunk_size會一直增加,直到觸達最大配置值,這樣的設計,目的還是為了減少底層分配次數。

 

每次ChunkAllocator::allocate()都會返回一個Chunk,進而包裝為ChunkInfo,被MemPool管理起來,所以MemPool會有多個ChunkInfo,用chunk_index標識chunk。

 

MemPool記錄一個current_chunk_idx,這個idx記錄了上次成功分配的ChunkInfo,下次分配的時候,先從current_chunk_idx指向的chunkInfo里嘗試分配,如果該ChunkInfo的剩余內存空間不夠,則會查找其他ChunkInfo,直到找到能滿足分配請求的ChunkInfo,如果現有的所有ChunkInfo都不滿足,那就走ChunkAllocator的allocate,并把新申請的Chunk,放入ChunkInfo list。

 

MemPool不支持單次分配的內存free,但是支持free_all,這會free該MemPool的所有Chunk。

 

MemPool::Clear()接口不會真正free Chunk,而是會重置allocated_size,復用原內存chunk。

 

一個細節,關于ChunkAllocator,分配的時候,會首先從線程運行的core上的ChunkArena分配,如果沒有合適的,會從其他Core的ChunkArena里分配,再分配不到,才會從system_allocate,這樣做的目的,是減少內存cache量。

 

我們做內存池有幾個目標

  1. 吞吐,吞吐越大越好,能滿足各種不同size,各種內存分配場景的大吞吐最好。
  2. 提高存儲空間利用率,千方百計減少碎片(內碎片+外碎片,不懂請補課)。
  3. 為了提高速度,我們經常要做cache,但是cache多了,會造成寶貴的內存資源的浪費,所以,需要balance。
  4. 最后,非常重要的一點,提高cache利用率。

 

大家可以結合以上幾點,慢慢體會該內存池的方式,是如何做到的。

 

很多人會質疑內存池的必要性,我只能說,如果線程很多,并發很大,時延要求也高,那可能真的需要加這么一層,不信你可以去測試一下。

 

不過,所有的方案都有缺點都有優點,都需要通用性,專用性,性能,效率,內存利用率等各個方面做出權衡,要結合業務,結合上層代碼來定制。

 

Nginx,clickhouse的內存管理方案也不錯,讀者有興趣可以去找來看看。

分享到:
標簽:延遲
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定