<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 中的5個(gè)代碼性能提升技巧,最高提升近10倍

          共 1070字,需瀏覽 3分鐘

           ·

          2021-12-30 13:12

          這篇文章介紹幾個(gè) Java 開(kāi)發(fā)中可以進(jìn)行性能優(yōu)化的小技巧,雖然大多數(shù)情況下極致優(yōu)化代碼是沒(méi)有必要的,但是作為一名技術(shù)開(kāi)發(fā)者,我們還是想追求代碼的更小、更快,更強(qiáng)。如果哪天你發(fā)現(xiàn)程序的運(yùn)行速度不盡人意,可能會(huì)想到這篇文章。

          提示:我們不應(yīng)該為了優(yōu)化而優(yōu)化,這有時(shí)會(huì)增加代碼的復(fù)雜度。

          這篇文章中的代碼都在以下環(huán)境中進(jìn)行性能測(cè)試。

          • JMH version: 1.33(Java 基準(zhǔn)測(cè)試框架)
          • VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724

          通過(guò)這篇文章的測(cè)試,將發(fā)現(xiàn)以下幾個(gè)操作的性能差異。

          1. 預(yù)先分配 HashMap 的大小,提高 1/4 的性能。
          2. 優(yōu)化 HashMap 的 key,性能相差 9.5 倍。
          3. 不使用 ?Enum.values() 遍歷,Spring 也曾如此優(yōu)化。
          4. 使用 Enum 代替 String 常量,性能高出 1.5 倍。
          5. 使用高版本 JDK,基礎(chǔ)操作有 2-5 倍性能差異。

          相關(guān)文章:使用JMH進(jìn)行Java代碼性能測(cè)試。

          預(yù)先分配 HashMap 的大小

          HashMap 是 Java 中最為常用的集合之一,大多數(shù)的操作速度都非常快,但是 HashMap 在調(diào)整自身的容量大小時(shí)是很慢且難以自動(dòng)優(yōu)化,因此我們?cè)诙x一個(gè) HashMap 之前,應(yīng)該盡可能的給出它的容量大小。給出 size 值時(shí)要考慮負(fù)載因子,HashMap 默認(rèn)負(fù)載因子是 0.75,也就是要設(shè)置的 size 值要除于 0.75。

          相關(guān)文章:HashMap 源碼分析解讀

          下面使用 JMH 進(jìn)行基準(zhǔn)測(cè)試,測(cè)試分別向初始容量為 16 和 32 的 HashMap 中插入 14 個(gè)元素的效率。

          /**
          ?*?@author?https://www.wdbyte.com
          ?*/

          @State(Scope.Benchmark)
          @Warmup(iterations?=?3,time?=?3)
          @Measurement(iterations?=?5,time?=?3)
          public?class?HashMapSize?{

          ????@Param({"14"})
          ????int?keys;

          ????@Param({"16",?"32"})
          ????int?size;

          ????@Benchmark
          ????public?HashMap?getHashMap()?{
          ????????HashMap?map?=?new?HashMap<>(size);
          ????????for?(int?i?=?0;?i?????????????map.put(i,?i);
          ????????}
          ????????return?map;
          ????}
          }

          HashMap 的初始容量是 16,負(fù)責(zé)因子 0.75,即最多插入 12 個(gè)元素,再插入時(shí)就要進(jìn)行擴(kuò)容,所以插入 14 個(gè)元素過(guò)程中需要擴(kuò)容一次,但是如果 HashMap 初始化時(shí)就給了 32 容量,那么最多可以承載 32 * 0.75 = 24 個(gè)元素,所以插入 14 個(gè)元素時(shí)是不需要擴(kuò)容操作的。

          #?JMH?version:?1.33
          #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724

          Benchmark???????????????(keys)??(size)???Mode??Cnt????????Score????????Error??Units
          HashMapSize.getHashMap??????14??????16??thrpt???25??4825825.152?±?323910.557??ops/s
          HashMapSize.getHashMap??????14??????32??thrpt???25??6556184.664?±?711657.679??ops/s

          可以看到在這次測(cè)試中,初始容量為32 的 HashMap 比初始容量為 16 的 HashMap 每秒可以多操作 26% 次,已經(jīng)有 1/4 的性能差異了。

          優(yōu)化 HashMap 的 key

          如果 HashMap 的 key 值需要用到多個(gè) String 字符串時(shí),把字符串作為某個(gè)類(lèi)屬性,然后使用這個(gè)類(lèi)的實(shí)例作為 key 會(huì)比使用字符串拼接效率更高。

          下面測(cè)試使用兩個(gè)字符串拼接作為 key,和把兩個(gè)字符串作為 MutablePair 類(lèi)的屬性引用,然后使用 MutablePair 對(duì)象作為 key 的運(yùn)行效率差異。

          /**
          ?*?@author?https://www.wdbyte.com
          ?*/

          @State(Scope.Benchmark)
          @Warmup(iterations?=?3,?time?=?3)
          @Measurement(iterations?=?5,?time?=?3)
          public?class?HashMapKey?{

          ????private?int?size?=?1024;
          ????private?Map?stringMap;
          ????private?Map?pairMap;
          ????private?String[]?prefixes;
          ????private?String[]?suffixes;

          ????@Setup(Level.Trial)
          ????public?void?setup()?{
          ????????prefixes?=?new?String[size];
          ????????suffixes?=?new?String[size];
          ????????stringMap?=?new?HashMap<>();
          ????????pairMap?=?new?HashMap<>();
          ????????for?(int?i?=?0;?i?????????????prefixes[i]?=?UUID.randomUUID().toString();
          ????????????suffixes[i]?=?UUID.randomUUID().toString();
          ????????????stringMap.put(prefixes[i]?+?";"?+?suffixes[i],?i);
          ????????????//?use?new?String?to?avoid?reference?equality?speeding?up?the?equals?calls
          ????????????pairMap.put(new?MutablePair(prefixes[i],?suffixes[i]),?i);
          ????????}
          ????}

          ????@Benchmark
          ????@OperationsPerInvocation(1024)
          ????public?void?stringKey(Blackhole?bh)?{
          ????????for?(int?i?=?0;?i?????????????bh.consume(stringMap.get(prefixes[i]?+?";"?+?suffixes[i]));
          ????????}
          ????}

          ????@Benchmark
          ????@OperationsPerInvocation(1024)
          ????public?void?pairMap(Blackhole?bh)?{
          ????????for?(int?i?=?0;?i?????????????bh.consume(pairMap.get(new?MutablePair(prefixes[i],?suffixes[i])));
          ????????}
          ????}
          }

          測(cè)試結(jié)果:

          #?JMH?version:?1.33
          #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724

          Benchmark??????????????Mode??Cnt?????????Score?????????Error??Units
          HashMapKey.pairMap????thrpt???25??89295035.436?±?6498403.173??ops/s
          HashMapKey.stringKey??thrpt???25???9410641.728?±??389850.653??ops/s

          可以發(fā)現(xiàn)使用對(duì)象引用作為 key 的性能,是使用 String 拼接作為 key 的性能的 9.5 倍

          不使用 ?Enum.values() 遍歷

          我們通常會(huì)使用 ?Enum.values() 進(jìn)行枚舉類(lèi)遍歷,但是這樣每次調(diào)用都會(huì)分配枚舉類(lèi)值數(shù)量大小的數(shù)組用于操作,這里完全可以緩存起來(lái),以減少每次內(nèi)存分配的時(shí)間和空間消耗。

          /**
          ?*?枚舉類(lèi)遍歷測(cè)試
          ?*
          ?*?@author?https://www.wdbyte.com
          ?*/

          @State(Scope.Benchmark)
          @Warmup(iterations?=?3,?time?=?3)
          @Measurement(iterations?=?5,?time?=?3)
          @BenchmarkMode(Mode.AverageTime)
          @OutputTimeUnit(TimeUnit.MILLISECONDS)
          public?class?EnumIteration?{
          ????enum?FourteenEnum?{
          ????????a,b,c,d,e,f,g,h,i,j,k,l,m,n;

          ????????static?final?FourteenEnum[]?VALUES;
          ????????static?{
          ????????????VALUES?=?values();
          ????????}
          ????}

          ????@Benchmark
          ????public?void?valuesEnum(Blackhole?bh)?{
          ????????for?(FourteenEnum?value?:?FourteenEnum.values())?{
          ????????????bh.consume(value.ordinal());
          ????????}
          ????}

          ????@Benchmark
          ????public?void?enumSetEnum(Blackhole?bh)?{
          ????????for?(FourteenEnum?value?:?EnumSet.allOf(FourteenEnum.class))?{
          ????????????bh.consume(value.ordinal());
          ????????}
          ????}

          ????@Benchmark
          ????public?void?cacheEnums(Blackhole?bh)?{
          ????????for?(FourteenEnum?value?:?FourteenEnum.VALUES)?{
          ????????????bh.consume(value.ordinal());
          ????????}
          ????}
          }

          運(yùn)行結(jié)果

          #?JMH?version:?1.33
          #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724

          Benchmark???????????????????Mode??Cnt?????????Score?????????Error??Units
          EnumIteration.cacheEnums???thrpt???25??15623401.567?±?2274962.772??ops/s
          EnumIteration.enumSetEnum??thrpt???25???8597188.662?±??610632.249??ops/s
          EnumIteration.valuesEnum???thrpt???25??14713941.570?±??728955.826??ops/s

          很明顯使用緩存后的遍歷速度是最快的,使用 EnumSet 遍歷效率是最低的,這很好理解,數(shù)組的遍歷效率是大于哈希表的。

          可能你會(huì)覺(jué)得這里使用 values() 緩存和直接使用 Enum.values() 的效率差異很小,其實(shí)在某些調(diào)用頻率很高的場(chǎng)景下是有很大區(qū)別的,在 Spring 框架中,曾使用 Enum.values() 這種方式在每次響應(yīng)時(shí)遍歷 HTTP 狀態(tài)碼枚舉類(lèi),這在請(qǐng)求量大時(shí)造成了不必要的性能開(kāi)銷(xiāo),后來(lái)進(jìn)行了 values() 緩存優(yōu)化。

          下面是這次提交的截圖:

          6020b192da000b82633224e14da8ae75.webp

          Spring Enum.values 改動(dòng)

          使用 Enum 代替 String 常量

          使用 Enum 枚舉類(lèi)代替 String 常量有明顯的好處,枚舉類(lèi)強(qiáng)制驗(yàn)證,不會(huì)出錯(cuò),同時(shí)使用枚舉類(lèi)的效率也更高。即使作為 Map 的 key 值來(lái)看,雖然 HashMap 的速度已經(jīng)很快了,但是使用 EnumMap 的速度可以更快。

          提示:不要為了優(yōu)化而優(yōu)化,這會(huì)增加代碼的復(fù)雜度。

          下面測(cè)試使用使用 Enum 作為 key,和使用 String 作為 key,在 map.get 操作下的性能差異。

          /**
          ?*?@author?https://www.wdbyte.com
          ?*/

          @State(Scope.Benchmark)
          @Warmup(iterations?=?3,?time?=?3)
          @Measurement(iterations?=?5,?time?=?3)
          public?class?EnumMapBenchmark?{

          ????enum?AnEnum?{
          ????????a,?b,?c,?d,?e,?f,?g,
          ????????h,?i,?j,?k,?l,?m,?n,
          ????????o,?p,?q,????r,?s,?t,
          ????????u,?v,?w,????x,?y,?z;
          ????}

          ????/**?要查找的?key?的數(shù)量?*/
          ????private?static?int?size?=?10000;
          ????/**?隨機(jī)數(shù)種子?*/
          ????private?static?int?seed?=?99;

          ????@State(Scope.Benchmark)
          ????public?static?class?EnumMapState?{
          ????????private?EnumMap?map;
          ????????private?AnEnum[]?values;

          ????????@Setup(Level.Trial)
          ????????public?void?setup()?{
          ????????????map?=?new?EnumMap<>(AnEnum.class);
          ????????????values?=?new?AnEnum[size];
          ????????????AnEnum[]?enumValues?=?AnEnum.values();
          ????????????SplittableRandom?random?=?new?SplittableRandom(seed);
          ????????????for?(int?i?=?0;?i?????????????????int?nextInt?=?random.nextInt(0,?Integer.MAX_VALUE);
          ????????????????values[i]?=?enumValues[nextInt?%?enumValues.length];
          ????????????}
          ????????????for?(AnEnum?value?:?enumValues)?{
          ????????????????map.put(value,?UUID.randomUUID().toString());
          ????????????}
          ????????}
          ????}

          ????@State(Scope.Benchmark)
          ????public?static?class?HashMapState{
          ????????private?HashMap?map;
          ????????private?String[]?values;

          ????????@Setup(Level.Trial)
          ????????public?void?setup()?{
          ????????????map?=?new?HashMap<>();
          ????????????values?=?new?String[size];
          ????????????AnEnum[]?enumValues?=?AnEnum.values();
          ????????????int?pos?=?0;
          ????????????SplittableRandom?random?=?new?SplittableRandom(seed);
          ????????????for?(int?i?=?0;?i?????????????????int?nextInt?=?random.nextInt(0,?Integer.MAX_VALUE);
          ????????????????values[i]?=?enumValues[nextInt?%?enumValues.length].toString();
          ????????????}
          ????????????for?(AnEnum?value?:?enumValues)?{
          ????????????????map.put(value.toString(),?UUID.randomUUID().toString());
          ????????????}
          ????????}
          ????}

          ????@Benchmark
          ????public?void?enumMap(EnumMapState?state,?Blackhole?bh)?{
          ????????for?(AnEnum?value?:?state.values)?{
          ????????????bh.consume(state.map.get(value));
          ????????}
          ????}

          ????@Benchmark
          ????public?void?hashMap(HashMapState?state,?Blackhole?bh)?{
          ????????for?(String?value?:?state.values)?{
          ????????????bh.consume(state.map.get(value));
          ????????}
          ????}
          }

          運(yùn)行結(jié)果:

          #?JMH?version:?1.33
          #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724

          Benchmark??????????????????Mode??Cnt??????Score??????Error??Units
          EnumMapBenchmark.enumMap??thrpt???25??22159.232?±?1268.800??ops/s
          EnumMapBenchmark.hashMap??thrpt???25??14528.555?±?1323.610??ops/s

          很明顯,使用 Enum 作為 key 的性能比使用 String 作為 key 的性能高出 1.5 倍。但是仍然要根據(jù)實(shí)際情況考慮是否使用 EnumMap 和 EnumSet。

          使用高版本 JDK

          String 類(lèi)應(yīng)該是 Java 中使用頻率最高的類(lèi)了,但是 Java 8 中的 ?String 實(shí)現(xiàn)相比高版本 JDK ,則占用空間更多,性能更低。

          下面測(cè)試 String 轉(zhuǎn) bytes 和 bytes 轉(zhuǎn) String 在 Java 8 以及 Java 11 中的性能開(kāi)銷(xiāo)。

          /**
          ?*?@author?https://www.wdbyte.com
          ?*?@date?2021/12/23
          ?*/

          @State(Scope.Benchmark)
          @Warmup(iterations?=?3,?time?=?3)
          @Measurement(iterations?=?5,?time?=?3)
          public?class?StringInJdk?{

          ????@Param({"10000"})
          ????private?int?size;
          ????private?String[]?stringArray;
          ????private?List<byte[]>?byteList;

          ????@Setup(Level.Trial)
          ????public?void?setup()?{
          ????????byteList?=?new?ArrayList<>(size);
          ????????stringArray?=?new?String[size];
          ????????for?(int?i?=?0;?i?????????????String?uuid?=?UUID.randomUUID().toString();
          ????????????stringArray[i]?=?uuid;
          ????????????byteList.add(uuid.getBytes(StandardCharsets.UTF_8));
          ????????}
          ????}

          ????@Benchmark
          ????public?void?byteToString(Blackhole?bh)?{
          ????????for?(byte[]?bytes?:?byteList)?{
          ????????????bh.consume(new?String(bytes,?StandardCharsets.UTF_8));
          ????????}
          ????}

          ????@Benchmark
          ????public?void?stringToByte(Blackhole?bh)?{
          ????????for?(String?s?:?stringArray)?{
          ????????????bh.consume(s.getBytes(StandardCharsets.UTF_8));
          ????????}
          ????}
          }

          測(cè)試結(jié)果:

          #?JMH?version:?1.33
          #?VM?version:?JDK?1.8.0_151,?Java?HotSpot(TM)?64-Bit?Server?VM,?25.151-b12

          Benchmark?????????????????(size)???Mode??Cnt?????Score?????Error??Units
          StringInJdk.byteToString???10000??thrpt???25??2396.713?±?133.500??ops/s
          StringInJdk.stringToByte???10000??thrpt???25??1745.060?±??16.945??ops/s

          #
          ?JMH?version:?1.33
          #?VM?version:?JDK?17,?OpenJDK?64-Bit?Server?VM,?17+35-2724

          Benchmark?????????????????(size)???Mode??Cnt?????Score?????Error??Units
          StringInJdk.byteToString???10000??thrpt???25??5711.954?±??41.865??ops/s
          StringInJdk.stringToByte???10000??thrpt???25??8595.895?±?704.004??ops/s

          可以看到在 bytes 轉(zhuǎn) String 操作上,Java 17 的性能是 Java 8 的 2.5 倍左右,而 String 轉(zhuǎn) bytes 操作,Java 17 的性能是 Java 8 的 5 倍。關(guān)于字符串的操作非常基礎(chǔ),隨處可見(jiàn),可見(jiàn)高版本的優(yōu)勢(shì)十分明顯。

          一如既往,當(dāng)前文章中的代碼示例都存放在 github.com/niumoo/JavaNotes.

          參考

          https://richardstartin.github.io/posts/5-java-mundane-performance-trickshttps://github.com/spring-projects/spring-framework/issues/26842

          當(dāng)前系列:

          1. 使用 JMX 監(jiān)控和管理 Java 程序

          2. Java 中的監(jiān)控與管理原理概述


          推薦閱讀:

          美團(tuán)面試:如何設(shè)計(jì)一個(gè)注冊(cè)中心?

          常見(jiàn)消息中間件大 PK

          如出一轍。。。

          為什么CTO不寫(xiě)代碼,還這么牛逼?



          關(guān)號(hào)互聯(lián)網(wǎng)全棧架構(gòu)價(jià)


          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲国产精品午夜福利 | 亚洲无码福利导航 | 3w,操逼网站免费播放 | 亚洲欧洲中文日韩免费视频一区二区 | 无码人妻一区二区三区毛片视频 |