Overview
I’ve been following Carmack’s tweets on porting RAGE to iOS and was intrigued by his and other mentions that memory mapped files work so well on the platform.
Mapped files have the advantage of being cached by the OS (in kernel memory space) and paged in and out of memory as necessary. On a desktop platform or console, the unpredictable seek time of the disk makes this less feasible, but with the gauranteed solid state disk of apple products it becomes an interesting proposition.
Since the mapping is in kernel space it can actually remain cached across application launches which is a nice performance win.
The Test
Wooords has a 1MB resident english dictionary for checking word submissions against. The dictionary is a array of sorted 64bit ints, to look up a word a simple binary search is performed.
The dictionary is an ideal candidate for moving to a memory mapped file as the OS can cache the commonly hit parts of the data file and leave the rest on disk (although I’m not sure how smart the caching algorithm is? Is it page based?)
To test these theories out I implemented the required calls and timed the binary search for a variety of words.
| Malloc | Mapped | |
| Lookup 1 | 0.009ms | 0.111ms |
| Lookup 2 | 0.009ms | 0.010ms |
| Lookup 3 | 0.008ms | 0.008ms |
| Lookup 4 | 0.011ms | 0.011ms |
| Lookup 5 | 0.008ms | 0.072ms |
As we can see the mmap array has similar performance but there’s an initial spike as the data is loaded by the kernel and a couple smaller spikes during usage.
For dictionary lookup the extra latency now and again is not a problem and therefore the choice to use mapped files easy.
Technical Implementation
To use a memory mapped file, simply open the file as usual and then call the mmap function. Once you’re done with the data munmap it and close the file.
Map
FILE* fp = fopen("file.dat", "rb");
fseek(fp, 0, SEEK_END);
size_t length = ftell(fp);fseek(fp, 0, SEEK_SET);
int fd = fileno(fp);
off_t offset = 0;
void* data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, offset);
fclose(fp);
Arguments
| NULL | Base address to use for mapping, this doesn’t matter for us, let the OS choose one |
| length | Number of bytes to map into memory, in our case the whole file |
| PROT_READ | We’re only interested in readying the file |
| MAP_SHARED | Share this file with other processes on the system, if its written to we get the updates. This should be the most performant, otherwise the OS has to do a copy on write. |
| fd | The file descriptor |
| offset | The start of the mapping on the file |
For a detailed explation of the parameters see man mmap.
Unmap
munmap(data, length);
Conclusion
Using memory mapped files can reduce your process memory footprint with minor modifications to your code. It can however cause small load stalls, so the best thing to do is to benchmark it in your application.
Overall its another great tool in the iOS developers toolbox.
Custom? Why custom I hear you say - GameCenter provides all the leaderboard APIs anybody could ever need, or does it? Unfortunately 