以下皆使用版本 2.0.1 進行測試
dev: development branch for mimalloc v1.dev-slice: development branch for mimalloc v2 with a new algorithm for managing internal mimalloc pages.
根據 github 上指示建構專案:
並透過 sudo make install 安裝到系統上,出現輸出:
但根據在 README.md 中的敘述:
sudo make install(install the library and header files in/usr/local/liband/usr/local/include)
header files 應放在 /usr/local/include 中,而非 /usr/local/lib/mimalloc-${version}/include 當中。
從 CMakeLists.txt 中可以找到以下程式碼:
其中 CMAKE_INSTALL_PREFIX 在 Unix 系統上預設為 /usr/local,所以當 MI_INSTALL_TOPLEVEL 未設置時 (default),cmake 會將的安裝路徑指定為 /usr/local/lib/mimalloc-${mi_version},而不會將 header files 放到 /usr/local/include 中,造成使用者程式中引入 header files 的部份失效。
issue 223 也有討論此問題,但開發者認為此舉會有版本混亂的問題,也就是 /usr/local/include/mimalloc.h 不會標明版本,但此部份應可以透過 CMakeLists.txt 額外撰寫,如 elbaro 的留言:
…
.h - it's good to have versioned headers if you want, but it should be under/usr/include/, not/usr/lib/.I recommend to have versioned filenames and also unversioned symlink.
e.g.
根據 Linux Filesystem Hierarchy 描述:
/usr/include
The directory for 'header files', needed for compiling user space source code.
可知 /usr/local/include 應用來放置 header files。
安裝完的結構長這樣:
其中 /usr/local/lib/libmimalloc.so 為 symbolic link,連結到 libmalloc-2.0 內實際的函式庫 libmimalloc.so.2.0.1。在需要使用 library 時使用 gcc linker 連結的參數 -lmimalloc 會在路徑中尋找 mimalloc.so 檔案做連結。
而在執行的時候,雖然編譯 libmimalloc.so 時有設定 soname 為 libmimalloc.so.2.0,可以透過以下命令查看:
但 ld.so 不會檢查 /usr/local/lib 底下的 shared library,故會找不到該檔案:
可以嘗試以下方法:
rpath 讓 linker 知道位置,gcc -Wl,-rpath,/usr/local/lib/mimalloc-2.0/ myprogram.c -lmimallocLD_PRELOAD=/usr/local/lib/libmimalloc.so ./a.out/usr/local/lib/mimalloc-2.0/ 路徑至 /etc/ld.so.conf,並透過 ldconfig 更新快取/usr/local/lib/mimalloc-2.0/libmimalloc.so.2.0 移動到 /usr/local/lib,並重新建立 /usr/local/lib/libmimalloc.so 的 symbolic link。Ubuntu 預設不會將 /usr/local/lib 加入到 linker 的 search path 中,需手動加入。
準備提交 pull request!
environment: Linux ip-172-31-53-217 5.4.0-1049-aws #51-Ubuntu SMP Wed May 12 21:13:51 UTC 2021 aarch64 aarch64 aarch64 GNU/Linux
| allocator | build | description |
|---|---|---|
| jemalloc |
Image Not Showing
Possible Reasons
|
|
| tcmalloc |
Image Not Showing
Possible Reasons
|
|
| mimalloc |
Image Not Showing
Possible Reasons
|
|
| TBB malloc |
Image Not Showing
Possible Reasons
|
|
| hoard |
Image Not Showing
Possible Reasons
|
inlining failed |
| mesh |
Image Not Showing
Possible Reasons
|
|
| nomesh |
Image Not Showing
Possible Reasons
|
|
| supermalloc |
Image Not Showing
Possible Reasons
|
need to use RTM instruction |
| snmalloc |
Image Not Showing
Possible Reasons
|
|
| rpmalloc |
Image Not Showing
Possible Reasons
|
|
| scalloc |
Image Not Showing
Possible Reasons
|
need -m64 flag |
mesh 和 nomesh 需要額外更改 script 才能 build。
[reference]
建置 bench 需額外指定 Arm 架構,添加 -DAPPLE 能讓產生的 CMakeLists.txt 略過編譯 alloc-test,其需要使用到 SIMD 指令集:
而 lean 預設使用多 CPU 編譯,會有卡住的問題 (原因?),改成單一 CPU 即可
mimalloc 程式碼熱點修改 CMakeLists.txt:
切換到 out/release 目錄,重新建構專案:
執行 mimalloc-test-stress,耐心等待程式結束:
注意,現行目錄應該會多一個檔案 gmon.out,接著可用 gprof 分析:
參考執行輸出: (部分)
-pg 編譯選項,適度重新編譯
執行並收集資訊:
sudo
分析 perf 收集的執行資訊:
需要進一步探究:
mi_page_free_list_extend (src/page.c)malloc_usable_size mi_usable_size (src/alloc.c)mi_page_queue_find_free_ex (src/page.c)_mi_page_free_collect (src/page.c)allocbench is a set of tools to benchmark memory allocators realistically using techniques from Android.
修改 reply.cc,將第 15 行的 DEBUG 數值變更為 false,接著編譯
解開 traces 檔案:
修改 run.sh,在 declare -A allocators 之後,僅保留 [mimalloc] 和 [glibc],並修正 libmimalloc.so 的路徑。
執行:
預期輸出:
madvise(MADV_DONTNEED) 使用案例呼叫 free() 函式後,該指定的記憶體空間將被回收,並移入 free list 中,倘若之後再去存取該記憶體位址,就會遇到 segmentation fault。若我們想釋放某段記憶體位址的內容,就可使用 madvise(addr, len, MADV_DONTNEED),addr 對應的記憶體空間將會被釋放,但該地址不會被 Linux 核心回收而放在 free list 中,也就是說,如果存取該地址,不會遇到 segmentation fault,這樣就能重用該地址。
以下是測試程式碼:
參考執行輸出:
注意看 used 欄位:
malloc 之前,系統佔用 4.0 GiBmalloc 後,系統佔用 5.0 GiB,也就是新增 1.0 GiB 空間madvise 後,系統佔用回到 4.0 GiB,也就是等同呼叫 malloc 之間的記憶體佔用量注意: posix_memalign((void **)&buffer, sysconf(_SC_PAGESIZE), len) 所配置的記憶體空間是 page-aligned,這是為了配合 madvise,後者只接受 page-aligned 地址。
getcpumimalloc 為了支援 NUMA,會頻繁呼叫 getcpu 系統呼叫,這可藉由 restartable sequences (rseq) 來加速,參見 The 5-year journey to bring restartable sequences to Linux
Google 發展的 TCMalloc 也用到 rseq,可見:
mimalloc #315 就討論 rseq 的引入,但尚未有程式碼 (很好的機會)
rseq 目前只提供 cpu id 的存取,而在 mimalloc 中呼叫 getcpu 的時機在於需要取得 numa node id,所以必須要透過 cpu id 額外對應 node id。根據 NUMA man page 可以使用 numa_node_of_cpu 取得 numa id,如以下程式:
以下測試四種方法取得 cpu id 的效能,分別是 glibc 的 getcpu、直接透過 vDSO 呼叫的 getcpu、system call 呼叫的 getcpu 以及 rseq 取得的 cpu:
(syscall 因時間太長超出圖片範圍)
可以看到 rseq 版本的時間最快,與 The 5-year journey to bring restartable sequences to Linux 內的實驗數據相同,而 glibc 為 vDSO 的額外一層包裝 (在 x86-64 當中),故兩者時間差不多,差別在於 glibc 函式呼叫較多層導致時間些微增加。
rseq 加上 numa_node_of_cpu 後的時間比較:
會發現 rseq 因為需要呼叫 numa_node_of_cpu 速度比 glibc 以及 vDSO 的 getcpu 還慢,而後兩者因在執行時會同時取得 node id,便不需要額外再取得 node id,速度上較有優勢。
可以將 mimalloc 中的 syscall 改為使用 vDSO 的系統呼叫。
rseq想法:在 segment 上層新增 segment pool。
這樣 segment 間的 atomic operation 應該可以去除?
__builtin_prefetch 在 linked list 的使用mimalloc 的 src/page.c, src/page-queue.c, src/segment.c 用到 linked list,可藉由 CPU 的 prefetch 指令來加速,可見:
使用 Cachegrind 分析 cache 效益Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
TCMalloc 的原始程式碼也用到這技巧,可見 tcmalloc/tcmalloc/internal/linked_list.h:
單純在 freelist 上添加 __builtin_prefetch 對 cache 並沒有影響。
以 SQLite 為例,引入 MAP_POPULATE 的效能影響
| Before | After | |
|---|---|---|
| Mean | 2.550 | 2.375 |
| StdDev | 0.021 | 0.043 |
The time taken to issue "SELECT * FROM tvshowview" in seconds
isoalloc 也用到 MAP_POPULATE
目前 mimalloc 的測試:
參考輸出:
mimalloc 中主要分配空間為 mi_unix_mmap 函式,嘗試新增 MAP_POPULATE 到其中:
在 mimalloc-bench 上測試:
| major-faults | major-faults (populate) | minor-faults | minor-faults (populate) | |
|---|---|---|---|---|
| cfrac | 0 | 0 | 369 | 65706 |
| espresso | 0 | 0 | 684 | 65693 |
| barnes | 0 | 0 | 15612 | 185511 |
| leanN | 3 | 0 | 122108 | 197477 |
| alloc-test1 | 1 | 0 | 2566 | 65787 |
| alloc-testN | 0 | 0 | 2971 | 65812 |
| larsonN | 1 | 0 | 17958 | 68939 |
| sh6benchN | 0 | 0 | 53165 | 65731 |
| sh8benchN | 0 | 0 | 31688 | 87237 |
| xmalloc-testN | 1 | 0 | 16094 | 65724 |
| cache-scratch1 | 0 | 0 | 230 | 65758 |
| cache-scratchN | 0 | 0 | 271 | 65773 |
| mstressN | 0 | 0 | 187768 | 328171 |
| rptestN | 1 | 0 | 41804 | 65814 |
| major-faults | major-faults (populate) | minor-faults | minor-faults (populate) | |
|---|---|---|---|---|
| cfrac | 0 | 0 | 371 | 2218 |
| espresso | 0 | 0 | 789 | 2206 |
| barnes | 0 | 0 | 15618 | 168386 |
| leanN | 0 | 0 | 125934 | 153145 |
| alloc-test1 | 0 | 0 | 2575 | 6403 |
| alloc-testN | 0 | 0 | 3002 | 16677 |
| larsonN | 0 | 0 | 19182 | 27134 |
| sh6benchN | 0 | 0 | 53427 | 57567 |
| sh8benchN | 0 | 0 | 38250 | 75987 |
| xmalloc-testN | 0 | 0 | 19217 | 20696 |
| cache-scratch1 | 0 | 0 | 240 | 4326 |
| cache-scratchN | 0 | 0 | 283 | 16643 |
| mstressN | 0 | 0 | 268846 | 534053 |
| rptestN | 0 | 0 | 51005 | 94541 |
可以看到在 mimalloc v1.x 中 major page faults 的次數會減少,但是 minor page faults 卻也一起增加,此情況在 v2.x 中也會出現。
mimalloc-bench 會設置 MIMALLOC_EAGER_COMMIT_DELAY=0,如果將 MIMALLOC_EAGER_COMMIT_DELAY 改為預設值 1 的話,minor page faults 的數值則會恢復正常。
而使用 MAP_POPULATE 測試 mimalloc-test-stress,此時 minor page faults 卻能大幅下降。(或許可以新增選項讓 user 決定是否開啟 MAP_POPULATE)
| eager_commit | populate | page-faults |
|---|---|---|
| 0 | No | 2,955,325 |
| 1 | No | 2,943,560 |
| 0 | Yes | 68,053 |
| 1 | Yes | 94,554 |
All bitmaps pages allocated with mmap are passed to the madvise syscall with the advice arguments
MADV_WILLNEEDandMADV_SEQUENTIAL. All user pages allocated with mmap are passed to the madvise syscall with the advice argumentsMADV_WILLNEEDandMADV_RANDOM. By default both of these mappings are created withMAP_POPULATEwhich instructs the kernel to pre-populate the page tables which reduces page faults and results in better performance. … The performance of short lived programs will benefit fromPRE_POPULATE_PAGESbeing disabled.
MADV_DONTNEED 減少記憶體參考 isoalloc 中的 guard page 實作,因 guard page 的內容在程式中不會使用,故可以藉由 MADV_DONTNEED 將之移出記憶體。guard page 對應到 mimalloc 中的部份在 mi_segment_init 內:
(在 mimalloc 1.x 版本則在 mi_segment_protect)
在 mi_os_protectx 中添加 MADV_DONTNEED: