引子
由于近期需要写一个java程序和一个已有的c程序(AFL)进行本地通信,使用udp发送数据感觉效率不是很高。另外afl本身使用shm共享物理内存获得待测试子程序的执行路径信息,而java程序共享内存只有文件映射内存的方式,类似与c里面的mmap。因此考虑将AFL的共享内存机制改为mmap,本文将测试linux c 中 mmap、shm和java nio MappedByteBuffer性能,来评估修改AFL共享内存方式是否会影响其本身的性能。
共享内存背景
mmap与shm
内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, mmap()系统调用使得进程之间可以通过映射一个普通的文件实现共享内存。普通文件映射到进程地址空间后,进程可以向访问内存的方式对文件进行访问,不需要其他系统调用(read, write)去操作。
mmap图示例:
shm直接将进程空间的虚拟内存与实际物理内存做了映射。
shm图例:
总结mmap和shm:
- mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘理论上(后面会结合实验结果分析原因)要快,但是存储量不是特别大。
- 相对于shm来说,mmap更加简单,调用更加方便,所以这也是大家都喜欢用的原因。
- 另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。
测试
实验环境
linux服务器用kvm开的虚拟机,因此实际测试可能不会特别准确,开的其他虚拟机在24h跑另外的实验。
4核 16G内存
测试项
- 随机写入1字节
- 清零共享内存
64KB共享内存大小 无负载
gcc + mmap | gcc + shm | gcc O3 + mmap | gcc O3 + shm | java MappedByteBuffer | |
---|---|---|---|---|---|
1e9次随机写入 | 9.535s | 9.716s | 8.761s | 9.548s | 11.672s |
1e7次清零 | 20.621s | 19.952s | 20.537s | 20.362s | 160.178s |
可以看出在64K共享内存的大小下,随机写入mmap和shm在性能上没有明显差异,MappedByteBuffer会慢个将近20%。但是MappedByteBuffer在大内存清零操作上效率很低。
64KB共享内存大小,有25%左右的CPU负载
额外运行了一个程序,该程序用top看大概稳定在25%的cpu占用。
gcc + mmap | gcc + shm | gcc O3 + mmap | gcc O3 + shm | java MappedByteBuffer | |
---|---|---|---|---|---|
1e9次随机写入 | 9.478s | 9.616s | 8.262s | 9.634s | 11.036s |
1e7次清零 | 20.097s | 20.239s | 19.713s | 19.252s | 142.245s |
该实验本来我是期望shm会比mmap效果好,因为想着mmap测试程序的内存可能会被其他程序给替换出去,后面发现这个情况的话shm测试程序也避免不了。但是总体实验结果比无负载情况还要快,还是让我觉得奇怪,只能将原因归其为虚拟机运行不能完全做到资源隔离。建议大家在自己电脑上跑一下看看结果。
1G共享内存大小
gcc + mmap | gcc + shm | java MappedByteBuffer | |
---|---|---|---|
1e9次随机写入 | 758.426s | 149.275s | 805.912s |
这个实验结果来看,shm的性能远超mmap,MappedByteBuffer比mmap慢了不到10%,比之前有进步,也许是JIT长时间运行慢慢优化过来了。
shm比mmap好这么多的原因我分析是由于缓存肯定远远不够1G,所以涉及到了大量的缓存替换的操作,由于shm是直接把物理内存的内容放入缓存,速度肯定比文件映射的内容放入缓存快得多。
总结
根据实验结果,有以下结论
- 共享内存在映射大小不大的情况下,效率非常高,mmap和shm甚至MappedByteBuffer都有不错的效率。循环1e9次随机写入操作的时间开销大概在10s量级,假设cpu频率为2GHz,那么一次循环内,循环操作(i++, i < 1e9)、生成随机数和写入操作一共只用了20个机器周期的时间。
- 共享内存在映射大小较大的情况下,shm效率比mmap更高,但是由于shm直接占用了物理内存,这样可能会影响其他程序的效率,因为可以使用的物理内存变少了,增加了换页的几率。但是,由于mmap映射的是虚拟内存,它可以开辟的空间上限比shm高,而且会保存到文件中,可操作性强于shm。
附 测试代码
// test_mmap.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/time.h>
#include<sys/mman.h>
#include<fcntl.h>
#define SIZE (1 << 16) // 64KB
#define TEST_NUM 10000000 // 1e7
int main() {
int fd = open("share-memory-file", O_RDWR | O_CREAT | O_TRUNC, 0644);
ftruncate(fd, SIZE);
// 以一个字节为单位
char *p = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
struct timeval start, end;
double during_time;
// test random write
gettimeofday(&start, NULL);
char to_write = 0b01010101;
for (long long i = 0; i < TEST_NUM * 100; i++) {
int pos = rand() % SIZE;
memcpy(p + pos, &to_write, sizeof(char)