中国交通建设集团英语网站新宾区网站建设

张小明 2026/1/17 15:18:56
中国交通建设集团英语网站,新宾区网站建设,绍兴网站建设服务,制作网页的超文本标记语言在 Linux C 编程的世界里#xff0c;存在着一个经典的问题#xff1a;同样是文件 I/O#xff0c;系统调用 (read/write) 和 C 标准库 (fread/fwrite) 到底哪个性能更好#xff1f; 教科书的说法一般是这样的#xff1a;fread/fwrite 因为带有用户态缓冲区#xff0c;能将…在 Linux C 编程的世界里存在着一个经典的问题同样是文件 I/O系统调用 (read/write) 和 C 标准库 (fread/fwrite) 到底哪个性能更好教科书的说法一般是这样的fread/fwrite因为带有用户态缓冲区能将多次小块 I/O 请求打包成大块显著减少系统调用次数所以在大多数场景下尤其是小块随机读写时通常更快、更高效。这也是为什么许多开发者在编写文件处理代码时会优先选择标准库函数而不是直接裸奔系统调用。然而理论很丰满但现实往往骨感得令人意外。为了验证这个结论我设计了一个文件拷贝实验。然而第一轮测试的结果就给了我一记响亮的耳光——裸奔的 read/write 竟然比精心封装的 fread/fwrite 快了 2 倍多这到底是怎么回事呢是代码写错了还是我对 Linux I/O 的理解有问题为了了解背后的真相我进行了一场从用户态到内核态的探索。其实这是 Linux I/O 机制的精妙设计所致内核的页面缓存Page Cache和预读readahead 机制已经为大块顺序读写提供了优化。系统调用直接利用内核缓冲而fread/fwrite虽有用户态缓冲却引入了额外memcpy和逻辑检查开销在缓冲区已对齐页面大小时反而成了累赘。本文我将会对比不同缓冲区大小查看 strace 系统调用轨迹。这不仅仅是一次性能测试更是一次对 Linux I/O 子系统VFS、Page Cache、缓冲策略的深度解密。大家准备好了吗我们开始。1. 实验设计与诡异的实验结果为了让对比实验公平且方便大家复现首先需要一个体积适中的测试文件以及系统调用和C库的拷贝程序。1.1 准备测试数据我们直接使用dd命令来创建一个10MB、内容全为 0的二进制文件。这个体积对于现代PC来说不大不小能触发I/O操作又不会测试太久。dd if/dev/zero oftestfile.dat bs1M count10我们在这里先简单介绍一下这个命令。dd是 Linux/Unix 系统里一个非常古老但极其强大的工具。核心功能就是从一个地方读取数据然后写入到另一个地方并且可以精确控制读写的方式。if可以理解为 input file 取两个单词的首字母表示输入源。if/dev/zero/dev/zero 是 Linux 内核提供的一个“虚拟设备文件”当你去读它时它会源源不断地给你返回二进制的 0。of可以理解为 output file 取两个单词的首字母表示输出目标。oftestfile.dat让 dd 把读到的 0 写到当前目录下一个叫 testfile.dat 的文件里。注这会自动创建该文件bs可以理解为 block size 取两个单词的首字母它用来指定块大小。dd 工作时不是一个字节一个字节搬运而是一块一块地搬。这个参数就是定义这个块有多大。bs1M我们把块大小设置为1 MB。最后的count10就是指定有多少块这里我们定义为10块。一次搬一块一块为1MB那么搬10块就是10MB。我们运行这条命令后查看一下文件信息发现确实得到了一个大小为10MB的 testfile.dat 文件。1.2 设计两个拷贝程序两个拷贝程序的核心逻辑都非常简单循环读写。为了让对比更有意义我将它们的读写缓冲区大小BUFFER_SIZE都设置为4096 字节 (4KB)这通常是一个内存页的大小也是I/O操作中常见的块大小。1.2.1 系统调用这里先贴上代码#include stdio.h #include time.h #include fcntl.h #include unistd.h ​ #define FILE_SOURCE testfile.dat #define FILE_DESTINATION des_sys_file.dat #define BUFFER_SIZE 4096 ​ int main() { clock_t start,end; double cpu_time_used; ​ start clock(); //开始计时 ​ int s_fd open(FILE_SOURCE,O_RDONLY); if(s_fd -1) { perror(open failed); return -1; } ​ int d_fd open(FILE_DESTINATION,O_RDWR|O_CREAT,0644); if(d_fd -1) { close(s_fd); perror(open failed); return -1; } ​ ​ char buf[BUFFER_SIZE]; //4KB缓冲区 ssize_t bytes; while(1) { bytes read(s_fd,buf,BUFFER_SIZE); if(bytes -1) { perror(read failed); return -1; } else if(bytes 0) { printf(数据拷贝完毕\n); break; } write(d_fd,buf,bytes); } ​ close(s_fd); close(d_fd); ​ end clock(); //计时结束 cpu_time_used ((double) (end - start)) / CLOCKS_PER_SEC; printf(系统调用拷贝耗时%f秒\n,cpu_time_used); ​ return 0; } ​有一个要注意的点这里使用clock()测量的是进程的 CPU 时间用户态 内核态不包含进程阻塞等待磁盘 I/O 的时间。因此这个时间主要反映memcpy和系统调用开销而非实际感受到的拷贝时长。这段代码看起来简单但每一次while循环都触发了系统调用当read()被调用程序执行从用户态切换到内核态。CPU 保存当前上下文加载内核上下文这是一个耗时的操作。内核从磁盘或 Page Cache 页缓存读取数据拷贝到我们传入的buf中。执行完后再从内核态切回用户态这又是一个耗时的操作。当write() 被调用再次发生一次用户态到内核态的切换。内核将buf中的数据拷贝到内核缓冲区准备写入磁盘。然后再切回用户态。对于一个 10MB 的文件和 4KB 的缓冲区表面上看这个 while 循环要执行约 2560 次每次循环调用两次系统调用readwrite总共约 5120 次系统调用这意味着超过一万次的上下文切换。可能有不少人包括我最初正是基于这种直觉认为裸系统调用一定会慢——因为教科书告诉我们标准库的fread/fwrite会通过用户态缓冲把多次小 I/O 合并成少量大 I/O从而大幅减少系统调用次数。但是当后面我们真正运行实验才发现并没有我们想象的这么简单。1.2.2 C库调用#include stdio.h #include fcntl.h #include unistd.h #include time.h ​ #define FILE_SOURCE testfile.dat #define FILE_DESTINATION des_libc_file.dat #define BUFFER_SIZE 4096 ​ int main() { clock_t start,end; double cpu_time_used; ​ start clock(); //开始计时 ​ FILE *s_fp fopen(FILE_SOURCE,rb); if(s_fp NULL) { perror(fopen source failed); return -1; } ​ FILE *d_fp fopen(FILE_DESTINATION,wb); if(d_fp NULL) { fclose(s_fp); perror(fopen destination failed); return -1; } ​ char buf[BUFFER_SIZE]; size_t read_items; while((read_items fread(buf,1,BUFFER_SIZE,s_fp)) 0) { fwrite(buf,1,read_items,d_fp); } ​ if(ferror(s_fp)) { perror(fread failed); } else if(feof(s_fp)) { printf(数据拷贝完毕\n); } ​ fclose(s_fp); fclose(d_fp); ​ end clock(); //结束计时 ​ cpu_time_used ((double) (end - start)) / CLOCKS_PER_SEC; printf(C库调用耗时: %f秒\n,cpu_time_used); ​ return 0; } ​表面逻辑看C 库版本和系统调用版本几乎一模一样同样是 4KB 的用户态缓冲区同样是循环读取 → 写入的模式。唯一的区别在于使用 FILE * 结构体代替文件描述符 int fd使用fopen/fclose代替open/close使用fread/fwrite代替read/write不少人会理所当然地认为这个版本应该更快。原因在于教科书反复强调的观点C 标准库的stdio为每个 FILE 结构体维护了一个用户态缓冲区默认大小通常为BUFSIZ即 8192 字节它能将多次小块fwrite合并成一次底层write系统调用从而大幅减少上下文切换次数。然而在我们这个实验里缓冲区大小已经固定为 4096 字节而且是顺序大块读取。glibc 的 stdio 实现在检测到大块且对齐良好的 I/O 时往往会直接透传到底层系统调用几乎不进行额外的“打包”操作。更重要的是fread/fwrite每一次调用都会带来额外的开销检查用户态缓冲区是否满或者空还有可能的 memcpy或者额外的函数调用层级和错误检查逻辑。这些开销在小块随机 I/O 时被“减少系统调用次数”的收益完全掩盖但在大块顺序 I/O 时反而成了纯纯的负担。1.2.3 Makefile下面是实验要使用的 Makefile 源码CC gcc ​ CFLAGS -Wall -g ​ all: sys libc ​ sys: copy_syscall.c $(CC) $(CFLAGS) -o sys copy_syscall.c ​ libc: copy_libc.c $(CC) $(CFLAGS) -o libc copy_libc.c ​ ​ ​ .PHONY: all clean clean-data test ​ clean: rm -f sys libc des_sys_file.dat des_libc_file.dat ​ clean-data: rm -f *.dat ​ test: all ./sys ./libc ​为了方便编译程序和删除程序运行生成的文件我写了这个Makefile下面介绍一下怎么使用。命令行输入make编译两个 c 源代码然后可以分别运行编译生成的可执行文件。或者直接输入make test编译并运行两个 c 源代码。命令行输入make clean可以清除两个可执行文件和程序运行生成的两个数据拷贝文件。命令行输入make clean-data可以清除上面命令行创建的用于拷贝的数据源文件。1.3 对比测试的结果现在一切已经准备就绪我们直接运行程序为了确保测试结果无偶然性我将程序运行了三遍。从上面的运行结果可以看到C库调用和系统调用两者几乎在同一个数量级上。在误差允许的范围内我甚至可以认为他们消耗的CPU时间几乎无差异。当时看到这个运行结果我整个人陷入了迷茫为什么在 4KB 缓冲区大小下C 库的用户态缓冲没有带来明显的性能优势要知道对于一个 10MB 文件 4KB 缓冲区如果没有用户态缓冲理论上会触发 5120 次系统调用2560 次read 2560 次write而有了 stdio 缓冲按理说应该能将多次fwrite合并成少数几次大write系统调用次数大幅下降性能应该暴涨才对。可是现实中C 库版本只快了那么一点点。这与我们通常的fread/fwrite显著更快的印象完全不符。2. 测试结果分析strace 与BUFSIZ揭示的线索第一次实验那微乎其微的性能差异让我百思不得其解。为了搞清楚fread/fwrite底层到底做了什么我决定使用strace来跟踪一下 libc 版本程序在运行时到底发起了多少次、多大的系统调用。2.1 与预期不符的系统调用我使用以下命令来运行 libc 程序并只关注read和write系统调用strace -e traceread,write ./libc我原以为会看到少数几次、大小为8KB(BUFSIZ这个宏的值) 的read系统调用。然而strace的输出再次颠覆了我的认知这里read和write的调用有上千次之多为了让大家看到strace内容的全貌我就只截图了最开始和最后的部分。下面我们来分析一下这两张截图从这两张图我们可以轻易的看出其实fread和fwrite最终都是要调用read和write的。先来看我们过滤后的strace的信息的第一行可以看到它读取的字节数是832不是我们预计的8K但实际上这里是正常的真正的读取还没有开始这只是程序开始前的一些预备动作。我们再往下看发现read的文件描述符全为3而write的文件描述符全为4这也是符合预料的。这个和我们打开文件的顺序有关首先我们都知道文件描述符的012分别被标准输入标准输出和标准错误占据。当我们再次打开文件时文件描述符就会从3开始往后每打开一个文件这个数字就会加一还有一点要提一下如果你打开了3456这几个文件然后关闭4这个文件描述符那么下一次再打开文件时就会把最小的文件描述符4分配出去。好了回归正题我们看一下C库测试程序的代码可以观察到我们先打开的是 testfile.dat 这个文件然后才打开了 des_libc_file.dat 这个文件而前者正是我们要读取数据的文件后者正是我们要写入数据的文件到这里已经很明显了我就不再赘述。再来看 strace 第二张图的最后一个read我们发现它的第二个参数是空的为什么呢这也是符合逻辑的只要read读到了数据那么它就会返回读取到的字节数。我们可以先看看第三个参数值为4096这个是我没有想到的这里先放着后面再提也就是说read每次读取4096字节即4KB而我们设置的文件大小为10MB正好是倍数关系这也就是说在若干次读取后它进行最后一次读取而最后一次读取刚好把剩下的4096字节读完。下一次再要read时已经没有数据可以读了这也是为什么最后一个read什么都没有读到的原因。这时read返回0表示读取结束。最后一个read的下一条信息就是write这里可以看到这个write文件描述符为1也就是说它在向终端打印内容聪明的小伙伴已经翻到上面去看代码了发现代码逻辑中在最后一次read结束后确实会向终端打印“数据拷贝完毕”这几个字。到这里strace的内容相信大家都能看懂了那我就不再赘述了。来看最重要的部分正如我上面所说我原以为能看到read和write的字节数都是 8KB但实际上却只有上面截图中的 4KB。也就是说当我请求 4KB 数据时(fread(..., 4096, ...)), stdio 库底层也精确地发起了4KB的read系统调用。这太令我惊讶了但这也刚好说明了为什么在上面的测试中C库调用和系统调用差不多因为底层的系统调用次数完全一样fread那微乎其微的性能劣势正来自于其额外的用户态函数调用和memcpy开销。也正是因为两者本身系统调用次数相同在不确定性因素的影响下才会出现上面三种测试结果谁快谁慢说不准但总体上不相上下。2.2 令人困惑的BUFSIZ在继续分析结果之前我觉得有必要先介绍一下BUFSIZ这个宏是干什么的。BUFSIZ这个宏定义在stdio.h中它表示stdio标准输入输出库为流FILE分配的默认缓冲区大小。当你调用fopen()打开一个文件时C 标准库会自动为这个流分配一个内部用户态缓冲区用于缓存读写数据。这个缓冲区的默认推荐大小就是BUFSIZ。全缓冲模式普通文件默认为这个模式缓冲区大小通常就是BUFSIZ8KB。数据会累积到缓冲区满调用fflush()或关闭流时才真正写入底层系统调用。行缓冲模式stdout连接终端时缓冲区大小通常是 1KB1024 字节与BUFSIZ无关。小块fwrite()每次写几个字节不会立即触发write()系统调用而是先积累到用户态内部缓冲区。等缓冲区达到 BUFSIZ 大小时才一次性调用底层write()把大块数据刷到内核。这正是为什么在小块 I/O 场景下fwrite比直接write()快得多的原因。我起初以为我的系统中BUFSIZ的值不是8KB才导致上面诡异的测试结果于是我写了一个小demo查了一下BUFSIZ的值请看#include stdio.h ​ int main() { printf(BUFSIZ %d\n,BUFSIZ); return 0; } ​从程序允许结果可以看出BUFSIZ的值确实是 8KB 。事情又变得有趣起来了C库明明给自己准备了一个 8KB 的大仓库为什么实际read时却只使用 4KB 呢是不是有一只强大的隐形之手在干扰这个过程呢2.3 注意重点来了经过查阅资料和进一步的实验我发现了 glibc 中一个极其精妙的优化机制当fread或fwrite检测到用户提供的缓冲区(char buf[4096])足够大通常与系统页大小或BUFSIZ相当且内存对齐良好时为了避免一次额外的memcpy开销stdio库会偷懒它会直接使用用户提供的缓冲区去执行底层的read系统调用而绕过它自己的内部缓冲区。这就完美解释了我们看到的所有现象为什么read是 4KB因为传给fread的buf就是 4KB。为什么性能差不多因为stdio库放弃了合并请求退化成了一个和裸read/write差不多的机制系统调用次数完全一样。所以我最初的那个性能对比实验无意间触发了这个优化导致没能观察到stdio真正的缓冲实力。这一切的背后都指向了 Linux 内核一个更深层次、更强大的隐形之手——页面缓存 (Page Cache)。3. 深入内核解析Page Cache 的隐形加速前两章的探索让我们发现无论是 C 库还是系统调用在处理大块顺序 I/O时性能都出奇地好且差异不大。这说明在我们的代码之下有一个比用户态缓冲更强大、更底层的机制在主导着 I/O 性能。这个机制就是 Linux 内核为了填补飞速的 CPU与龟速的磁盘之间巨大鸿沟而设计的——页面缓存 (Page Cache)。3.1 什么是 Page Cache简单来说Page Cache 就是内核在物理内存 (RAM) 中开辟的一块区域专门用来缓存磁盘文件的数据。它就像是内核给自己建的一个内存硬盘把最近访问过的文件数据都存在里面。到这儿如果看过我上一篇文章的朋友标题为《C语言实战手搓高并发异步日志库基于 Ring Buffer 和生产者消费者模型》的3.1.3节可能会想起 CPU 的三层缓存为了帮大家区分并且更加深入的了解 Page Cache 下面我将先介绍他们的共同点在介绍他们的区别以对比的方式列出他们的区别。首先Page Cache 和 CPU Cache在设计思想上极其相似为了解决快慢速设备差异但它们在物理位置、管理者、工作粒度上有着本质的区别。3.1.1 CPU Cache 和 Page Cache 的共同点他们都遵循着一个真理就是局部性原理。这个局部性原理包括空间局部性和时间局部性空间局部性在我的上一篇文章异步日志库那个也有提过。在上篇文章的场景下就是说你CPU在访问数组的一个元素时由于同一个数组的元素是连续存放的根据空间局部原理你用了一个数据有大概率也会用到它旁边的数据因此CPU拿到的就不仅仅是它要访问的那个数据而是那个元素所在的一整个Cache Line总共64字节。对详细内容感兴趣的读者可以去看看我上一篇这里先回归正题。我用大白话描述一下这两个原理时间局部性刚用过的数据很可能马上又要用。空间局部性用了 A 数据很可能马上要用 A 存储位置附近的数据。无论是 CPU Cache 还是 Page Cache都是利用这个原理把热门数据从慢速区搬到快速区暂存以备后用。3.1.2 CPU Cache 和 Page Cache 的本质区别在介绍他们的本质区别之前大家先来看一张图这张图已经揭示了他们的区别第一他们的管理者不同。CPU 三层 Cache 由 CPU硬件自动管理无法通过程序进行控制。写一个int a 1;数据会不会进 L1 Cache或者什么时候被踢出去都是 CPU 自己的事。我们只能通过写出缓存友好的代码比如遍历数组来间接帮助它提升性能。Page Cache 由操作系统内核软件管理虽然不能直接读写但可以通过一些系统调用来影响它mmap,O_DIRECT也能直接操作 Page Cache。第二服务对象不同。CPU Cache 服务于CPU 的计算指令它的目标是让add,mov这些指令能以最快速度拿到操作数。Page Cache服务于I/O 系统调用它的目标是让read,write感觉上像是操作内存而不是操作慢速磁盘。第三工作粒度不同。CPU Cache工作单位是Cache Line (64 字节)。Page Cache工作单位是Page (4KB)。3.1.3 总结CPU Cache 是 CPU 与内存之间的硬件缓存由 CPU 硬件自动管理对程序员透明目的是加速计算指令。Page Cache 是内存与磁盘之间的软件缓存由操作系统内核管理目的是加速文件 I/O。一个是硬件层面一个是内核软件层面两者协同工作才构成了现代计算机高效的 I/O 体系。3.2 Page Cache 的三大特性第一透明性Page Cache 对上层的应用程序是完全透明的。你只管调用read但根本不知道数据是从内存来的还是从硬盘来的内核已经帮你搞定了一切。第二脏页回写当你调用write时数据也不是立刻就写到硬盘里。内核会先把数据写入对应的 Page Cache并把这个 Page 标记为脏页 (Dirty Page)。write系统调用会立刻返回你的程序感觉写入已经完成了。内核会在后台找个合适的时间比如系统不忙时或者脏数据积累到一定程度再把这些脏页统一刷回磁盘。这也是为什么write看起来很快的原因之一。第三预读当内核检测到你在顺序读取一个文件时比如你正在read第 1-4KB它会猜测你马上就要读第 5-8KB、9-12KB...于是它会提前把文件的后续内容也加载到 Page Cache里。当你真的read后续内容时发现数据已经在内存里面了当然你不会真的发现你只会感觉程序运行很快。3.3 清除页缓存后的结果根据上面的三大特性我猜想第一次实验结果可能是从Page Cache里面拿的数据。所以总体速度很快如果清除页缓存后速度可能会变慢所以我又进行了第二次实验。在第二次实验中我每次在运行程序之前都会将页缓存清除echo 1 /proc/sys/vm/drop_caches数字1代表清除Page Cache。然后我得到了下面的结果我依旧运行了两次我们现在有两点不需要关注。第一点是为什么两次运行的结果不同这是因为虚拟机的不稳定行或者受到系统噪声影响造成的因此我们不需要关注。第二点是C库调用和系统调用在忽略干扰因素后差距不大这在上面2.3 节已经提到过了是因为在4KB用户态缓冲的条件下他们的系统调用次数是相同的这个问题我们已经了解了因此也不需要关注。我们唯一需要关注的就是第二次实验也就是本次总体和第一次实验总体上的差异。可以看到第一次实验数据基本都在0.02左右徘徊而第二次实验结果基本都在0.04-0.05之间。这也恰恰验证了 Page Cache 在数据拷贝时的加速功能。第二次实验在清除 Page Cache 后运行程序要比第一次没有清除 Page Cache 慢1倍左右。3.4 结论至此我们已经离真相不远了。Page Cache 解释了为什么这么快glibc 策略解释了为什么差距不大。而要想真正看到fread/fwrite的真正速度我们必须打破4KB 对齐的舒适区去模拟一个极端的高频小包 I/O 场景。4. 逼近最后的真相4.1 设计第二次实验并得出结果既然大块 I/O 拉不开差距那我们就来模拟小包高频的场景比如解析协议、处理串口数据等。我将BUFFER_SIZE改为16 字节再次测试 10MB 文件。#define BUFFER_SIZE 16我们这里要将两个拷贝程序中的宏定义用户态缓冲区大小改为16。然后清除之前程序运行产生的文件并重新编译。在每次运行程序之前清理一下 Page Cache 最终运行结果如下图4.2 第二次实验结果分析第二次实验中系统调用消耗时间为1.698秒而C库调用仅为0.05秒差距高达32倍。为了拷贝 10MB 数据每次搬运 16 字节这意味着程序发起了超过 130 万次的read/write系统调用。CPU 绝大部分时间都浪费在了用户态与内核态的上下文切换上导致效率极低。而C库调用通过其内部缓冲区将成百上千次的小额fwrite请求合并为一次底层的write系统调用。用廉价的内存拷贝memcpy换掉了昂贵的内核切换这就是 33 倍性能差距的来源。为了验证 C 库是如何处理这 16 字节请求的我又去查了strace发现确实是4096这足以说明数据先从用户态缓冲区的16字节到C库缓冲区攒够4096字节后再一次交给内核。因此第二次实验的两个拷贝程序的系统调用次数差距是非常大的所以消耗时间的差距也非常大。而对于系统调用的strace也就在预料之中了因为程序中我们设置的缓冲区大小就是16strace的结果如果不是 16 那才真的奇怪了。4.3 glibc 原来会去查 st_blksize 的值这里有个有趣的细节细心的读者可能发现虽然stdio.h中的BUFSIZ定义为 8192但在strace中看到的系统调用大小却是 4096。这是因为 glibc 的实现非常智能。在打开文件时它会通过fstat获取文件系统推荐的最佳 I/O 块大小(st_blksize)。在我的 Linux 虚拟机中Ext4 文件系统的块大小正是4KB。C 库顺势调整了缓冲策略以匹配这一物理特性从而避免了跨块读写带来的额外开销。我们观察一下测试文件的信息发现 I/O 块大小为 4096。实际上当fopen打开一个文件时glibc 的stdio实现会调用fstat系统调用去询问内核这个文件所在磁盘的最佳 I/O 块大小为多少。对于绝大多数 Linux 文件系统如 Ext4, XFS这个最佳块大小st_blksize都是4096 字节 (4KB)。#include stdio.h #include sys/stat.h ​ int main() { FILE *fp fopen(testfile.dat, rb); struct stat st; fstat(fileno(fp), st); printf(st_blksize %ld\n, st.st_blksize); // 通常 4096 fclose(fp); return 0; }想验证的读者可以使用这段代码我就不在贴出运行结果了。5. 系统调用 VS C库如何选择我们从 Page Cache 的干扰到 glibc 的旁路优化再到极限小包的性能碾压终于看清了 Linux I/O 的全貌。回到最初的问题read/write 和 fread/fwrite 到底谁更快5.1 实验结论复盘大块顺序 I/O (≥ 4KB)不相上下原因现代 Linux 内核的 Page Cache 和预读机制极其强大系统调用的开销被 I/O 延迟稀释。且 glibc 在大块对齐读写时会触发旁路优化直接透传数据两者底层的系统调用行为几乎一致。小块/随机 I/O ( 4KB)C库完胜原因stdio 的用户态缓冲区发挥了关键作用。它像一个高效的批发商将成千上万次碎片的read/write请求合并为少数几次系统调用。用廉价的内存拷贝memcpy换掉了昂贵的内核态上下文切换。5.2 决策指南那么在实际操作中我们该如何选择呢实际上对于大部分情况我们直接使用C库就好了只有在下面的特殊情况时才使用系统调用直接操作文件描述符第一种就是网络编程Socket通信必须操作fd且需要配合epoll/select使用。第二种是设备操作操作/dev/下的硬件设备或者需要ioctl控制时。第三种是特殊文件 I/O需要非阻塞 I/O(O_NONBLOCK)。需要直接 I/O(O_DIRECT) 绕过 Page Cache如数据库开发。或者需要文件元数据操作(ftruncate,fsync)。5.3 结语所谓的性能优化从来没有绝对的胜利者。系统调用赢在控制力C 库赢在效率与易用性。只有理解了从用户缓冲区 - C 库内部缓冲 - 系统调用 - Page Cache - 磁盘的整条链路你才能在面对不同的业务需求时写出真正高效、健壮的代码。6. 项目源码github链接
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

织梦如何做电商网站长春朝阳网站建设

评论Comment互动区开放:交流使用心得 在大模型技术飞速演进的今天,一个现实问题困扰着无数开发者:明明手握强大的预训练模型,却卡在了环境配置、脚本拼接和硬件适配这些“脏活累活”上。你是不是也经历过这样的场景——为了微调一…

张小明 2026/1/9 3:34:56 网站建设

上海网站开发薪资做网站费用会计科目

一、背景意义 随着计算机视觉技术的迅猛发展,物体检测领域逐渐成为了研究的热点之一。尤其是在体育领域,运动员的动作分析、比赛策略的制定以及运动表现的提升都离不开对运动关键点的精确检测。羽毛球作为一项技术性和竞技性兼备的运动,其运动…

张小明 2026/1/9 3:34:53 网站建设

黑色大气网站源码学校ftp服务器做网站

《高效网络文件传输与管理指南》 在网络环境中,文件的安全传输、备份以及非交互式下载是常见的需求。本文将介绍几种实用的工具和命令,帮助你轻松应对这些任务。 常用服务器命令 在与服务器交互时,有一些基本命令可以帮助我们完成文件的操作和管理,以下是一些常见命令的介…

张小明 2026/1/9 3:34:51 网站建设

网站域名一年多少钱专业做网站推广

还在为Spotify会员到期后无法继续欣赏心爱歌曲而烦恼吗?想要在任何时候都能离线享受高品质音乐体验?spotify-downloader就是您寻找的终极解决方案!这个强大的开源工具让您能够轻松将Spotify上的歌曲、专辑和歌单永久保存到本地。 【免费下载链…

张小明 2026/1/12 12:06:00 网站建设

小说网站开发l做网站推广的好处

Vue与React技术生态深度对比:基于Soybean Admin的架构迁移完整指南 【免费下载链接】soybean-admin A clean, elegant, beautiful and powerful admin template, based on Vue3, Vite6, TypeScript, Pinia, NaiveUI and UnoCSS. 一个清新优雅、高颜值且功能强大的后…

张小明 2026/1/13 0:20:43 网站建设

东莞网站建设要注意什么公司做自己的网站

解放双手!原神自动化工具BetterGI让你的游戏体验更轻松 【免费下载链接】better-genshin-impact 🍨BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动派遣 | 一键强化 - UI Automation Testing Tools Fo…

张小明 2026/1/10 18:28:16 网站建设