标准库最关键的作用不是提供函数,是攒着一起交:同一个文件写 300 个字节,带缓冲与每次直写差出一个数量级;它把你的一百次改成一两次,省下的是过路费,不是算法。
2.1 是门、2.2 是传达室,这一节是屋里的办事员——它自己也不进内核,只是把你的请求攒起来、整理好,再一次性交给传达室。
一、三层叠在一起长什么样
从下往上数:系统调用(过门搬数据)→ 包装函数(翻错误、查参数)→ C 标准库(缓冲、格式、分配)。
你在 C 里写 printf("hello"),这一句要经过:标准库把字符串塞进自己的缓冲区→ 缓冲区满了才调 write → write 是包装函数 → 最后真的过门。
没满的时候,一次也没进内核。这就是缓冲的意思:先攒着。
二、缓冲为什么这么值钱
第 1 章那笔账还记得:过门有固定开销,与搬多少数据无关。 所以 300 个字节分 300 次写,就是 300 次过门;攒起来一次写,只过 1 次门。 下面 Demo 量了这个差:带缓冲与每次直写,差出一个数量级。 而且这还不是最极端的情况——真正的差别在磁盘上:一次写 4KB 与写 300 次 1 字节,落到盘上是【一次寻道】与【三百次寻道】的差别。
缓冲区大小是有讲究的:太小省不下钱,太大浪费内存,还要考虑和文件系统块大小、页大小对齐。标准库默认取的值(通常是 4KB~8KB)就是这套权衡的结果。
三、标准库还管哪些事
格式化:printf 家族的格式串解析、scanf 的反向解析。这部分其实很贵——一次 printf 里那点解析可能比一次 write 还费。
字符串:strlen、strcpy、memcpy。这些通常被编译器内联或换成更快的指令,所以别自己手写循环去【优化】它们。
数学:sin、sqrt。很多 CPU 有对应指令,标准库只是包了一层。
内存分配:malloc/free。这是最容易被误解的一个——它大部分时候不进内核,只是从自己手里的空闲链表里切一块给你;真的不够了才向内核要(brk/mmap)。
所以 malloc 快、free 慢、小块多要很多次也不一定慢——这些现象都能用它【在用户态攒着】解释。
四、Python 里为什么也看得见这一层
print 有缓冲:输出到终端时行缓冲,重定向到文件时块缓冲——所以你在管道里看到的输出顺序可能和终端里不一样。
sys.stdout.flush() 就是【别攒了,现在就交】。
读文件也一样:open() 返回的对象自带缓冲,f.read(1) 读 1024 次可能只进内核一两次。想每次都真读,得用 os.read。
这件事有个实际后果:如果你用 f.read(1) 去量系统调用,量到的是 Python 的缓冲,不是内核的速度——第 1 章那个 Demo 特意绕开了它。
五、动手 Demo:C 标准库站在哪一层——缓冲这件事最能说明问题
同一个文件写 300 个字节:一次走标准库的缓冲,一次绕过它直接 os.write。
这两次写的字节数完全一样,差别只在【过几次门】。
# ① 走标准库缓冲:300 次 write 攒在对象里,最后一次性交
with open(p, "w", encoding="utf-8") as f:
for _ in range(300):
f.write("x")
# ② 绕开缓冲:每次 os.write 都要过门
fd = os.open(p, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
for _ in range(300):
os.write(fd, b"x")
os.close(fd)
实跑输出:
--------------------------------------------------------------------
C 标准库站在哪一层——缓冲这件事最能说明问题
--------------------------------------------------------------------
① 写 300 个字节,两种写法
写法 总耗时 进内核几次
-----------------------------------------
标准库带缓冲 1.7 毫秒 少数几次
每次直接写 3.4 毫秒 300 次
前者比后者省 2.0 倍
**标准库最关键的作用不是【提供函数】,是【攒着一起交】。**
**同一件事,攒 300 次交一次,和跑 300 趟,差的不是代码长短,是过路费。**
② 三层各管什么,一句话
层 例子 它替你办的事
----------------------------------------------------------
系统调用 open / read / write 只负责过门搬数据
包装函数 glibc 的 open() 翻错误码、管参数
C 标准库 fopen / printf 再攒一层缓冲、管格式
你的程序 读一行、写一句 只管业务
**一本书讲【用户模式实现的功能】,讲的就是最上面这三层。**
它们都在用户模式里跑,一次也没进内核——除了那几次过门。
两次写出的文件一模一样,字节数一样、内容一样。差的只是 300 次过门与 1 次过门。 这就是标准库真正的价值:它不教你新算法,它替你攒。 记住这条判断法:当你发现【同样的活儿,写法不同但结果一样、速度差很多】,先问一句——是不是一个在批量、一个在逐次?
📋 在真 Linux 上验证:strace -e trace=write ./程序,带缓冲的版本只会打印一两次 write,直接写的版本会打印三百行。这是肉眼可见的【谁在攒】。
30 秒实验
python -c "print('a'); print('b')" > /tmp/o.txt然后cat /tmp/o.txt,把输出重定向到终端与重定向到文件各跑一次,感受行缓冲与块缓冲的区别。strace -e trace=write python -c "print('x')",数一下这一句 print 实际发了几次 write。- 在 Python 里对同一个文件分别用
f.read(1)循环 1000 次与os.read(fd, 1)循环 1000 次,用strace -c对比系统调用次数。
常见误区
- 【printf 就是往屏幕上写字】。它先把字排进缓冲区,屏幕是终端程序画的,内核只负责送字节到 tty。
- 【malloc 每次都要进内核】。大部分
malloc只是在用户态的堆里切一块,只有手里的地不够了才向内核要。所以小块频繁申请不一定慢。 - 【缓冲越大越好】。太大浪费内存、增加延迟(数据躺在缓冲区里还没落盘),而且断电时缓冲区里的东西会丢——所以关键数据要
flush或fsync。
自测三问(点开对答案)
1. 标准库最关键的作用是什么?为什么它值钱? —— **攒着一起交**(缓冲)。因为过门开销固定,把 N 次合成 1 次,省下的全是固定开销。 2. malloc 为什么大部分时候不进内核? —— 它先从自己维护的空闲链表里切一块(用户态),只有不够了才用 `brk`/`mmap` 向内核要。 3. 用 f.read(1) 量系统调用速度,量到的是什么? —— 量到的是 Python 对象的缓冲,**不是内核**。要量真系统调用必须用 `os.read`。- ☐ 2.4 OS提供的程序
相关笔记
- 系统调用的包装函数 — 下面那层传达室
- 页面缓存 — 内核自己也在攒
- OS提供的程序 — 最上面那层
- 🐧 Linux是怎样工作的 · 索引