当前位置: 首页 > 后端技术 > Java

如何写出高性能代码(二)巧用数据特性

时间:2023-04-01 15:35:10 Java

如何写出高性能代码(2)巧妙利用数据特征代码的顺序会有数倍的性能提升;同样的代码在不同的处理器上运行也可能有数倍的性能差异;十倍程序员不只存在于传说中,可能在我们身边也比比皆是。十倍体现在程序员方法的方方面面,代码性能是最直观的方面。  本文是《如何写出高性能代码》系列的第二篇。本文将告诉您如何利用数据的几个特性来提高代码性能。复用性  我们在代码中使用的大部分数据都可以复用。这种可以重复使用的数据,不应该重复获取或初始化。例如:  上图中在for循环中调用了getSomeThing()函数,这个函数与循环无关。可以放在循环外,结果可以重用。上面的代码调用了99次都是白费。第二,如果getSomeThing()是一个非常耗时或者CPU耗费的函数,那么性能会提高近百倍。  在Java代码中,我们经常会用到枚举类。大多数枚举类可能经常有获取所有枚举信息的接口。大多数人可能会写出像上面getList()这样的代码。不过这种写法虽然在功能上没有问题,但是每次调用都会生成一个新的List。如果调用频率很高,会对性能产生很大的影响。正确的做法应该是静态初始化生成一个不可变列表,然后直接复用。提醒:这里我特地标记了一个不可变对象。在对象复用的情况下,需要格外注意对象的内容是否会被改变。如果对象需要更改,则不能重复使用。您可以对其进行深度复制,然后进行更改。当然,如果这个对象天生就是可以改变的,就没有必要重用它了。Non-essential  non-essential是指有些数据可能不需要初始化。举个简单的例子:  上面代码中,获取到sth对象后,验证参数的合法性。其实如果参数不合法,sth是不需要初始化的。这里sth有一个无效的必要性。类似上面的代码其实很常见。我在我们公司的代码库中遇到过很多次。基本模式是先获取一些数据,但是之后一些过滤或者检查逻辑导致代码跳出,然后数据就完全没用了。  解决非必要的方法是延迟初始化。有的地方也叫延迟加载或惰性加载。和上面的代码一样,只需要将getSomeThing()移到参数校验的后面,就可以避免这个性能问题。.就像我们在Java中使用的checkstyle插件一样,它提供了一个VariableDeclarationUsageDistance规则。这条规则的作用是强制代码的声明和使用不能被过多的行隔开,从而避免上面声明了却没有使用带来的性能问题。.  其实惰性初始化是一种很常用的机制,比如著名的copyonwrite其实就是一种惰性初始化的模型。另外Jdk中的很多集合基本都是懒初始化的。以HashMap为例。当你执行newHashMap()时,你只是创建了一个空的shell对象。将被初始化。//newHashMap()只是初始化一个空壳hashmappublicHashMap(intinitialCapacity,floatloadFactor){if(initialCapacity<0)thrownewIllegalArgumentException("Illegalinitialcapacity:"+initialCapacity);如果(initialCapacity>MAXIMUM_CAPACITY)initialCapacity=MAXIMUM_CAPACITY;if(loadFactor<=0||Float.isNaN(loadFactor))thrownewIllegalArgumentException("非法加载因子:"+loadFactor);this.loadFactor=loadFactor;this.threshold=tableSizeFor(initialCapacity);}publicVput(Kkey,Vvalue){returnputVal(hash(key),key,value,false,true);}finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){Node[]tab;节点p;诠释n,我;//第一个put触发内部真正的初始化if((tab=table)==null||(n=tab.length)==0)n=(tab=resize())。长度;如果((p=tab[i=(n-1)&hash])==null)tab[i]=newNode(hash,key,value,null);else{//省略其他代码}++modCount;如果(++大小>阈值)调整大小();节点插入后(逐出);returnnull;}locality  locality也是一个很常见的特征,有很多种locality,datalocality,spacelocality,Timelocality……可以说,正是因为有locality的存在,世界才能更有效地运行更多有关本地的内容。可以参考我之前写的一篇文章。局部性原理——各种优化的基石。  先说数据局部性。大多数情况下,只有少量数据被频繁访问,俗称热点数据。处理热点数据最简单的方法就是为其添加缓存和分区。具体的解决方案要看具体的问题。我举个在互联网公司很常见的例子。大量的业务数据存储在数据库中。但是,数据库面对大量的请求就有些力不从心了。由于本地性,只有少量数据被频繁访问,我们可以将这部分数据缓存在Redis中,从而减轻数据库的压力。  还有一点大家容易忽略的就是代码局部性。系统中只有少量代码被重复执行,如果系统出现性能问题,也是代码量少造成的,所以只要找到这部分代码并优化,就可以提升系统性能显着改善。依靠一些性能分析工具,比如使用arthas火焰图,可以很容易的找到这部分代码(其他工具会在本系列的第五篇文章中介绍)。多读少写  除了局部性,数据还有一个非常显着的特点,就是多读少写。这也很符合大家的直觉和习惯。例如,大多数人阅读文章而不是写文章。你在嘿嘿网站上看的多,改的少。这是一条几乎是普遍法则的规则。那么这个特性对我们写代码有什么意义呢?这个特性意味着你的代码局部性很有可能出现在读取数据的代码中,所以要格外注意这部分代码。  当然不是说写数据不重要。这里不得不说说多读少写的另一个特点,就是写作的成本远高于阅读的成本,写作的重要性远高于阅读。重要性。重要性不言而喻。看不到余额去银行还可以,但如果不能取款,那肯定不行。那为什么写入数据的成本远高于读取数据的成本呢?这样就很容易理解了。由于数据局部性的加持,很多读可以通过各种手段进行优化,但是写入不是很好,写入可能会带来很多额外的副作用,需要通过增加很多逻辑来避免,比如验证不需要的副作用.  以上就是本文的全部内容,希望大家有所收获。如何写出高性能代码系列文章(一)善用算法和数据结构(二)善用数据特性