Linux/UNIX发展数十年,IPC可谓五花八门,好在后来POSIX和SUS标准化下了很多功夫,如今接口清晰稳定了不少,但各系统实现依然有不少大坑小坑,不仅要看书和查文档,还要多实践,才能逐步熟悉掌握,本文就是熟悉IPC的一种途径。
性能测试代码和思路主要基于UNIX网络编程第二卷[Stevens, 1999],后文简称UNPv2,但做了如下调整:
1. 去掉Linux不支持或不常用的IPC,如Doors和Sun RPC。
2. 用pthread API重写读写锁,Stevens写UNPv2时Solaris和Digital UNIX还没有pthread版本的读写锁。
3. 为了减少外部依赖,重写TCP/UDP/UNIX Domain socket带宽和延迟测试,UNPv2的该部分数据是利用了开源的lmbench。
4. 增加了gcc版本的atomic作为一种同步原语,atomic如今已在C++ 11和C11获得标准化。
5. 暂时没包含System V semaphore,因为在自己机器测试时,出现极高的性能下降,比其他同步方式慢100倍以上,还不能确定是虚拟机或内核版本或用法导致,等后续有结论时,再补充。
6. 所有设置维持系统默认参数,除非文中特别说明。
硬件:CPU双核(2.3GHz,6MiB L3),内存2GiB
软件:Fedora 20(kernel 3.12.10, gcc 4.8.2, glibc 2.18)
方法:程序分别选取1KiB | 2KiB | 4KiB | 8KiB | 16KiB | 32KiB | 64KiB作为一个消息大小,父子进程间传输数据,每个消息大小传输500MiB数据,运行5次取均值,得出每秒带宽数值。
图表:
Message size |
Bandwidth (MB/sec) |
|||||
Pipe |
POSIX message queue |
System V message queue |
TCP socket |
UNIX domain socket |
UDP socket |
|
1024 |
1,233 |
405 |
354 |
1,756 |
1,603 |
15 |
2048 |
2,048 |
869 |
655 |
2,625 |
2,132 |
30 |
4096 |
2,944 |
1,653 |
1,075 |
3,483 |
3,013 |
60 |
8192 |
3,211 |
4,250 |
1,599 |
4,175 |
4,779 |
119 |
16384 |
3,300 |
5,982 |
2,510 |
4,552 |
6,414 |
231 |
32768 |
2,876 |
6,929 |
2,888 |
4,450 |
7,508 |
435 |
65536 |
2,830 |
7,483 |
3,830 |
4,692 |
3,953 |
|
说明:
1. UDP带宽很低,因为UDP是IPC中唯一不可靠的数据传输方式,没有流量控制,不得不自己实现一个应用层的简单PUSH-ACK协议,但副作用就是性能下降严重。实践中可以实现一个批量传输/异步重传的传输协议,性能会高很多。
2. TCP和UDP都是基于lo环回网络接口,MTU 64KiB。TCP是流式传输,应用层不会感知到MTU限制。UDP是有边界的数据包,要考虑path MTU的问题,理论上IP可以分片UDP大数据包,但会带来更大的性能和可靠性问题,所以多数系统都会限制UDP报文大小,比如MTU 64KiB减去IP和UDP头部与变长选项,实际可发报文数据会低于64KiB,为了图表整齐,UDP的message size只取到32KiB。
3. UNIX domain socket可以支持字节流和数据包两种形式,这里只选取了字节流方式,类似TCP。
4. 需要提升内核对消息队列的限制/proc/sys/fs/mqueue/msgsize_max,/proc/sys/kernel/msgmax, /proc/sys/kernel/msgmnb,前面三个值都要提升到至少64KiB。
5. 从曲线走势中可以明显分为两个阵营:字节流无边界,有 pipe/TCP socket/UNIX Domain socket,该种实现通常维护一个内部buffer,每次读取用户空间的数据块不超过某个大小,当用户message size超过该值时,带宽不会有明显增长,甚至会下降,像pipe有PIPE_BUF限制,我的本机是4KiB; 数据报有边界,有POSIX message queue/System V message queue/UDP socket,一个message size都是一次调用,带宽随着message size持续增长。
方法:程序的父子进程交换1字节数据10K次取均值,程序运行5次再取均值。
图表:
Latency (microseconds) |
|||||
Pipe |
POSIX message queue |
System V message queue |
TCP socket |
UDP socket |
UNIX domain socket |
53 |
53 |
57 |
67 |
63 |
54 |
说明:
延迟方面实在没有意外发生。。。都差不多。
方法:程序分别启动1 | 2 | 3 | 4 | 5个子进程,在共享内存中放一个long整数,每个子进程对其自增1M次,总计时间,程序运行5次取均值。
图表:
# processes |
time to count 1M times (microseconds) |
|||||
atomic |
mutex |
read-write lock |
memory semaphore |
named semaphore |
fcntl record locking |
|
1 |
6,082 |
20,478 |
34,125 |
20,736 |
23,340 |
544,179 |
2 |
40,948 |
120,419 |
411,038 |
192,671 |
222,371 |
1,317,033 |
3 |
72,817 |
177,074 |
726,129 |
648,390 |
630,069 |
2,191,806 |
4 |
101,213 |
287,997 |
1,012,311 |
855,711 |
891,484 |
6,125,641 |
5 |
128,190 |
371,302 |
1,129,752 |
1,122,309 |
1,198,571 |
3,757,362 |
说明:
1. 因为整数自增操作是非常简单的运算,并发访问时最适合atomic操作(__atomic_fetch_add),不涉及系统调用,只是几条汇编指令,所以效率最高,但它能做的操作都很简单,更复杂的场景就要求助mutex等手段了,此处只为说明同步原语本身的代价。
2. fcntl记录锁最慢,毕竟是文件系统相关的操作,是意料之中的,但记录锁用起来非常简单,各平台都支持,多进程下是很好的同步方式。
3. 除了fcntl,其它同步方式都是内存操作,规律就是功能越多,效率越低。atomic只能做一些整数类型的原子操作,最快;mutex可以锁定任意代码段,但只有0-1两个状态,仅次于atomic;读写锁需要区分读锁和写锁,比mutex复杂,信号量要维护数值的变化,可以看做是互斥锁与条件变量的合体,也比mutex复杂,这两个比mutex慢也是情理之中。
方式:程序分别启动1 | 2 | 3 | 4 | 5个线程,在共享内存中放一个long整数,每个线程对其自增1M次,总计时间,程序运行5次取均值。
图表:
# threads |
time to count 1M times (microseconds) |
|||||
atomic |
mutex |
read-write lock |
memory semaphore |
named semaphore |
fcntl record locking |
|
1 |
6,011 |
20,161 |
36,107 |
20,472 |
21,136 |
581,972 |
2 |
40,732 |
197,068 |
322,856 |
177,390 |
196,268 |
|
3 |
60,447 |
266,436 |
364,316 |
281,590 |
536,500 |
|
4 |
81,705 |
383,399 |
468,483 |
459,140 |
742,603 |
|
5 |
102,755 |
476,966 |
565,017 |
517,602 |
1,116,406 |
|
说明:
1. 对比上面的多进程同步时间,可以看出Linux的进程切换并不比线程切换慢多少。
2. fcntl记录锁是基于进程的,所以有意义的测试只需开启一个线程。
不可直接把上面的结论套用到任何实际项目中,IPC实际性能有很多因素影响,比如系统负载/进程上下文切换/各种参数设置等等,需要以自身机器测试结果为依据,但可以参考上述思路。
1. UNIX Network Programming, Volume 2, Second Edition: Interprocess Communications, Prentice Hall, 1999, ISBN 0-13-081081-9.
2. Linux/UNIX系统编程手册. Kerrisk, M著;孙剑等译. 人民邮电出版社
3. UNPv2源码,http://www.kohala.com/start/unpv22e/unpv22e.html
4. matplotlib生成上述图表,http://matplotlib.org