1. 为什么我建议用 CPU 当秒表来测硬盘和网络
先说一个可能颠覆认知的事实:你电脑里的 CPU,一秒钟能执行几十亿次基本操作。而你的硬盘,哪怕是最新的 NVMe 固态,随机读一次数据也要消耗几十微秒。这中间的差距,不是几倍,不是几十倍,而是上万倍。
大多数人电脑用着"感觉卡",其实并不是 CPU 不够快,而是 CPU 在等硬盘、等网络。这个"等"的过程,你感知到的只是"转圈圈",却没法量化到底等了多少。如果你能钻进 CPU 的视角,用它的时钟周期去给硬盘和网络测速,你会发现:原来每次我以为"很快"的操作,在 CPU 眼里已经漫长得像等了一整天。
这篇内容不是什么高深的理论课,而是我自己做的几个测量小实验。核心思路很简单:用 CPU 内部的时钟计数器来计时,分别测量内存访问、硬盘随机读、网络请求这些操作的耗时。读完你不仅能得到一个量化的数字,更重要的是建立一种"数量级直觉"——以后你再看到某个系统设计、某个框架选型,心里会立刻浮现出"这操作要花多少 CPU 周期"的衡量标准。
适合谁看?如果你写过代码,遇到过程序卡顿、接口超时、数据库查询慢这类问题,却说不清楚到底慢在哪,那这篇内容很值得花十分钟看完。就算你没写过底层代码,只要会跑几条命令,我也尽量把所有实验步骤写明白。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 秒表从哪来:认识 CPU 内部的 TSC 时钟
2.1 CPU 自带一个纳秒级秒表
现代 x86 处理器内部有一个叫 TSC(Time Stamp Counter,时间戳计数器)的寄存器,从 CPU 上电开始,它就以 CPU 的主频频率持续递增。比如一颗 3.2GHz 的 CPU,TSC 每秒钟增加约 32 亿次。也就是说,TSC 每一次递增,代表约 0.31 纳秒。
这玩意儿最大的价值在于:它不像 gettimeofday 或者 clock_gettime 那样要经过操作系统、要处理系统调用开销,而是 CPU 硬件原生的计数器。你在用户态直接读它,开销几乎是零。拿它来做微基准测试,精度是纳秒级的。
在 Linux 上,一条 rdtsc 指令就能把它读出来。C 语言里可以这样写:
c复制#include <stdint.h>
static inline uint64_t read_tsc() {
unsigned int lo, hi;
__asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
读出来的值除以 CPU 主频,就得到了从开机到现在经过的秒数。测量某个操作耗时,只需要在操作前后各读一次 TSC,相减得到周期数,再除以频率就是秒。
2.2 用之前必须注意的坑
TSC 虽然好用,但直接用会踩坑,我先说清楚:
第一,指令乱序执行。现代 CPU 为了提高效率,会乱序执行指令。rdtsc 本身不阻止乱序,所以你测出的时间可能偏大或偏小。严谨的做法是用 rdtscp 指令,它会等前面的指令都执行完再读数。或者直接用 lfence 加 rdtsc:
c复制static inline uint64_t read_tsc() {
unsigned long long tsc;
__asm__ volatile ("lfence; rdtsc" : "=A"(tsc) :: "memory");
return tsc;
}
第二,CPU 频率会变。笔记本上尤其明显,负载低时降到 1.2GHz,负载高时睿频到 4.5GHz。如果 TSC 也跟着变,那测出的周期数没法换算成时间。好在现代 CPU 的 TSC 大多已经是 constant_tsc 和 nonstop_tsc,也就是恒定频率,不随主频变化。你在 Linux 上可以用 grep tsc /proc/cpuinfo 看有没有这两个标志,有的话说明 TSC 是稳定的,可以直接按标称主频换算。
第三,多核迁移问题。线程可能在 CPU 核心间迁移,不同核心的 TSC 理论上应该同步,但保险起见测量时最好绑定一个核心。用 sched_setaffinity 可以做到:
c复制#include <sched.h>
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
sched_setaffinity(0, sizeof(set), &set);
搞定了这三个坑,你就拿到了一个精度约 1 纳秒、开销几乎为零的大自然馈赠秒表。
2.3 为什么不用系统自带的计时函数
你可能会问:clock_gettime(CLOCK_MONOTONIC) 不也能测吗?实测你会发现,一次 clock_gettime 调用本身的成本大约是 20~50 纳秒,而且它在底层其实也是基于 TSC 之类的硬件时钟来做的,只是中间包裹了一层虚拟化适配和系统调用的逻辑。
如果你的测量目标本身是微秒甚至毫秒级,用 clock_gettime 完全没问题。但我们这次要测的内存访问,是几十纳秒级别的操作——用 clock_gettime 去测,就像用一把最小刻度为厘米的尺子去量一根头发丝的直径,尺子本身的厚度比头发丝还粗,量出来的数字没有意义。
所以我的结论是:测纳秒级操作,直接读 TSC;测微秒级以上,clock_gettime 够用。两把尺子配合使用。
3. 硬盘速度实测:从内存到 SSD 再到机械盘的延迟阶梯
3.1 先用 CPU 测内存访问延迟
在测硬盘之前,先测一个"参照物"——内存。因为内存是 CPU 直接打交道的存储,理解了内存的速度,后面的对比才有意义。
内存延迟的经典测法是"指针追逐":构造一个链表,让每个节点的下一个节点地址是随机分布的,然后从头节点开始不断访问 node = node->next。这样 CPU 没法预取,每一次访问都必须真实地去内存里取数据,测出来的就是真实的内存随机访问延迟。
c复制#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#define ARRAY_SIZE (64 * 1024 * 1024) // 64MB 数组
#define STRIDE 64
static inline uint64_t read_tsc() {
uns
