• 一个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()进行扩大。

  • 相关阅读:
    codeforce 1B
    codeforce A. Accounting
    20145208 《Java程序设计》第9周学习总结
    20145208 实验三 Java面向对象程序设计
    20145208 《Java程序设计》第8周学习总结
    20145208 《Java程序设计》第7周学习总结
    20145208 实验二 Java面向对象程序设计
    20145208实验一 Java开发环境的熟悉
    20145208 《Java程序设计》第6周学习总结
    20145208 《Java程序设计》第5周学习总结
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/13402311.html
Copyright © 2020-2023  润新知