最近踩到 compiler optimization 的一個小坑,gcc5 版本以上,開啟 optimization level 2 時,會將 malloc + memeset 轉換成 calloc。看起來似乎蠻有道理的改善,卻因為我在進行客製化 malloc 時,沒有這注意到這點,導致最後程式執行的是 libc 的 calloc 而不是我的 wrap_malloc。
起因
使用 void *__wrap_malloc(size_t size) 將 malloc function 進行額外的處理 (How to wrap a system call (libc function) in Linux),其中一段 code 為:
| |
但實際上執行時卻不如預期執行到 malloc,dump code 之後發現這段 code 被調整成 calloc。
| |
| |
malloc + memset Optimization
GCC commit: PR tree-optimization/57742 (memset(malloc(n),0,n) -> calloc(n,1))
calloc 看起來結果跟 malloc + memset to zero 一樣,但是根據實作差異,calloc 在某些情況下,效率會較 malloc + memset 好。
舉例來說,在 allocate 指定大小的記憶體空間時,如果這個記憶體空間所對應的 page 是當前透過 mmap 來的,這表示此 page 對應的空間已被清空歸零,因此不需要再逐一清空。
當 allocator 剩餘空間不足以分配指定的記憶體空間,會透過 sysmalloc 的方式,使用 mmap 向系統新申請以 page 為單位的空間,而在呼叫 mmap 時,會帶有
MAP_ANONYMOUS的 flag,這也意味著 page 所有的內容會被初始化歸零。基於安全考量,如果取得沒有初始化的 page ,會導致其他使用此 physical memory 的 process 處理的資料被取得,因此一般情況下都會被初始化,不過如果因為其他因素而不需要初始化,可以透過CONFIG_MMAP_ALLOW_UNINITIALIZEDkernel option 來設定)
除此之外,calloc 在將記憶體初始化歸零時,也可以透過實作來進行效能改善,以 libc 所實作的 calloc 為例:
| |
透過 loop unrolling 的手法來初始化小於 36 bytes 的記憶體空間。
Issues
在查找相關資料的時候,看到一個有趣的討論 Bug 67618 - malloc+memset optimization breaks code,由於此改善,導致有人抱怨 code 出現問題,主要原因是:
| |
即使有 if 條件判斷的情況下,一樣會被調整成 calloc。不過 committer 表示,malloc 本意是分配一個可用的空間,其內容是初始化或是非初始化,都不應影響 code 運行。