4 ms·
> When a user program requests to read a file, the page from the file is (usually) first put into the buffer cache. Then the data is copied from the buffer cach
by labawi 6y ago
> When a user program requests to read a file, the page from the file is (usually) first put into the buffer cache. Then the data is copied from the buffer cache out to the user-supplied buffer during the return from the system call.
Doesn't linux do shared-read-only mapping of pages on read(2) and only copy if you modify? Though it can only do so on page-aligned buffers. Maybe results would be different if the buffers were aligned?
> So I changed my test to invoke mmap with MAP_POPULATE and learned that the experiment completes about 36% faster.
If your result spuriously change by 36%, you don't really have a robust statistic, and a model/understaing of objects measured. AFAIU, mmap with MAP_POPULATE should be equivalent to a read of whole file in a single call, on an aligned buffer - significant performance differences here would be interesting.
- kentonv 6y ago> Doesn't linux do shared-read-only mapping of pages on read(2) and only copy if you modify? Eh? Do you have a reference for this? I'd be pretty interested to learn more. This sounds like a very weird optimization to me, because, as you point out, it would only work if your buffer (and file offset) was page-aligned. That doesn't happen by accident, so "normal" programs that use read() would almost never get a benefit. To get guaranteed page alignment, you probably have to use mmap() to allocate the buffer in the first place, so... why wouldn't you just mmap the file, then? In fact, you could arguably translate a page-aligned pread() into mmap() in userspace, using MAP_FIXED to force the mapping to be placed at your buffer address. No need for kernel help? So why would a kernel implement this optimization? It seems like a lot of risk (of unexpected side effects) with little potential benefit.
- skbpush 6y agoA small jumping point that you can start from. https://en.wikipedia.org/wiki/Copy-on-write https://en.wikipedia.org/wiki/Copy-on-write The only reason I remember is from first encountering this in code and wondering what the hell the "COW" flag was. >To get guaranteed page alignment, you probably have to use mmap() Most modern implementations of malloc will call mmap in the background.
- kentonv 6y agoYes, I know what copy-on-write is. You can get copy-on-write semantics by passing MAP_PRIVATE to mmap(). So, again, you could literally replace your pread() calls with mmap() calls with MAP_FIXED and MAP_PRIVATE and get the described optimization, without kernel help -- so long as your buffer is page-aligned. If your buffer isn't page aligned, then no such optimization is possible in either the kernel or userspace. So the question is: Why would the kernel implement this optimization if (1) programs that aren't explicitly designed for it (by using page-aligned buffers) won't get the benefit, and (2) programs design for it could implement the same optimization in userspace anyway? > Most modern implementations of malloc will call mmap in the background. Well of course they do. But malloc() won't promise you a page-aligned address, regardless of how it's implemented. If you need a page-aligned address you have to call mmap() directly. (Or I suppose you could malloc() one more page than you need, and then find the page-aligned subset, but it's a lot easier to just call mmap()...)
- tedunangst 6y ago1. It's not too hard to page align a buffer. If you malloc a big buffer, almost certainly page aligned anyway. So requires very little effort. 2. You can try the same with mmap, but large files won't fit in (32 bit) memory, and read() is cheaper than mapping and unmapping as you go.
- kentonv 6y ago> If you malloc a big buffer, almost certainly page aligned anyway. Not necessarily. Remember that malloc() must produce a pointer which can later be passed to free() without any other details. free() needs some way to figure out, at the very least, how big the allocation is, to properly free it. Many allocators, such as glibc's, accomplish this by placing metadata in the bytes immediately before the allocated block -- which implies that a large allocation won't be page-aligned. Some other allocators, such as tcmalloc, manage to place the metadata somewhere else, and in that case maybe they return page-aligned buffers, but it's hardly safe to assume. (I just ran a quick test and verified -- a 4MB malloc() under glibc is typically 16 bytes off of the page boundary, but under tcmalloc it is aligned.) > 2. You can try the same with mmap, but large files won't fit in (32 bit) memory, and read() is cheaper than mapping and unmapping as you go. This thread is discussing the claim that the Linux kernel transparently "optimizes" large read() calls into mmap(), with me arguing that doesn't make a lot of sense as an optimization. If you're saying that this "optimization" would actually be slower, then that supports my point. You may be right. munmap() (or remapping with MAP_FIXED) triggers TLB shootdowns, which are rather expensive in multithreaded programs. This is another reason why "optimizing" read() to mmap() sounds awfully dubious to me. That said, shootdowns would have a constant cost per read(), independent of the size. For a sufficiently gigantic read, the cost of the shootdown would be less than the cost of copying data into the target memory, so the "optimization" would possibly start to make sense at that point?
- labawi 6y agoYou are likely correct. I knew you could do zero-copy, but it seems special syscalls are necessary for the stated reasons. Calls like mmap, splice, vmsplice, tee, sendfile, io_uring, ..
- scottlamb 6y agoIt looks like the mmap call is not within the timed region in do_mmap_test. I'd call that a major flaw in the test methodology when combined with MAP_POPULATE and a cold cache. The read syscall version has to actually do IO in the timed region; the mmap(MAP_POPULATE) version just does TLB misses and memcpy. edit: actually, sorry, I think the article points this out, mostly correctly: > We will come back to __memmove_avx_unaligned_erms in a moment, but for the time being let’s try to figure out exactly how much time is spent mapping pages. I have a neat trick up my sleeve to do that. On Linux, the mmap system call can accept a MAP_POPULATE flag. What this flag does is it forces mmap to pre-populate all the page mappings during the actual system call, so none of the page-mapping work would be done when my test actually runs. So I changed my test to invoke mmap with MAP_POPULATE and learned that the experiment completes about 36% faster. (I only measure the timing of the main loop, and not that of the mmap system call). Therefore, I assume that in the above profile all those mapping functions take up about 36%. They didn't point out that it doesn't actually do any IO in the main loop either then, but maybe they were only running the warm cache variant by here anyway. And it seems like they didn't use MAP_POPULATE when generating the comparison graphs earlier in the article.
- jschwartzi 6y agoMethodologically it makes sense to exclude work done in the mmap call. After all you would do that once to initialize the memory but you never need to call it to do the work. The point of the test wasn't to check performance of the mmap syscall versus the read syscall, but simply to check the performance of read() versus memory-mapped files.
- scottlamb 6y agoTo check the performance of read() versus memory-mapped files with a cold cache, you should absolutely be including the actual IO. But on my closer read, they did that earlier in the article. Here they are examining the (userspace) memcpy in isolation. MAP_POPULATE is just a tool to help with that. They could get a similar effect by just looking at the second (on to Nth) call to do_mmap_test, not the first. Or just copying from heap memory to heap memory, without involving any kernel stuff at all...
- deleted 6y ago[deleted]
- elihu 6y ago> Doesn't linux do shared-read-only mapping of pages on read(2) and only copy if you modify? No, I don't think that's how it works for the plain read() system call. (The kernel will have its own page cache / buffer cache entries that would be the same for both processes, but read() can't map those directly, it has to make a copy.) Though it should work the way you describe if multiple processes mmap the same file, in which case the page cache pages are exactly the same pages that are mapped by the processes. However, if you use read() to copy data into a buffer and then fork() the process, I believe the child process should end up with a copy of the buffer that is backed by the same physical pages with copy-on-write semantics.
- asveikau 6y ago> Doesn't linux do shared-read-only mapping of pages on read(2) and only copy if you modify? Though it can only do so on page-aligned buffers. Maybe results would be different if the buffers were aligned? Page table modifications slow down your program and many read(2) calls are short and frequent. I doubt that would be a helpful optimization unless the buffer sizes are big.
- eloff 6y agoSource? I've never heard of this. What I have heard of is Linus ranting and insulting the intelligence of BSD kernel developers doing these kinds of virtual memory tricks. I'm pretty sure Linux just does a memory copy.