Skip to content

Commit 69b6f86

Browse files
committed
优化“ 为什么处理排序的数组要比非排序的快”一文
1 parent eba95e7 commit 69b6f86

File tree

2 files changed

+31
-20
lines changed

2 files changed

+31
-20
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ stackoverflow-Java-top-qa
3939
4040
* [LinkedList、ArrayList各自的使用场景,如何确认应该用哪一个呢?](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/when-to-use-linkedlist-over-arraylist.md)
4141
* [StringBuilder和StringBuffer有哪些区别呢](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/stringbuilder-and-stringbuffer.md)
42+
* [为什么处理排序的数组要比非排序的快](https://github.com/giantray/stackoverflow-java-top-qa/blob/master/contents/Why-is-processing-a-sorted-array-faster-than-an-unsorted-array.md)
4243

4344

4445
### 待翻译问题链接(还剩x问题)
45-
- [Why is processing a sorted array faster than an unsorted array?](http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array)
4646
- [Why is subtracting these two times (in 1927) giving a strange result?](http://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result)
4747
- [Proper use cases for Android UserManager.isUserAGoat()?](http://stackoverflow.com/questions/13375357/proper-use-cases-for-android-usermanager-isuseragoat)
4848
- [Creating a memory leak with Java [closed]](http://stackoverflow.com/questions/6470651/creating-a-memory-leak-with-java)

contents/Why-is-processing-a-sorted-array-faster-than-an-unsorted-array.md

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,36 +95,43 @@ public class Main
9595
### 回答
9696
#### 什么是分支预测?
9797
看看这个铁路分岔口
98+
![](http://i.stack.imgur.com/muxnt.jpg)
99+
Image by Mecanismo, via Wikimedia Commons. Used under the CC-By-SA 3.0 license.
100+
98101
为了理解这个问题,想象一下,如果我们回到19世纪.
102+
99103
你是在分岔口的操作员。当你听到列车来了,你没办法知道这两条路哪一条是正确的。然后呢,你让列车停下来,问列车员哪条路是对的,然后你才转换铁路。
104+
100105
**火车很重有很大的惯性。所以他们得花费很长的时间开车和减速。**
106+
101107
是不是有个更好的办法呢?你猜测哪个是火车正确的行驶方向
102108
- 如果你猜对了,火车继续前行
103109
- 如果你猜错了,火车得停下来,返回去,然后你在换条路。
104110

105111
**如果你每次都猜对了,那么火车永远不会停下来。**
106-
**如果你猜错太多次,那么火车会花费很多时间来停车,返回,然后在启动**
112+
**如果你猜错太多次,那么火车会花费很多时间来停车,返回,然后再启动**
107113

108114
----------------------------------------------------------
109115
**考虑一个if条件语句**:在处理器层面上,这是一个分支指令:
110-
http://i.stack.imgur.com/pyfwC.png
111-
你是个处理器当你看到一个分支的时候。你没办法知道下一条指令在哪的时候,你该怎么办呢?你暂停执行一直等到前面的指令完成,然后你在继续执行正确的下一条指令。
112-
现代的处理器很复杂并且有很长的管道。所以他需要很长的时间"热身""冷却"
116+
![](http://i.stack.imgur.com/pyfwC.png)
117+
当处理器看到这个分支时,没办法知道哪个将是下一条指令。该怎么办呢?貌似只能暂停执行,直到前面的指令完成,然后再继续执行正确的下一条指令?
118+
现代处理器很复杂,因此它需要很长的时间"热身""冷却"
113119

114120
是不是有个更好的办法呢?你猜测下一个指令在哪!
115121
- 如果你猜对了,你继续执行。
116-
- 如果你猜错了,你需要清理管道,返回到那个出错的分支,然后你才能继续。
122+
- 如果你猜错了,你需要flush the pipeline,返回到那个出错的分支,然后你才能继续。
117123

118124
**如果你每次都猜对了**,那么你永远不会停
119125
**如果你猜错了太多次**,你就要花很多时间来滚回,重启。
120126

121127
-------------------------------------------------------
122-
这就是分支预测。我承认这不是一个好的类比,因为火车可以用旗帜来作为方向的标识。但是在电脑中,处理器不会知道哪一个分支直到走到最后的时候。
123-
所以怎样能很好的预测,在尽可能的使火车必须返回的次数变小?你看看火车之前的选择历史,如果这个火车往左的概率是99%。那么你猜左,反之亦然。如果每三次走这条路,你选择也按这个规律。
128+
这就是分支预测。我承认这不是一个好的类比,因为火车可以用旗帜来作为方向的标识。但是在电脑中,处理器不能知道哪一个分支将走到最后。
129+
130+
所以怎样能很好的预测,尽可能地使火车必须返回的次数变小?你看看火车之前的选择过程,如果这个火车往左的概率是99%。那么你猜左,反之亦然。如果每3次会有1次走这条路,那么你也按这个三分之一的规律进行。
124131

125132
**换句话说,你试着定下一个模式,然后按照这个模式去执行**。这就差不多是分支预测是怎么工作的。
126133

127-
大多数的应用都有很好的分支预测。所以现代的分支预测者通常能实现大于90%的命中率。但是当面对没有模式识别的无法预测的分支,那分支预测基本就没用了。
134+
大多数的应用都有很好的分支预测。所以现代的分支预测器通常能实现大于90%的命中率。但是当面对没有模式识别、无法预测的分支,那分支预测基本就没用了。
128135

129136
如果你想知道更多:[Branch predictor" article on Wikipedia](https://en.wikipedia.org/wiki/Branch_predictor).
130137

@@ -135,9 +142,9 @@ http://i.stack.imgur.com/pyfwC.png
135142
if (data[c] >= 128)
136143
sum += data[c];
137144
```
138-
注意到数据是平局分布在0到255之间的。当数据排好序后,大概有一半的的迭代数据不会进入这个条件语句,在这之后的数据会进入该条件语句.
145+
注意到数据是分布在0到255之间的。当数据排好序后,基本上前一半大的的数据不会进入这个条件语句,而后一半的数据,会进入该条件语句.
139146

140-
连续的进入同一片区域很多次,这对分支预测是非常友好的。甚至一个简单的饱和计数器会成功预测出分支除了一些需要转换的数据
147+
连续的进入同一个执行分支很多次,这对分支预测是非常友好的。可以更准确地预测,从而带来更高的执行效率
141148

142149
##### 快速理解一下
143150
```
@@ -173,7 +180,7 @@ sum += ~t & data[c];
173180
```
174181
这消灭了分支,把它替换成按位操作.
175182

176-
(说明:这个技巧不是非常严格的等同于原来的if条件语句。但是在`data[]`所有输入值这个情况下是用的
183+
(说明:这个技巧不是非常严格的等同于原来的if条件语句。但是在`data[]`当前这些值下是OK的
177184

178185
**使用的设备参数是:Core i7 920 @ 3.5 GHz**
179186
C++ - Visual Studio 2010 - x64 Release
@@ -204,23 +211,24 @@ seconds = 3.113581453
204211
// Branchless - Sorted
205212
seconds = 3.186068823
206213
```
207-
意见
208-
- 用了分支:没有排序和排序的数据有很大的区别
209-
- 用了上面的技巧:对于排没排序的数据,没有很大的区别
210-
- 在使用C++的情况下,用了小技巧事实上比用排好序的分支要慢上一点点
214+
结论
215+
- 用了分支(if):没有排序和排序的数据,效率有很大的区别
216+
- 用了上面提到的按位操作替换:排序与否,效率没有很大的区别
217+
- 在使用C++的情况下,按位操作还是要比排好序的分支操作要慢
211218

212219
一般的建议是尽量避免在关键循环上出现对数据很依赖的分支。(就像这个例子)
213220

214221
------------------------------------------------
215222

216223
更新:
217224

218-
- GCC 4.6.1 用了 `-O3` or `-ftree-vectorize`在64位机器上,数据有没有排序,都是一样快。
225+
- GCC 4.6.1 用了 `-O3` or `-ftree-vectorize`在64位机器上,数据有没有排序,都是一样快。
219226
**...**
220227
**...**
221-
**...**
222-
等等一下
223-
说明了现代编译器发展的已足够成熟能够各种疯狂的优化代码
228+
**...等各种例子**
229+
230+
231+
说明了现代编译器越发成熟强大,可以在这方面充分优化代码的执行效率
224232

225233
### 相关内容
226234

@@ -240,4 +248,7 @@ CPU的流水线指令执行
240248

241249

242250
**stackoverflow链接**
251+
252+
这个问题的所有回答中,最高的回答,获取了上万个vote,还有很多个回答,非常疯狂,大家觉得不过瘾可以移步到这里查看
253+
243254
http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array

0 commit comments

Comments
 (0)