定时器: 石心

334 阅读6分钟

在看 Node http 模块文档的时候, 才留意到 server.timeout 这个属性, 本想简单介绍一下, 但是在梳理过后发现关于 timeout 有庞大的内容支撑: server.timout -> node core timers -> uv timers -> linux msleep/hrtimer -> clocksource -> tsc -> cmos rtc -> clock cycle, 所以拆分成几部分大致做下介绍, 期望定时器系列结束之后, noder 能够大致明白: clock cycle 是如何驱动 linux 的 msleep/hrtimer; linux 的 timers 与 uv timers 的关系; node timers 与 uv timers的关系。

定时器是编程中运用最广的特性之一, 那么定时器实现的基本原理是什么呢? 无论多么复杂计算机,当一层层退去她的外衣,她的内核只是一块可以发出固定频率脉冲的石心(石英), 好比人类的心脏,通过跳动给整个躯体提供能量和信号。

周期 与 精度

时钟周期, CPU周期[min(取指令)], 指令周期[取指令+执行指令], 微指令周期[时钟周期] tick.jpg

在 intel haswell 微架构内核架构中, 一条指令通常会被分解微多条微指令, 一个时钟周期,理论上最多可以并行执行8个微指令。

时钟周期与时间关系:

频率(HZ)11K1M1G1000G
时间(/Hz)1s1ms1us1ns1ps

目前普通的计算机CPU频率都在GHz级别, 以鄙人电脑为例: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz, 意味这每秒钟有 1.6e9 次时钟周期。一个时钟周期大约为 0.625ns,所以目前来说普通的机器的定时器精度最多只能达到 ns 纳秒级别,那精度到底是 1n,10ns, 100ns 级别?要解决这个问题,需要从(微)指令的周期说起。在 linux 内核中 ,timer 最底层(本质是计数器)的一个指令是 ADD 1, 纵然在流水线的加持下,也避免不两个最基本的且具有先后管线的微指令: 运算和回写,所以最小的运算操作都需要0.625ns*2 = 1.25ns ,那么目前定时器最经最大应该在几个纳秒左右。

原本期望能够大该算出 linux 的 hrtimer 的精度范围, 实在因为 CPU 和 hrtimer 太复杂,个人知识有限,无法估计出来。目前 intel 最高睿频 10GHz 的情况下,如果如果能在10个时钟周期内完成一次while-schedule循环,也许。。。

本质 --- while + schedule

讲一个笑话: 双11凌晨开始大促销

while (true) {
	const now = new Date();
	if (now === '双11凌晨') {
		break;
	}
}
开始优惠();

虽然该程序或许存在一点点阻塞问题,但不可否认可以实现定时, 其实这就是定时器的本质。

加入非阻塞

while (true) {
	const now = new Date();
	if (now === '双11凌晨') {
		break;
	}
	schedule();// 调度让出 cpu 操作
}
开始优惠();

支持多个定时器

const timers = Some Data Structur[timer];
。。。
while (true) {
    const latest_timer = timers.peek(); // get the latest timer
	if (llatest_timer && atest_timer === '双11凌晨') {
		break;
	}
	schedule();// 调度让出 cpu 操作
}
开始优惠();

剩余部分就是考虑在什么场景下,采用什么数据结构来保存定时器数据,从而可以实现C|U|R|D的最优(通常是时间最优)

参考

CPU流水线的探秘之旅 64-ia-32-architectures-optimization-manual

在看 Node http 模块文档的时候, 才留意到 server.timeout 这个属性, 本想简单介绍一下, 但是在梳理过后发现关于 timeout 有庞大的内容支撑: server.timout -> node core timers -> uv timers -> linux msleep/hrtimer -> clocksource -> tsc -> cmos rtc -> clock cycle, 所以拆分成几部分大致做下介绍, 期望定时器系列结束之后, noder 能够大致明白: clock cycle 是如何驱动 linux 的 msleep/hrtimer; linux 的 timers 与 uv timers 的关系; node timers 与 uv timers的关系。

定时器是编程中运用最广的特性之一, 那么定时器实现的基本原理是什么呢? 无论多么复杂计算机,当一层层退去她的外衣,她的内核只是一块可以发出固定频率脉冲的石心(石英), 好比人类的心脏,通过跳动给整个躯体提供能量和信号。

周期 与 精度

时钟周期, CPU周期[min(取指令)], 指令周期[取指令+执行指令], 微指令周期[时钟周期]

在 intel haswell 微架构内核架构中, 一条指令通常会被分解微多条微指令, 一个时钟周期,理论上最多可以并行执行8个微指令。

时钟周期与时间关系:

频率(HZ)11K1M1G1000G
时间(/Hz)1s1ms1us1ns1ps

目前普通的计算机CPU频率都在GHz级别, 以鄙人电脑为例: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz, 意味这每秒钟有 1.6e9 次时钟周期。一个时钟周期大约为 0.625ns,所以目前来说普通的机器的定时器精度最多只能达到 ns 纳秒级别,那精度到底是 1n,10ns, 100ns 级别?要解决这个问题,需要从(微)指令的周期说起。在 linux 内核中 ,timer 最底层(本质是计数器)的一个指令是 ADD 1, 纵然在流水线的加持下,也避免不两个最基本的且具有先后管线的微指令: 运算和回写,所以最小的运算操作都需要0.625ns*2 = 1.25ns ,那么目前定时器最经最大应该在几个纳秒左右。

原本期望能够大该算出 linux 的 hrtimer 的精度范围, 实在因为 CPU 和 hrtimer 太复杂,个人知识有限,无法估计出来。目前 intel 最高睿频 10GHz 的情况下,如果如果能在10个时钟周期内完成一次while-schedule循环,也许。。。

本质 --- while + schedule

讲一个笑话: 双11凌晨开始大促销

while (true) {
	const now = new Date();
	if (now === '双11凌晨') {
		break;
	}
}
开始优惠();

虽然该程序或许存在一点点阻塞问题,但不可否认可以实现定时, 其实这就是定时器的本质。

加入非阻塞

while (true) {
	const now = new Date();
	if (now === '双11凌晨') {
		break;
	}
	schedule();// 调度让出 cpu 操作
}
开始优惠();

支持多个定时器

const timers = Some Data Structur[timer];
。。。
while (true) {
    const latest_timer = timers.peek(); // get the latest timer
	if (llatest_timer && atest_timer === '双11凌晨') {
		break;
	}
	schedule();// 调度让出 cpu 操作
}
开始优惠();

剩余部分就是考虑在什么场景下,采用什么数据结构来保存定时器数据,从而可以实现C|U|R|D的最优(通常是时间最优)

参考

CPU流水线的探秘之旅

64-ia-32-architectures-optimization-manual

关注我的微信公众号"SUNTOPO WLOG",欢迎留言讨论,我会尽可能回复,感谢您的阅读。