为什么 (n + t - 1) // t
比 ceil(n / t)
更高效?
导言
在刷leetcode的时候,发现了用
(n + t - 1) / t
替换ceil(n/t)
从而带来的性能优化原理:(因为没有浮点转换带来的性能损耗)
在处理数组分块、分页计算等场景时,我们经常需要计算向上取整的除法。例如,将长度为103的数组分成长度为10的块,需要计算ceil(103/10)=11
。
常见的实现方式有两种:
- 直接向上取整:
math.ceil(n / t)
math.ceil(103/10)=11
- 整数运算模拟:
(n + t - 1) // t
(这里//
表示纯整数除法)(103+10-1)//t=11
虽然结果相同,但后者在多数情况下更高效(因为没有浮点转换带来的性能损耗)。
数学原理(太妙啦啦啦啦啦啦啦啦啦)
偏移量设计
选择t-1
作为偏移量的原因:
-
当n能被t整除时(余数r=0):
n = k*t
(n + t - 1) // t = (k*t + t - 1) // t = k + (t-1)//t = k
(正确)
-
当n不能被t整除时(余数r≥1):
n = k*t + r
(n + t - 1) // t = (k*t + r + t - 1) // t = k + (r + t - 1)//t = k + 1
(正确)
示例验证
python
# 整除情况
(4 + 2 - 1) // 2 = 5 // 2 = 2 # 等同于ceil(4/2)
# 非整除情况
(5 + 2 - 1) // 2 = 6 // 2 = 3 # 等同于ceil(5/2)
性能分析
ceil(n / t)
的问题
大多数语言在实现这个函数的时候,内部都会使用强制浮点运算
-
强制浮点运算:
- 整数→浮点转换
- 浮点除法
- 浮点取整
强制浮点运算会带来性能损耗
(n + t - 1) // t
的优势
这里的
//
在python中表示纯整数除法
pythonsum = (103+10-1)//10 = 11
在java或者C/C++中只要类型是
int
,计算除法的时候就会触发纯整数除法
javaint sum = (103+10-1)/10 = 11
-
纯整数运算:
- 直接使用CPU整数除法指令
- 无类型转换开销
更稳定不受浮点精度限制,需要纯整数运算支持,例如下面的语言
语言特性差异
语言 | 推荐写法 | 原因 |
---|---|---|
C/C++/Java | (n + t - 1) / t |
整数除法直接截断 |
Python | (n + t - 1) // t |
// 避免浮点运算 |
JavaScript/TS | Math.ceil(n / t) |
JS和TS中的所有除法都是浮点运算,使用Math.ceil可读性优先 |
高性能场景 | (n + t - 1) >> k (t=2^k) |
位运算最快 |
建议
-
优先考虑整数运算:
- 在支持整数除法的语言中使用
(n + t - 1) // t
- 在支持整数除法的语言中使用
-
可读性与性能平衡:
- 在JS/TS等语言中,
Math.ceil
的可读性更佳
- 在JS/TS等语言中,
-
特殊场景优化:
- 当除数为2的幂次时,可用位运算进一步优化
-
避免过早优化:
- 非性能关键路径可优先考虑代码可读性
总结
(n + t - 1) // t
通过巧妙的数学设计,在多数编程语言中实现了:
- 更高效的纯整数运算
- 避免浮点转换开销
- 更好的大数稳定性
6666666666666666666