一個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()進行擴大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM