一個malloc()->mmap()->memset()效能問題解決過程
關鍵詞:perf、malloc()、mmap()、memset()等。
一個嵌入式專案中啟動4個執行緒,每個執行緒進行浮點數轉換。
在啟動後發現,這幾個執行緒每個佔用率都在15%左右,並且總的CPU耗時user遠小於sys。
1. 現象分析
首先通過top簡單檢視,各個執行緒消耗的CPU情況;總的CPU消耗中user/sys的比例。
top -p xxx -H -b
得出結論:進行浮點轉換的執行緒佔用率高,這個符合預期,但是有點過高,不符合預期;sys消耗要大於user,這個不符合預期。
通過如下命令,分析執行過程中不同執行緒佔用率統計以及特定符號取樣結果。
perf record -a -e cpu-clock -F 1000-D 500 -o /tmp/perf.data -- sleep 30 perf report -i /tmp/perf.data -s pid perf report -i /tmp/perf.data -s symbol
得出結論:CPU佔用率和top吻合;最高的符號是核心中的memset()。
再通過trace-cmd抓取程序切換資料,在kernelshark中進行分析:
trace-cmd record -m 50000 -e sched_switch -e sched_wakeup -s 1000000 -o trace.data & kernelshark perf.data
得出結論:這個單次執行耗時比較大,在4個執行緒同時執行時,相互之間頻繁切換。
綜合得出結論,問題的根源在於浮點轉換執行緒本身,耗時過長,並且核心耗時過長,memset()不應該發生。
2. 程式碼走查
通過分析浮點轉換程式碼,發現能跟核心相關的是malloc()。malloc()的size是動態變化的,申請後進行處理,然後free()釋放。就這樣頻繁週期性處理。
關於malloc(),在libc中如果size大於128KB的時候,就不通過brk(),而是通過mmap()系統呼叫進行記憶體分配。
mmap()相關參考:《Linux記憶體管理 (9)mmap(補充)》《Linux記憶體管理 (9)mmap》。
當malloc()通過mmap()進行記憶體申請的時候,簡要程式碼流程如下:
do_mmap() {
mmap_region() {
kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
}
}
下面重點看看kmem_cache_zalloc(),這裡的z就表示zero。
static inline void *kmem_cache_zalloc(struct kmem_cache *k, gfp_t flags) { return kmem_cache_alloc(k, flags | __GFP_ZERO); } void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) { void *ret = slab_alloc(cachep, flags, _RET_IP_); kasan_slab_alloc(cachep, ret, flags); trace_kmem_cache_alloc(_RET_IP_, ret, cachep->object_size, cachep->size, flags); return ret; } static __always_inline void * slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller) { unsigned long save_flags; void *objp; flags &= gfp_allowed_mask; cachep = slab_pre_alloc_hook(cachep, flags); if (unlikely(!cachep)) return NULL; cache_alloc_debugcheck_before(cachep, flags); local_irq_save(save_flags); objp = __do_cache_alloc(cachep, flags); local_irq_restore(save_flags); objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller); prefetchw(objp); if (unlikely(flags & __GFP_ZERO) && objp) memset(objp, 0, cachep->object_size); slab_post_alloc_hook(cachep, flags, 1, &objp); return objp; }
3.解決問題
所以當malloc()超過128KB之後,通過mmap()申請的記憶體是隱含做了一次清零的操作的。
而恰恰這款晶片的memset()效能很低,是個瓶頸。在每次進行浮點轉換申請釋放記憶體,尤其是操作128B的時候效率極其低,造成CPU佔用率高,並且sys耗時遠高於user。
解決方式是通過申請一個預估最大值的記憶體,然後進行週期性轉換,執行緒退出的時候釋放。
中間如果遇到一個更大的值,通過realloc()進行擴大。