Why does calloc exist?
vorpus.org
vorpus.org
buf = calloc(huge, huge);
if (errno) perror("calloc failed");
printf("calloc(huge, huge) returned: %p\n", buf);
free(buf);
This has a flaw. errno doesn't magically get reset to zero. You should check the return value of calloc, then use errno. Checking if(errno) is not the right way to determine if there was an error. Its [errno's] value is significant only when the return value of
the call indicated an error (i.e., -1 from most calls; -1 or NULL
from most library functions); a function that succeeds is allowed
to change errno.If you write a program that relies on this behaviour you're going to have a hard to track down bug at some point.
The code should be something like this:
buf = calloc(huge, huge);
if (!buf)
perror("calloc failed");
printf("calloc(huge, huge) returned: %p\n", buf);
free(buf);Here's an early implementation: https://github.com/dspinellis/unix-history-repo/blob/Researc...
Of course, there are plenty of systems where this doesn't happen, or there are no pages in the first place, or there's no kernel zeroing stuff for you.
That tells your cache "pretend that you read all zeroes into the cache line at this address, and mark it as dirty (that guarantees the zeroes will be written out, even if the caller doesn't write to the cache line)
For small amounts of memory that will be written to soon, that's as good as free since it doesn't have to read from memory (the naive loop will, as it has to bring in an entire cache line before it can zero out its first byte or word)
Here's the earliest implementation in that repo (in Research UNIX V6; your link in V7): https://github.com/dspinellis/unix-history-repo/blob/Researc...
calloc(n, s)
{
return(alloc(n*s));
}
There are several interesting things we learn from poking around V6 though:- `calloc` originated not on UNIX, but as part of Mike Lesk's "iolib", which was written to make it easier to write C programs portable across PDP 11 UNIX, Honeywell 6000 GCOS, and IBM 370 OS[0]. Presumably the reason calloc is the-way-it-is is hidden in the history of the implementation for GCOS or IBM 370 OS, not UNIX. Unfortunately, I can't seem to track down a copy of Bell Labs "Computing Science Technical Report #31", which seems to be the appropriate reference.
- `calloc` predates `malloc`. As you can see, there was a `malloc`-like function called just `alloc` (though there were also several other functions named `alloc` that allocated things other than memory). (Ok, fine, since V5 the kernel's internal memory allocator happened to be named `malloc`, but it worked differently[1]).
[0]: https://github.com/dspinellis/unix-history-repo/blob/Researc... (format with `nroff -ms usr/doc/iolib/iolib`)
[1]: https://github.com/dspinellis/unix-history-repo/blob/Researc...
https://github.com/openbsd/src/commit/c7b2af4b3f7e78424f8943...
https://github.com/bminor/glibc/commit/0950889b810736fe7ad34...
The standard reads as follows:
void *calloc(size_t nmemb, size_t size);
The calloc function allocates space for an array of nmemb objects, each of whose size is size.[...]
and, The calloc function returns either a null pointer or a pointer to the allocated space.
So if it cannot allocate space for an array of nmemb objects, each of whose size is size, then it has to return null pointer.[0] https://github.com/dspinellis/unix-history-repo/blob/Researc...
[1] https://github.com/dspinellis/unix-history-repo/blob/Researc...
[2] https://github.com/dspinellis/unix-history-repo/blob/Researc...
Yes, calloc is used in lex, but that is not part of v6...at least not the official distribution, I don't know when he started development. But since he also uses fopen and friends why shouldn't he be using malloc as well? changing 'calloc(n, m)' to 'malloc(n*m)' doesn't sound like such a huge change.
[1] http://www-01.ibm.com/support/docview.wss?uid=isg1OA28314
On the flip side, if your critical metric is latency then these tricks of calloc's and the OS's are exactly what you try to avoid. memset() the buffer, and if you have the privileges you should mlock() it to prevent it from being paged out. Of course, this all presumes that it's not an ephemeral buffer to begin with. Best to change your design to leverage a long-lived resource if possible.
Best to change your design to leverage a long-lived
resource if possible.
On the flip side, if your critical metric is latency
then these tricks [...] are exactly what you try to avoid
If you keep the buffer alive as long as possible with a slab allocator, or just smart/good memory management strategy. How you acquire the buffer will ultimately be trivial, likely dwarfed your other startup tasks (reading config, opening sockets, etc.)In a real-time system, the start-up time matters less than having predictable response times.
Hard Realtime: Embedded system, no virtual memory/OS. Or special OS provisions to let them run.
Soft Realtime: Responsive.
In the case you are aiming for the second. There are several million things that'll net greater performance. We're talking about saving a matter of nano-seconds in C/C++. How you load your config will have more effect then this.
If you want to save $1,000,000 rolling pennies a start. But there are likely way bigger savings elsewhere, worth way more time, and less effort.
Hard – missing a deadline is a total system failure.
Firm – infrequent deadline misses are tolerable, but may degrade the system's quality of service. The usefulness of a result is zero after its deadline.
Soft – the usefulness of a result degrades after its deadline, thereby degrading the system's quality of service
You've created a false dichotomy.
If you depend on copy-on-write functionality, then you need to use an API that is specced to guarantee copy-on-write functionality. If that means you use an #ifdef per platform and do OS-specific stuff, then that is what you do.
Anything else is amateur hour.
If copy-on-write is a desirable feature, then as the API creator, your job is to expose this functionality in the clearest and simplest way possible, not to hack it in obscurely via the implementation details of some random routine. (And then surprise people who didn't expect copy-on-write with the associated performance penalties.)
This is why we can't have nice things.
If you really care, then you actually profile your system and see what takes how much time, under which circumstances. The results of such a profile are almost always surprising.
I guess this is a basic cultural difference -- almost nobody in the HN crowd really cares whether their software runs quickly; there is just a bunch of lip service and wanting-to-feel-warm-fuzzies, with very little actual work.
In video games (for example) we need to hit the frame deadline or else there is a very clear and drastic loss in quality. This makes this kind of issue a lot more real to us. If you look at the kinds of things we do to make sure we run quickly ... they are of a wholly different character than "guess that calloc is going to do copy-on-write maybe."
(In fact we go out of our way to not do malloc-like things in quantity unless we really have to, because the general idea of heap allocation is slow to begin with.)
The calloc() function contiguously allocates enough
space for count objects that are size bytes of memory
each and returns a pointer to the allocated memory.
The allocated memory is filled with bytes of value zero.
You should not care how it does it.You first write your code using standard system functions, using the right calls for what you're doing. If after that performance of the code is bad because of calloc() only then you roll out your own implementation (most likely in assembly), and accept that in the future your code might not work well, because something in your OS has changed since you wrote your code.
Choosing whether to use malloc vs calloc is not an architectural change though, and in fact it is very easy to replace one with the other, but if you use the right call for right use case, then you will benefit from optimizations that the OS provides, and often you might not even be able to achieve them from user space.
Rule of thumb: if you need to allocate memory region that will be overwritten anyway (for example reading a file) use malloc(). If you need a zeroed memory, use calloc().
As long as you rely on guarantees provided by the calls and use the right call for right use case you get predictability and very often optimization.
Here's the specification of calloc from the ISO standard:
7.22.3.2 The calloc function
Synopsis
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
Description
The calloc function allocates space for an array of nmemb objects, each of whose size is size. The space is initialized to all bits zero.
Returns
The calloc function returns either a null pointer or a pointer to the allocated space.The article says you should use calloc because it provides these optimizations. I am saying no, that's goofy, because it is not specced to provide these optimizations.
That specification gives the implementation exactly two options: return NULL, or return a pointer to a block of sufficient size.
Such as writing the optimizing compilers that make it feasible for you to use C at all?
There should be a way to explicitly demand the optimization instead of relying on the behavior of a specific compiler to implicitly optimize the code.
Shifting beyond the bitwidth is undefined behavior. Why can't shifts be checked by default. Let me explicitly demand the undefined behavior when I really need it.
FYI optimizing compilers are also used to produce efficient instruction streams.
I'm agreeing with you here - if your intent is to have copy-on-write functionality, that's not what you're communicating when you use calloc.
It's okay for an implementation to try to optimize given the constraints of intent, but I agree that if an implementation is doing something non-straightforward (copy-on-write in this case), that is a smell that perhaps the API needs to expose an additional layer.
No version of Research UNIX V1 through V7, nor any of BSD 1, 2, 3, 4, or 4.4 did overflow checking. They all just did `m * n` or `m *= n`.
Like reallocarray(3) does?
buf = malloc(x * y);
// becomes
buf = reallocarray(NULL, x, y);
newbuf = realloc(buf, (x * y));
// becomes
newbuf = reallocarray(buf, x, y);Many open source projects include their own, or simply bundle the OpenBSD implementation:
* mandoc
* flex
* unbound and nsd
* tor
* tmux
* libbsd
* libressl
* xorg-xserver
* ...
The list only continues to grow, several more examples to be found on GitHub.reallocarray() has some difficulties as an interface, mostly inherited from realloc(). I'm a bigger fan of reallocarr(), but that's NetBSD only. We (the operating systems community) need to find a consensus here, but I'm not convinced that reallocarray() is that consensus yet.
reallocarr changes the semantics. Equally easy in new pieces of code and a little harder when converting existing code.
#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
void *reallocarray(void *optr, size_t nmemb, size_t size) {
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size) {
errno = ENOMEM;
return NULL;
}
return realloc(optr, size * nmemb);
}
[0] https://github.com/openssh/openssh-portable/blob/master/open...https://bugzilla.redhat.com/show_bug.cgi?id=1293976 http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-5229
Ooh, nice one. My first impression was that calloc was just lazy-allocating, which is fine in most cases but when you want precise control over timing, maybe you want to be sure that memory is zero'd at allocating time rather than pay the cost unexpectedly at use time.
But the cache-awareness makes that a moot point. You'd be paying double cache-eviction costs if you were clearing that memory up front: once at clearing time, and once at actual-writing time. This implementation of calloc avoids that.
Also, I wonder if zeroing large chunks of memory would be faster to do in kernel space using real addresses. You can avoid the multiple real memory lookups involved in a single virtual write.
(Of course, we already avoid those often, but it could be useful to avoid entirely. Not sure what the tradeoffs are here)
(The kernel's linear mapping of physical memory can take advantage of huge pages though, which means that there might be one or two less levels of page tables involved with those addresses). TLB misses aren't significant if you're bulk-writing to a block of memory anyway, you'll max out the bandwidth of the memory without that being an issue.
glibc's is all in assembly full of SIMD instructions, which seem very much magical...
https://sourceware.org/git/?p=glibc.git;a=tree;f=sysdeps/x86...
http://stackoverflow.com/questions/8858778/why-are-complicat...
With malloc + memset, the OS will likely allocate that memory in huge pages, on PC that would be 2-4MB / page depending on the architecture, https://en.wikipedia.org/wiki/Page_(computer_memory)#Huge_pa...
If I calloc then write, the OS can’t give me huge pages because of that copy on write thing. Instead, the OS will gradually give me the memory in tiny 4kb pages. For large buffers you should expect TLB cache misses, therefore slowing down all operations with that memory.
If substantial count of people will read this article, believe what’s written, and [re]design their software under the wrong assumption calloc returns sparse copy-on-write memory buffer at no performance cost — the OSes will no longer be able to use huge pages for a calloc. Because doing that would dramatically increase physical memory usage for such software.
moarmem(n) // malloc(n)
moarmemslower(n) // p = malloc(n); memset(p, 0);
moarmemfaster(n) // calloc(n, 1)
evenmoarmem(p, n); // realloc()
fuggetaboutit(p) // free()malloc will generally do lazy copy-on-write above a certain limit; it was 128kb for glibc last time I checked.
> why can't I have an error checking malloc()
reallocarray is becoming the de-facto standard for that.
> why can't I have a calloc() that allocates the memory up front and doesn't zero it out?
Er, malloc?
reallocarray(3) (though fundamentally that's what malloc does as well, it just doesn't do overflow checking)
> why can't I have an error checking malloc()
reallocarray(3) (because malloc doesn't get the information, it gets a single size rather than a count and a per-object size)
> why can't I have a calloc() that allocates the memory up front and doesn't zero it out?
reallocarray(3)
> Sounds like what you want to do practically is basically just make your malloc() wrap a calloc() with size 1, and stop explicitly memset()ing.
If you're going to memset(0) it there's no reason to use malloc() ever, but it's very common to malloc() then fill the allocation directly, in which case the memsetting is a redundant cost.
Why so ?
(i.e., don't use a professional-grade band-saw, if you're not a professional)
What this doesn't express is that dealing with page allocation directly can be quite annoying to get correct cross platform. You generally don't want to do that unless a) you're optimizing past the "knuth level" and know you need to for performance (e.g. mapping files to memory), b) you're writing something where you run dynamic code (JIT or dynamic recompilation) or c) you're writing your own allocator and/or using page faults to get some functionality, ala Go's stop-the-world hack.
Basically, don't bypass _alloc unless you have a reason.
(Despite the syntax, the same goes for operator new in C++! Placement vector new in particular is a trap.)
struct S {
long a;
char b;
};
On my computer (64-bit Mac), sizeof(struct S) is 16, due to 7 bytes of padding after b. Since the compiler handles the padding, that means calloc doesn't have to.The zero'ing is done significantly for security reasons anyway. If a program could somehow disable that feature and get leftover memory from the kernel it could very easily contain password, secret keys, and other important bits of data that you wouldn't want random programs on your computer to have access to.
On modern server/desktop/mobile CPUs, this won't make much difference anyway because the second write to the same location is essentially 'free' due to the store buffer.
And of course if you're calling calloc() in a tight loop, then the zeroing is the least of your performance concerns!
If you have a calloc, even along conditional paths, it will understand the value along that path is zero until the store, and that the zero is killed by the store.
What it will do varies, because it does not want to screw up the sparse memory usage.
But, for example, it even understands how to take the zero values + stores and turn them into a memset and kill the stores, etc.
It generally does not do something like split the allocation into a calloc and malloc+memset part, or whatever
My turn: A file explorer is part of the OS because it is installed by default on Windows. A good OS provides a graphical file browser.
For example, on Solaris and various *BSDs, the syscall interface is private or unstable and is explicitly NOT an interface.
So without the standard library, none of the applications could run.
That sure sounds like part of the operating system to me, and fits various accepted definitions such as the one Wikipedia provides.
And yes, I would consider the file explorer included with Windows part of the operating system, as most people I think would also.
The confusion here seems to come from the Linux world, where components are mix and match; where you can pair the kernel with an entirely different base.
That isn't true of many other operating systems; the base distributed set is designed to work together and provide the environment and platform for applications.
But if you consider a file browser as being part of the OS, I can't help you. You want to adopt a view that profanes have in a technical discussion. That's worse than ignorance.
The original claim was the standard library is not part of the OS. However, the standard library is objectively part of the OS on many platforms and so the original claim is factually incorrect.
My so-called "view" reflects the industry accepted definition of an OS, so perhaps you should review your suppositions.
Darwin, as shipped by Apple is another.
There are a variety of embedded OS' that are the same.
So yes, as shipped and delivered, you can't replace the standard library and the vendors both consider the standard library as part of the OS.
And furthermore, existing application binaries for those platforms would not work without them.
Err...mmap(2)?
The article suggests that Malloc + Memset is slower than Calloc.
Malloc will be faster depending on your use case. If your plan is to eventually call memset. Then just use Calloc, otherwise malloc will be faster all the time.
How so? C is low level, so to be secure you must be fully aware of the behaviours and side effects of what you're doing. In another, perhaps higher level language, sure, there may be less of these gotchas but to be properly secure you need a similar amount of knowledge about background behaviour.