场景是这样的。我先还原一下场景。下图中有一段代码。这段代码的逻辑非常简单。生成0-top范围的有序集合,比如top=100,那么就会生成[0,1,2,3,...99,100];如果shuffle=true,这个set的顺序会被打乱,相当于Shufflethecards;然后遍历集合,统计集合中小于top/2的值个数。无论是否洗牌,结果都是顶的一半;记录循环的开始和结束时间,以查看循环总共花费了多长时间。按理说不管集合有多大,都会执行最上面的循环,而且每次循环都要执行if判断,所以不管有没有洗牌,花费的时间应该是一样的吧?然而,神奇的事情发生了!publicstaticvoidmain(String[]args){perdication(1000000,true);perdication(1000000,false);}复制代码我们来执行这段代码,分别用有序集合和无序集合执行,看耗时。从执行结果来看,发现无序集合的执行时间比有序集合要长!!而且多次执行之后,情况都是一样的。为什么是这样?原来计算机在幕后帮我们做了一些事情,优化了我们代码的性能。计算机会做一些叫做分支预测的事情。在开始讲分支预测之前,首先要科普一下一个概念,叫做指令流水线。指令流水线当我们编写任何计算机程序时,我们都在编写一组我们希望计算机按顺序执行的指令。早期的计算机一次执行所有这些命令。每个命令都加载到内存中,完整执行,只有当它完成时才加载下一个命令。对这种情况的一种改进是指令流水线,它允许处理器将工作分成多个部分,然后并行执行。允许处理在执行一个命令时加载并准备好下一个命令。例如,我们有一个简单的程序:inta=0;一个+=1;一个+=2;一个+=3;复制代码每行代码都会经过处理器的加载(Fetch)、解码(Decode)、执行(Execute)、存储(Store)等步骤来执行。从上图中,您可以看到这四个命令的执行是如何并行运行的,从而使其在花费的时间上更快。会出现什么问题?通过指令流水线优化后,虽然在某些情况下可以让程序运行得更快,但也会带来一些问题。一些命令的执行取决于其他命令的执行结果,也可能取决于命令执行时未完成的命令执行。代码中的分支会导致此类问题。因为在分支部分的执行方向上只会选择其中的一个,而在分支执行之前不可能知道该往哪个方向走。这使得对分支执行方向的任何预测都是不安全的,因为我们无法确定分支的去向。inta=0;a+=1;if(a<10){a+=2;}a+=3;复制代码这段代码的执行结果和上面的代码是一样的,但是因为加了一个if语句,会导致计算机无法预知if分支的走向,所以后面的代码命令之前不能加载if被执行。可以发现,因为引入了if分支,执行时间立马变长了。那么这和我们原来的分支预测有什么关系呢?什么是分支预测?事实上,分支预测是对指令流水线优化的增强。计算机会尝试预测分支的方向,然后根据预测结果执行一些指定的载荷。inta=0;a+=1;if(a<10){a+=2;}a+=3;复制同样的代码,计算机可能会预测a<10为真,这样指令的执行情况就会变成如下:可以看出,时间立即从原来的11变成了9,提高了性能程序。然而,这样的预测是有风险的,因为计算机也可能做出错误的预测。如果执行结果与预测结果不一致,则不应执行加载的命令,并将丢弃并重新加载新命令。比如我们在程序中修改条件为a>10:inta=0;a+=1;if(a>10){a+=2;}a+=3;copy代码,那么它的执行可能就是这样:可以发现这个比上面的执行时间要慢。分支预测的作用是什么?现在我们了解了分支预测,它如何影响代码?一般情况下,我们的业务逻辑并不复杂,或者因为分支预测错误,执行几条处理器命令,对性能影响不会很大。但在某些特定场景下,这种机制可能会带来巨大的性能提升。让我们再次回忆起起始代码。因为在for循环中,预测了if条件的执行结果,并行加载指令,提高了执行效率。对于shuffle后的set,由于数字顺序被打乱,分支预测的结果在大多数情况下与实际结果不一致,导致drop命令后需要重新加载正确的命令执行。总结通过以上我们了解了什么是分支预测以及它如何影响我们的程序。让我们在优化程序性能的时候多了一种选择。如果您觉得这篇文章有趣,请点个赞。
