A faster way to convert a timestamp to Hour, Min, Sec
A faster way to convert a timestamp to Hour, Min, Sec
将时间戳转换为时、分、秒的更快方法
50% faster than usual, by breaking the dependency chain. 通过打破依赖链,比通常速度快 50%。
7 September 2026 2026年9月7日
Nearly all major date/time libraries compute hour, minute and second the same way - slowly. In this article, I will dive deep into this little problem, and demonstrate a seemingly impossible claim: A daily timestamp [0..86399] can be converted into Hour, Minute and Second – with the latency of only two multiplications (and on some targets even lower!). 几乎所有主流的日期/时间库计算时、分、秒的方法都是一样的——而且很慢。在本文中,我将深入探讨这个小问题,并证明一个看似不可能的结论:每日时间戳 [0..86399] 可以转换为时、分、秒,且延迟仅需两次乘法(在某些目标平台上甚至更低!)。
This is in contrast to most date libraries which take around 16 cpu-cycles to compute. The speed gain will come from two main ideas (in order of importance): 这与大多数需要约 16 个 CPU 周期来计算的日期库形成了鲜明对比。速度的提升将主要来自两个核心思路(按重要性排序):
- A non-obvious reordering of the calculation steps, breaking the dependency chain.
- Optional math tricks, including converting a base-60 clock into binary-friendly base-64.
- 对计算步骤进行非直观的重排序,从而打破依赖链。
- 可选的数学技巧,包括将 60 进制时钟转换为对二进制友好的 64 进制。
Even if you don’t like low-level bit hacks in your code, there will be something here for you. 即使你不喜欢代码中的底层位操作技巧,这里的内容也同样适合你。
Traditional Approach 1
传统方法 1
Assume you have already reduced your timestamp to a date (not considered here) and time [0‥86399] (the subject of this article). We’ll first look at the way time is further decomposed in most date libraries, including the Linux kernel, glibc, Go’s standard library, CPython’s datetime, OpenJDK’s java.time. 假设你已经将时间戳简化为日期(此处不讨论)和时间 [0‥86399](本文的主题)。我们首先来看看大多数日期库(包括 Linux 内核、glibc、Go 标准库、CPython 的 datetime 和 OpenJDK 的 java.time)分解时间的方式。
The intuitive sequential approach: 直观的顺序方法:
hour = time / 3600
rem = time % 3600 // Compiles to: time - hour * 3600
minute = rem / 60
second = rem % 60 // Compiles to: rem - minute * 60
Notice that there is a complete dependency chain throughout this entire process: second depends on minute, which depends on rem, which depends on hour. There is nothing here that can overlap, or be parallelised. 请注意,整个过程中存在一条完整的依赖链:秒依赖于分,分依赖于余数(rem),而余数又依赖于时。这里没有任何部分可以重叠或并行化。
Traditional Approach 2
传统方法 2
Some date libraries take a slightly different approach, computing each component independently. Sometimes this decision is driven by the shape of the API (e.g., if each component is calculated in a separate function), other times it’s a conscious choice. Date libraries using this approach include V8, Boost, and musl. 一些日期库采用了略有不同的方法,即独立计算每个组件。有时这种决定是由 API 的形式驱动的(例如,如果每个组件都在单独的函数中计算),有时则是有意的选择。使用这种方法的日期库包括 V8、Boost 和 musl。
The “three separate components” approach: “三个独立组件”方法:
hour = time / 3600
minute = (time / 60) % 60
second = time % 60
This might be a little surprising at first that this can be faster. When we trace through the computations, we get this table on the right. The dependency chain has been broken! But, there is a downside. Traditional Approach 1 had 8 computations, whereas this one has 10. 起初,这可能会让人感到惊讶,因为这种方法竟然更快。当我们追踪计算过程时,会发现依赖链被打破了!但它也有缺点:传统方法 1 有 8 次计算,而这种方法有 10 次。
Optimisation: Fixed-Point Math
优化:定点数学
As is often the theme in this “fast date” series of articles, manually implementing a mul-shift for division can speed things up on some processors. 正如这一系列“快速日期”文章中常见的主题一样,在某些处理器上手动实现用于除法的“乘法-移位”(mul-shift)可以加快速度。
Standard division: 标准除法:
u32 a = x / 60
u32 b = x / 3600
Fixed-point approach: 定点方法:
u32 a = (u64) x * 71582789 >> 32
u32 b = (u64) x * 1193047 >> 32
The mul-shifts are collectively accurate for over 626 hours. This means that if the input is bounded to just one day [0‥86399], then the substitutions are valid, and will provide a speed boost on 32-bit and x86 processors, and some SIMD chips. 这些“乘法-移位”操作在超过 626 小时的范围内都是准确的。这意味着如果输入限制在一天之内 [0‥86399],那么这些替换是有效的,并且会在 32 位和 x86 处理器以及某些 SIMD 芯片上提供速度提升。