我们经常听说系统调用的开销比函数调用大得多,因此需要尽量减少系统调用的次数来提高代码性能。那么,系统调用的具体开销是多少呢?它需要消耗多少cpu时间?
1
系统调用概述
系统调用是用户程序与内核进行交互的机制。当代码需要进行I/O操作(如open、read、write)、内存操作(如mmap、sbrk)或获取网络数据时,必须通过系统调用来实现。无论你使用的是什么编程语言,如PHP、C、Java还是Go,只要你的程序运行在Linux内核上,就无法避免系统调用。
图1 系统调用在计算机系统中的位置
你可以使用strace命令查看程序正在执行的系统调用。例如,查看一个在生产环境中运行的nginx的系统调用情况如下(可能需要左右滑动查看完整内容):
# strace -p 28927
Process 28927 attached
epoll_wait(6, {{EPOLLIN, {u32=96829456, u64=140312383422480}}}, 512, -1) = 1
accept4(8, {sa_family=AF_INET, sin_port=htons(55465), sin_addr=inet_addr("10.143.52.149")}, [16], SOCK_NONBLOCK) = 13
epoll_ctl(6, EPOLL_CTL_ADD, 13, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=96841984, u64=140312383435008}}) = 0
epoll_wait(6, {{EPOLLIN, {u32=96841984, u64=140312383435008}}}, 512, 60000) = 12
使用strace命令进行实验
通过对线上运行的nginx进行strace统计,我们可以看到系统调用的耗时大约在1-15微秒(μs)之间。因此,可以得出系统调用的耗时通常在微秒级别。当然,由于不同系统调用执行的操作和环境不同,耗时会有所波动(可能需要左右滑动查看完整内容)。
# strace -cp 8527 strace: Process 8527 attached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 44.44 0.000727 12 63 epoll_wait 27.63 0.000452 13 34 sendto 10.39 0.000170 7 25 21 accept4 5.68 0.000093 8 12 write 5.20 0.000085 2 38 recvfrom 4.10 0.000067 17 4 writev 2.26 0.000037 9 4 close 0.31 0.000005 1 4 epoll_ctl
3
使用time命令进行实验
我们手动编写一段代码来测试read系统调用,代码如下:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
<p>int main() {
char c;
int in;
int i;</p><pre class="brush:php;toolbar:false;"><code>in = open("in.txt", O_RDONLY);
for(i=0; i<1000000; i++) {
read(in, &c, 1);
}
close(in);
return 0;}
注意,只能使用read库函数进行测试,不要使用fread,因为fread是用户态库函数,带有缓存,而read每次调用都会触发一次系统调用。
首先,创建一个大小为1MB的文件:
dd if=/dev/zero of=in.txt bs=1M count=1
然后编译并运行代码进行测试:
# gcc main.c -o main</p><h1>time ./main</h1><p>real 0m0.258s user 0m0.030s sys 0m0.227s
由于上述实验循环了100万次,因此平均每次系统调用的耗时大约为200纳秒(ns)左右。
4
使用Perf命令查看系统调用消耗的CPU指令数
x86-64 CPU具有特权级别的概念。内核运行在最高级别(Ring0),用户程序运行在Ring3。当用户态程序需要访问磁盘等外设时,必须通过系统调用进行特权级别的切换。
普通函数调用通常只需要几次寄存器操作和用户栈操作,而系统调用则需要从用户态切换到内核态,涉及到内核栈和寄存器的切换,如SS、ESP、EFLAGS、CS和EIP寄存器。此外,系统调用还可能导致缓存和TLB页表缓存的命中率下降,并需要进行权限校验和有效性检查。因此,系统调用的开销远大于函数调用。
我们使用perf命令计算每个系统调用需要执行的CPU指令数(可能需要左右滑动查看完整内容):
# perf stat ./main Performance counter stats for './main': 251.508810 task-clock # 0.997 CPUs utilized 1 context-switches # 0.000 M/sec 1 CPU-migrations # 0.000 M/sec 97 page-faults # 0.000 M/sec 600,644,444 cycles # 2.388 GHz [83.38%] 122,000,095 stalled-cycles-frontend # 20.31% frontend cycles idle [83.33%] 45,707,976 stalled-cycles-backend # 7.61% backend cycles idle [66.66%] 1,008,492,870 instructions # 1.68 insns per cycle # 0.12 stalled cycles per insn [83.33%] 177,244,889 branches # 704.726 M/sec [83.32%] 7,583 branch-misses # 0.00% of all branches [83.33%]
将for循环中的read调用注释掉后,再次编译并运行:
# gcc main.c -o main</p><h1>perf stat ./main</h1><p>Performance counter stats for './main': 3.196978 task-clock # 0.893 CPUs utilized 0 context-switches # 0.000 M/sec 0 CPU-migrations # 0.000 M/sec 98 page-faults # 0.031 M/sec 7,616,703 cycles # 2.382 GHz [68.92%] 5,397,528 stalled-cycles-frontend # 70.86% frontend cycles idle [68.85%] 1,574,438 stalled-cycles-backend # 20.67% backend cycles idle 3,359,090 instructions # 0.44 insns per cycle # 1.61 stalled cycles per insn 1,066,900 branches # 333.721 M/sec 799 branch-misses # 0.07% of all branches [80.14%] 0.003578966 seconds time elapsed
平均每次系统调用需要执行的CPU指令数为(1,008,492,870 - 3,359,090)/1000000 ≈ 1005条指令。
5
深挖系统调用实现
如果你想了解系统调用的具体实现,可以参考《深入理解LINUX内核-第十章系统调用》。最初,系统调用通过汇编指令int(中断)实现,当用户态进程发出int $0x80指令时,CPU切换到内核态并执行system_call函数。后来,Intel提供了“快速系统调用”指令sysenter以提高效率。我们通过实验验证如下(可能需要左右滑动查看完整内容):
# perf stat -e syscalls:sys_enter_read ./main Performance counter stats for './main': 1,000,001 syscalls:sys_enter_read 0.006269041 seconds time elapsed
上述实验证明,系统调用确实是通过sys_enter指令进行的。
6
结论
与函数调用不到1纳秒的耗时相比,系统调用的开销确实较大。尽管使用了“快速系统调用”指令,但耗时仍在200纳秒以上,某些情况下可能达到十几微秒。每次系统调用需要执行约1000条CPU指令,因此确实应该尽量减少系统调用次数。然而,即使是10微秒,也仅是1毫秒的百分之一,所以不必过分担心系统调用的开销。
系统调用之间耗时差异较大的原因在于,虽然内核态与用户态的切换时间基本相同,但不同的系统调用在内核态的处理工作不同,导致在内核态停留的时间差异较大。
以上就是一次系统调用开销到底有多大?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号