用计算证明:我远远低估了编译器
前情回顾
在将正整数扩大1000倍
的计算当中,朋友提出了计算机二进制算法理论:
计算机会进行正整数 X 512 + 正整数 X 256 + 正整数 X 128 + 正整数 X 128 + 正整数 X 64 + 正整数 X 32 + 正整数 X 8
,一直累加到凑齐 正整数的 1000 倍 为止。
而运算正整数 X 2的10次方 - ( 正整数 X 16 + 正整数 X 8)
要比那一串加号更快接近结果。
但是当我使用解释型语言PHP
进行验算 如何使用PHP最高效率的将一个正整数扩大一千倍? 后却得出了相反的结论:只进行一次运算比进行两次计算要更快。
于是我猜测可能是编译型语言
与解释型语言
的差异导致的。围绕这个观点展开了论证。
验算
代码部分
代码价值较低,为不影响阅读,置于附录
运算结果
将代码执行了数十次之后我得出了一个有些出乎意料的结论:方案三对方案二只有微乎其微的优势,甚至可以说两者的计算效率没有差距!
以下贴出两个比较有代表性的结果
修正运算
改变计算量级
后来我将计算量上升至10亿次
,得出的结论也差不多。只靠这个结论我很难说服自己方案三比方案二更值得选择。但是运算结果也不合理:两者明明采取了不同的计算方案,为什么无法产生差距?
与其说计算时间没有差距,不如说现有的运行时间全部都消耗在了for
, rand
, 和time
上。既然如此,那么提升计算复杂度,来降低过程类计算的干扰是否会得出结果呢?
改变计算复杂度
我将随机数的范围取从 999
改为了 9999999999
,将乘数从 1000
改为了 8,589,934,590(2的33次方减2)
同样各进行一千万次计算,在其他处理步骤不变的情况下,计算耗时被明显延长,但两种方案的计算时长依旧没能拉开差距。
按照豆豆告诉我的理论,integer * 8,589,934,590
会被解析integer * 4,294,967,296 + integer * 2,147,483,648 ...
一直累加30多次来累计到目标乘积,而integer * 8,589,934,592 - integer * 2
则避免了这样的运算。试验结论却不能支持这个观点。
再次讨论
带着这样的数据,我再次找到了豆豆。这次修行更进一步的豆豆同学嘲讽我一番后直接甩给我两篇知乎:
我的理解
两种方案的计算之所以没有产生时间差距,是因为两种写法经过编译之后,都运行了同样的算法。
- 我:我想让编辑器做一个复杂步骤的计算,再做一个简单步骤的计算,用两种计算的耗时差距来证明计算机的算法。
- 编译器:没想到吧崽种,我早已预判了你的预判!
要将编译器的预判囊括入我的预判,这一点重新升格为大神的豆豆同学
也给出了方案
你把你博客上的代码反汇编成汇编语言,看一看,就知道为什么时间差别不大了
但是编译器的算法早已经超出了蹒跚学步初学者的理解范畴。对于当前学习阶段的我而言,通过本次试验和讨论窥探一眼就好,看多了容易掉 SAN 值。日后若窥探到更多的内容,我会再写一篇博客出来,欢迎关注。
附录
附录代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| func main() { maxI := 10000000
fmt.Println("方案1:拼接法")
rand.Seed(time.Now().UnixNano())
echoString := ""
for count := 0; count < 3; count++ { start := time.Now() echoString += "第" + strconv.Itoa(count + 1) + "次:"
for i := 0; i < maxI; i++ { integer := rand.Intn(999) _, _ = strconv.Atoi(strconv.Itoa(integer) + "000") }
end := time.Now()
betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000
echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒" }
fmt.Println(echoString) echoString = ""
fmt.Println("方案2:乘1000") for count := 0; count < 3; count++ { start := time.Now() echoString += "第" + strconv.Itoa(count + 1) + "次:"
for i := 0; i < maxI; i++ { integer := rand.Intn(999) _ = integer * 1000 }
end := time.Now()
betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000
echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒"
}
fmt.Println(echoString) echoString = ""
fmt.Println("方案3:乘以1024") for count := 0; count < 3; count++ { start := time.Now() echoString += "第" + strconv.Itoa(count + 1) + "次:"
for i := 0; i < maxI; i++ { integer := rand.Intn(999) _ = (integer * 1024) - (integer * 24) }
end := time.Now()
betweenTime := float64(end.Sub(start).Nanoseconds()) / 1000000000
echoString += strconv.FormatFloat(betweenTime, 'f', 10, 64) + "秒"
}
fmt.Println(echoString)
}
|