当前位置: 首页 > news >正文

stackoverflow:为什么排序后的数组要比未排序数组运行快3倍以上?

周末,在 stackoverflow 上面发现一个有趣的问题:Why is it faster to process a sorted array than an unsorted array?


a0402616c4c90e890ca78a32bae4d2f291c5aac7

提问者用256的模数随机填充一个固定大小的大数组,然后对数组的一半元素求和,并分别用 C++ 和 Java 代码来证实了这一现象,本文主要用 Java 来说明,代码如下。


9159e748b29ce57ae3d90b99d4c2c66534c0b6b1

我通过 IDEA 运行发现,先排序后计算比不排序直接计算快三倍。

那么,问题来了,本例子中的代码在数组填充时已经加入了分区函数,充分保证填充值的随机性,计算时也是按一半的元素来求和,所以不存在特例情况。而且,计算也完全不涉及到数据的有序性,即数组是否有序理论上对计算不会产生任何作用。在这样的前提下,为什么排序后的数组要比未排序数组运行快3倍以上?

我看获赞最高的答案提到了一个关键字:分支预测器

它的英文名叫做 Branch predictor,是一种数字电路,在分支指令执行结束之前猜测哪一路分支将会被运行,以提高处理器的指令流水线的性能。使用分支预测器的目的,在于改善指令管线化的流程。—— 引自维基百科

这个分支预测器,跟在无线电还未普及的 19 世纪的铁路交叉口的扳道工很相似,假如你就是一个搬道工,当听到火车快来了的时候,你无法猜测它应该朝哪个方向走。于是你叫停了火车,上前去问火车司机该朝哪个方向走,以便你能正确地切换铁轨。


fbc5cc61c0636792b6d8b96b0c08e96a8c333f89要知道,火车是非常庞大的,切急速行驶时有巨大的惯性。为了完成上述停车-问询-切轨的一系列动作,火车需耗费大量时间减速,停车,重新开启。

既然上述过程非常耗时,那是否有更好的方法?当然有!当火车即将行驶过来前,你可以猜测火车该朝哪个方向走。

如果猜对了,它直接通过,继续前行。

如果猜错了,车头将停止,倒回去,你将铁轨扳至反方向,火车重新启动,驶过道口。

如果你不幸每次都猜错了,那么火车将耗费大量时间,停车-倒回-重启。

如果你很幸运,每次都猜对了呢?火车将从不停车,持续前行!

我相信谁都不会一直都有好运,那有没有好的方法使得每次猜对的几率变大呢?是有的,可以通过小旗子来作为信号判断火车的走向。

是不是很形象?这样你就知道引起本文代码出现这一现象的关键原因在于分支跳转指令


c01c0f31273aefaaed1ee97bf9fc5c88ffd8d69d

那么问题又来了,处理器是采用什么策略用最小的次数来尽量猜对指令分支的下一步走向呢?

一般来说,对于处理器而言,只能利用历史运行记录来进行分析。

由于上例 data 数组里的元素是按照 0-255 的值被均匀存储的,数组 data 有序时,前面一半元素的迭代将不会进入 if-statement,超过一半时,元素迭代将全部进入 if-statement。

具体分析如下。

有序数组的分支预测流程:


a34a52ebfea6bbdb1eaa07e0d13a7871a901d10e

无序数组的分支预测流程:


dfd04e4c88f614a725ab6684ebc50ff187293245

所以,咱们可以得出结论:造成无序数组耗时较多的原因在于它迭代过程中 50% 的错误率。

那问题来了,对于这个例子,有没有好的优化方案呢?

是有的,可以利用位运算来取消分支跳转,代码如下。


ccd5a3cf4eec5bf2952174b8d9ce1940b2996f44

更多的位运算 hack 操作,可以参考 bithacks。

这个例子是不是很有趣啊?

如果你涨知识了,记得转发哦。

参考

https://en.wikipedia.org/wiki/Branch_predictor

http://graphics.stanford.edu/~seander/bithacks.html

https://stackoverflow.com/questions/11227809/why-is-it-faster-to-process-a-sorted-array-than-an-unsorted-array


原文发布时间为: 2018-11-27
本文作者:Java面试那些事儿
本文来自云栖社区合作伙伴“Java面试那些事儿”,了解相关信息可以关注“Java面试那些事儿”。

相关文章:

  • 胡小林:把日常生活中碰到的事变成我们发露忏悔的机会
  • 分布式消息队列 Kafka
  • 网站如何做好SEO优化,该怎么选择SEO软件?
  • JAVA入门到精通-第67讲-sqlserver作业讲评
  • Tcp/Ip 三次握手与四次挥手
  • jQuery操作表格(table)的常用方法、技巧汇总
  • [转]GitLab Continuous Integration (GitLab CI/CD)
  • 实战开发正则归纳
  • Android Activity生命周期图解
  • JavaScript权威指南手记(一)
  • 阿里巴巴下一代云分析型数据库AnalyticDB入选Forrester Wave™ 云数仓评估报告 解读...
  • 美团容器平台架构及容器技术实践
  • 利用aiohttp制作异步爬虫
  • 怎么在线编辑图片 PS怎么处理图片
  • .net mvc部分视图
  • Google 是如何开发 Web 框架的
  • 《Java编程思想》读书笔记-对象导论
  • 08.Android之View事件问题
  • create-react-app项目添加less配置
  • eclipse(luna)创建web工程
  • Effective Java 笔记(一)
  • go append函数以及写入
  • HTTP请求重发
  • Java应用性能调优
  • JS实现简单的MVC模式开发小游戏
  • Less 日常用法
  • SQLServer之索引简介
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • 爱情 北京女病人
  • 大数据与云计算学习:数据分析(二)
  • 聊聊flink的BlobWriter
  • 小李飞刀:SQL题目刷起来!
  • Android开发者必备:推荐一款助力开发的开源APP
  • Semaphore
  • ​第20课 在Android Native开发中加入新的C++类
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #Linux(帮助手册)
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (1)Nginx简介和安装教程
  • (附源码)ssm基于web技术的医务志愿者管理系统 毕业设计 100910
  • (转)视频码率,帧率和分辨率的联系与区别
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .net(C#)中String.Format如何使用
  • .NET上SQLite的连接
  • .NET运行机制
  • @基于大模型的旅游路线推荐方案
  • []我的函数库
  • [20150707]外部表与rowid.txt
  • [20161101]rman备份与数据文件变化7.txt
  • [C#]猫叫人醒老鼠跑 C#的委托及事件
  • [CSS]盒子模型
  • [Docker]三.Docker 部署nginx,以及映射端口,挂载数据卷
  • [exgcd] Jzoj P1158 荒岛野人
  • [ITIL学习笔记]之事件管理(2)