kzalloc()/kmem_cache_zalloc()相对kmalloc()+memset的性能优势探究
问题
除了写法更简洁之外,使用kzalloc()/kmem_cache_zalloc()相比kmalloc()/kmem_cache_alloc()搭配memset()是否还有其他优势?尤其是性能层面?我自己做了性能测试,但二者结果几乎一致。
内核版本:4.18.0-372.19.1.el8_6.x86_64
测试代码用于创建200万个4096字节的slab对象:
#define DATA_NUMBER 2000000 char *data[DATA_NUMBER]; /* kthread */ static int zero_slab(void *arg) { unsigned int counter, i; struct kmem_cache *mp_cache; unsigned long before, after; mp_cache = kmem_cache_create("MP_CACHE", PAGE_SIZE, 0, 0, NULL); if (!mp_cache) { printk(KERN_INFO "Failed to create slab.\n"); return -ENOMEM; } printk(KERN_INFO "Slab MP_CACHE is created.\n"); before = ktime_get(); for (counter = 0; counter < DATA_NUMBER; counter++) { // data[counter] = kmem_cache_zalloc(mp_cache, GFP_KERNEL); data[counter] = kmem_cache_alloc(mp_cache, GFP_KERNEL); if (!data[counter]) goto err_out; memset(data[counter], 0, PAGE_SIZE); } after = ktime_get() - before; printk(KERN_INFO "Time taken in ns (%lu)\n", after); err_out: printk(KERN_INFO "Total objects : %u\n", counter); for (i = 0; i < counter; ++i) kmem_cache_free(mp_cache, data[i]); kmem_cache_destroy(mp_cache); return 0; }
回答
核心优势总结
- 内存安全保障:
kzalloc/kmem_cache_zalloc是内核原生的零初始化接口,能避免手动调用memset可能出现的疏漏——比如分配成功后、memset执行前就误用脏内存,或是memset的长度参数写错导致部分内存未清零,这类问题极易引发诡异的内存访问bug,且排查难度极大。 - 性能的场景化优势:
- 当分配的是伙伴系统刚拿出的全新物理页时,内核已经完成了清零操作,
kzalloc这类接口会直接复用这个干净状态,跳过冗余的memset;而手动调用memset不管内存原本状态都会执行清零,这种场景下kzalloc的效率明显更高。 - 部分架构的内核实现中,
kmem_cache_zalloc会结合slab缓存的批量处理逻辑完成清零,比单独调用memset的单对象清零更高效,尤其是批量分配大量对象时。
- 当分配的是伙伴系统刚拿出的全新物理页时,内核已经完成了清零操作,
- 代码可读性与维护性:原生零初始化接口的意图更明确,看到
kzalloc就能立刻知道是要获取零初始化的内存,无需额外检查后续代码是否有memset操作,降低了代码阅读和维护的成本。
测试结果一致的原因分析
你的测试用例中,slab对象大小为PAGE_SIZE,且分配后会被释放回缓存,再次分配时内存处于非清零状态。这种场景下,kmem_cache_zalloc和kmem_cache_alloc+memset都需要执行清零操作,因此性能表现几乎无差异。如果换用全新物理页的分配场景(比如第一次批量分配大量对象,或是给slab缓存添加SLAB_POISON标志让释放的对象被标记为脏数据),就能看到两者的性能差距。
内容的提问来源于stack exchange,提问作者MankPan
相关产品推荐
相关产品推荐

