Why malloc+memset is slower than calloc?
https://stackoverflow.com/questions/2688466/why-mallocmemset-is-slower-than-calloc/
The short version: Always use calloc() instead of malloc()+memset(). In most cases, they will be the same. In some cases, calloc() will do less work because it can skip memset() entirely. In other cases, calloc() can even cheat and not allocate any memory! However, malloc()+memset() will always do the full amount of work.
Understanding this requires a short tour of the memory system.
Quick tour of memory
There are four main parts here: your program, the standard library, the kernel, and the page tables. You already know your program, so...
Memory allocators like malloc() and calloc() are mostly there to take small allocations (anything from 1 byte to 100s of KB) and group them into larger pools of memory. For example, if you allocate 16 bytes, malloc() will first try to get 16 bytes out of one of its pools, and then ask for more memory from the kernel when the pool runs dry. However, since the program you're asking about is allocating for a large amount of memory at once, malloc() and calloc() will just ask for that memory directly from the kernel. The threshold for this behavior depends on your system, but I've seen 1 MiB used as the threshold.
The kernel is responsible for allocating actual RAM to each process and making sure that processes don't interfere with the memory of other processes. This is called memory protection, it has been dirt common since the 1990s, and it's the reason why one program can crash without bringing down the whole system. So when a program needs more memory, it can't just take the memory, but instead it asks for the memory from the kernel using a system call like mmap() or sbrk(). The kernel will give RAM to each process by modifying the page table.
The page table maps memory addresses to actual physical RAM. Your process's addresses, 0x00000000 to 0xFFFFFFFF on a 32-bit system, aren't real memory but instead are addresses in virtual memory. The processor divides these addresses into 4 KiB pages, and each page can be assigned to a different piece of physical RAM by modifying the page table. Only the kernel is permitted to modify the page table.
How it doesn't work
Here's how allocating 256 MiB does not work:
Your process calls
calloc()and asks for 256 MiB.The standard library calls
mmap()and asks for 256 MiB.The kernel finds 256 MiB of unused RAM and gives it to your process by modifying the page table.
The standard library zeroes the RAM with
memset()and returns fromcalloc().Your process eventually exits, and the kernel reclaims the RAM so it can be used by another process.
How it actually works
The above process would work, but it just doesn't happen this way. There are three major differences.
When your process gets new memory from the kernel, that memory was probably used by some other process previously. This is a security risk. What if that memory has passwords, encryption keys, or secret salsa recipes? To keep sensitive data from leaking, the kernel always scrubs memory before giving it to a process. We might as well scrub the memory by zeroing it, and if new memory is zeroed we might as well make it a guarantee, so
mmap()guarantees that the new memory it returns is always zeroed.There are a lot of programs out there that allocate memory but don't use the memory right away. Some times memory is allocated but never used. The kernel knows this and is lazy. When you allocate new memory, the kernel doesn't touch the page table at all and doesn't give any RAM to your process. Instead, it finds some address space in your process, makes a note of what is supposed to go there, and makes a promise that it will put RAM there if your program ever actually uses it. When your program tries to read or write from those addresses, the processor triggers a page fault and the kernel steps in assign RAM to those addresses and resumes your program. If you never use the memory, the page fault never happens and your program never actually gets the RAM.
Some processes allocate memory and then read from it without modifying it. This means that a lot of pages in memory across different processes may be filled with pristine zeroes returned from
mmap(). Since these pages are all the same, the kernel makes all these virtual addresses point a single shared 4 KiB page of memory filled with zeroes. If you try to write to that memory, the processor triggers another page fault and the kernel steps in to give you a fresh page of zeroes that isn't shared with any other programs.
The final process looks more like this:
Your process calls
calloc()and asks for 256 MiB.The standard library calls
mmap()and asks for 256 MiB.The kernel finds 256 MiB of unused address space, makes a note about what that address space is now used for, and returns.
The standard library knows that the result of
mmap()is always filled with zeroes (or will beonce it actually gets some RAM), so it doesn't touch the memory, so there is no page fault, and the RAM is never given to your process.Your process eventually exits, and the kernel doesn't need to reclaim the RAM because it was never allocated in the first place.
If you use memset() to zero the page, memset() will trigger the page fault, cause the RAM to get allocated, and then zero it even though it is already filled with zeroes. This is an enormous amount of extra work, and explains why calloc() is faster than malloc() and memset(). If end up using the memory anyway, calloc() is still faster than malloc() and memset() but the difference is not quite so ridiculous.
This doesn't always work
Not all systems have paged virtual memory, so not all systems can use these optimizations. This applies to very old processors like the 80286 as well as embedded processors which are just too small for a sophisticated memory management unit.
This also won't always work with smaller allocations. With smaller allocations, calloc() gets memory from a shared pool instead of going directly to the kernel. In general, the shared pool might have junk data stored in it from old memory that was used and freed with free(), so calloc()could take that memory and call memset() to clear it out. Common implementations will track which parts of the shared pool are pristine and still filled with zeroes, but not all implementations do this.
Dispelling some wrong answers
Depending on the operating system, the kernel may or may not zero memory in its free time, in case you need to get some zeroed memory later. Linux does not zero memory ahead of time, and Dragonfly BSD recently also removed this feature from their kernel. Some other kernels do zero memory ahead of time, however. Zeroing pages durign idle isn't enough to explain the large performance differences anyway.
The calloc() function is not using some special memory-aligned version of memset(), and that wouldn't make it much faster anyway. Most memset() implementations for modern processors look kind of like this:
function memset(dest, c, len)
// one byte at a time, until the dest is aligned...
while (len > 0 && ((unsigned int)dest & 15))
*dest++ = c
len -= 1
// now write big chunks at a time (processor-specific)...
// block size might not be 16, it's just pseudocode
while (len >= 16)
// some optimized vector code goes here
// glibc uses SSE2 when available
dest += 16
len -= 16
// the end is not aligned, so one byte at a time
while (len > 0)
*dest++ = c
len -= 1
So you can see, memset() is very fast and you're not really going to get anything better for large blocks of memory.
The fact that memset() is zeroing memory that is already zeroed does mean that the memory gets zeroed twice, but that only explains a 2x performance difference. The performance difference here is much larger (I measured more than three orders of magnitude on my system between malloc()+memset() and calloc()).
Party trick
Instead of looping 10 times, write a program that allocates memory until malloc() or calloc()returns NULL.
What happens if you add memset()?
Why malloc+memset is slower than calloc?的更多相关文章
- Linux C 堆内存管理函数malloc()、calloc()、realloc()、free()详解
C 编程中,经常需要操作的内存可分为下面几个类别: 堆栈区(stack):由编译器自动分配与释放,存放函数的参数值,局部变量,临时变量等等,它们获取的方式都是由编译器自动执行的 堆区(heap):一般 ...
- malloc、calloc、realloc函数说明
malloc 函数 #include <stdlib.h> void* malloc(int n); n为要分配的字节数,如果成功,返回获得空间的首地址,如果分配失败,则返回NULL,ma ...
- malloc calloc realloc
三个函数的申明分别是: void* realloc(void* ptr, unsigned newsize); void* malloc(unsigned size); void* calloc(si ...
- Linux中brk()系统调用,sbrk(),mmap(),malloc(),calloc()的异同【转】
转自:http://blog.csdn.net/kobbee9/article/details/7397010 brk和sbrk主要的工作是实现虚拟内存到内存的映射.在GNUC中,内存分配是这样的: ...
- malloc,calloc,realloc
与堆操作相关的两个函数 malloc #include<stdio.h> #include<stdlib.h> #include<string.h> int mai ...
- malloc realloc calloc free
自上次发现自己对这几个C函数不熟悉,就打算抽空整理一下,也就现在吧.这几个函数都是跟堆内存打交道的,还有一个好玩的函数--alloca,它是跟栈内存打交道的,我想留在以后研究出好玩点的来,再专门为其写 ...
- 关于new,delete,malloc,free的一些总结
首先,new,delete都是c++的关键字并不是函数,通过特定的语法组成表达式,new可以在编译的时候确定其返回值.可以直接使用string *p=new string("asdfgh&q ...
- 动态内存管理详解:malloc/free/new/delete/brk/mmap
c++ 内存获取和释放 new/delete,new[]/delete[] c 内存获取和释放 malloc/free, calloc/realloc 上述8个函数/操作符是c/c++语言里常用来做动 ...
- 动态内存管理:malloc/free/new/delete/brk/mmap
这是我去腾讯面试的时候遇到的一个问题——malloc()是如何申请内存的? c++ 内存获取和释放 new/delete,new[]/delete[] c 内存获取和释放 malloc/free, c ...
随机推荐
- Problem C: [noip2016十连测第五场]travel (构造+贪心)
题面 https://www.lydsy.com/JudgeOnline/upload/201610/statements(1).pdf 题解 好神仙的贪心-- 首先无解的情况很容易判断,就是\(l= ...
- 为CentOS下的Docker安装配置python3【转】
* 安装python3以及docker yum install docker docker pull centos service docker start systemctl enable dock ...
- SAP 权限层次
此文可是没有维护过权限的人对权限有一个整体的认知,转来加以记忆. 一是系统权限 主要是对模块为单位的权限划分,具体就是用户对该模块可见不可见,能不能对该模块进行再授权的操作.表现在用户界面就是用户登录 ...
- 关于CSRF攻击详解
CSRF的原理以及防范 CSRF概念:CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解: 攻击者盗用了你的身份,以你 ...
- HDU2066-一个人的旅行
题目链接:点击打开链接 Problem Description 虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子, ...
- Vue-multiselect详解(Vue.js选择框解决方案)
github地址:https://github.com/shentao/vue-multiselect 官网链接:https://vue-multiselect.js.org/#sub-getting ...
- Navicat连接MySQL数据库的一些问题与解决方案
前言 安装MySQL数据库与Navicat并不算难事,关键是怎么让他们工作花费了我整整一天的时间,最终才把弄好.遇到各种各样的问题,上网看了大量博客,发现很多博客都是直接copy或者并不能非常好的解答 ...
- Luogu P3546 [POI2012]PRE-Prefixuffix 神奇的递推+哈希
设$f[i]$表示切掉前$i$位和后$i$位后,即剩下$s[i+1]到s[n-i]$,的公共前后缀长度.此时我们发现,$f[i-1]$相对于$f[i]$少切了两个$char$,所以有$f[i-1]\l ...
- js如何删除json里的值
思路 第一种方法:通过把json中需要的值取出来,重新生成json对象,这种方法比较笨 第二种方法:通过delete 删除属性,这种方法比较常用,在第三方js库中经常能看到,推荐 举例 1 2 3 4 ...
- 3305: Hero In Maze II (优先队列+bfs)
Description 500年前,Jesse是我国最卓越的剑客.他英俊潇洒,而且机智过人^_^.突然有一天,Jesse心爱的公主被魔王困在了一个巨大的迷宫中.Jesse听说这个消息已经是两天以后了, ...