I wrote my bachelor thesis about the comparism of fwrite VS mmap ("An Experiment to Measure the Performance Trade-off between Traditional I/O and Memory-mapped Files"). First of all, for writing, you don't have to go for memory-mapped files, espacially for large files. fwrite
is totally fine and will nearly always outperform approaches using mmap
. mmap
will give you the most performance boosts for parallel data reading; for sequential data writing your real limitation with fwrite
is your hardware.
In my examples remapSize
is the initial size of the file and the size by which the file gets increased on each remapping.
fileSize
keeps track of the size of the file, mappedSpace
represents the size of the current mmap (it's length), alreadyWrittenBytes
are the bytes that have already been written to the file.
Here is the example initalization:
void init() {
fileDescriptor = open(outputPath, O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600); // Open file
result = ftruncate(fileDescriptor, remapSize); // Init size
fsync(fileDescriptor); // Flush
memoryMappedFile = (char*) mmap64(0, remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create mmap
fileSize = remapSize; // Store mapped size
mappedSpace = remapSize; // Store mapped size
}
Ad Q1:
I used an "Unmap-Remap"-mechanism.
Unmap
- first flushes (
msync
)
- and then unmaps the memory-mapped file.
This could look the following:
void unmap() {
msync(memoryMappedFile, mappedSpace, MS_SYNC); // Flush
munmap(memoryMappedFile, mappedSpace)
}
For Remap, you have the choice to remap the whole file or only the newly appended part.
Remap basically
- increases the file size
- creates the new memory map
Example implementation for a full remap:
void fullRemap() {
ftruncate(fileDescriptor, mappedSpace + remapSize); // Make file bigger
fsync(fileDescriptor); // Flush file
memoryMappedFile = (char*) mmap64(0, mappedSpace + remapSize, PROT_WRITE, MAP_SHARED, fileDescriptor, 0); // Create new mapping on the bigger file
fileSize += reampSize;
mappedSpace += remapSize; // Set mappedSpace to new size
}
Example implementation for the small remap:
void smallRemap() {
ftruncate(fileDescriptor, fileSize + remapSize); // Make file bigger
fsync(fileDescriptor); // Flush file
remapAt = alreadyWrittenBytes % pageSize == 0
? alreadyWrittenBytes
: alreadyWrittenBytes - (alreadyWrittenBytes % pageSize); // Adjust remap location to pagesize
memoryMappedFile = (char*) mmap64(0, fileSize + remapSize - remapAt, PROT_WRITE, MAP_SHARED, fileDescriptor, remapAt); // Create memory-map
fileSize += remapSize;
mappedSpace = fileSize - remapAt;
}
There is a mremap function
out there, yet it states
This call is Linux-specific, and should not be used in programs
intended to be portable.
Ad Q2:
I'm not sure if I understood that point right. If you want to tell the kernel "and now load the next page", then no, this is not possible (at least to my knowledge). But see Ad Q3 on how to advise the kernel.
Ad Q3:
You can use madvise
with the flag MADV_SEQUENTIAL
, yet keep in mind that this does not enforce the kernel to read ahead, but only advices it.
Excerp form the man:
This may cause the kernel to aggressively read-ahead
Personal conclusion:
Do not use mmap
for sequential data writing. It will just cause much more overhead and will lead to much more "unnatural" code than a simple writing alogrithm using fwrite
.
Use mmap
for random access reads to large files.
This are also the results that were obtained during my thesis. I was not able to achieve any speedup by using mmap
for sequential writing, in fact, it was always slower for this purpose.