http://blog.chinaunix.net/uid-190176-id-4177874.html
一. 概述
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)
三. IPC带宽测试
方法:程序分别选取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持续增长。
四. IPC延迟测试
方法:程序的父子进程交换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