Java 中的5個代碼性能提升技巧,最高提升近10倍
提示:我們不應(yīng)該為了優(yōu)化而優(yōu)化,這有時會增加代碼的復(fù)雜度。
JMH version: 1.33(Java 基準(zhǔn)測試框架) VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724
預(yù)先分配 HashMap 的大小,提高 1/4 的性能。 優(yōu)化 HashMap 的 key,性能相差 9.5 倍。 不使用 ?Enum.values() 遍歷,Spring 也曾如此優(yōu)化。 使用 Enum 代替 String 常量,性能高出 1.5 倍。 使用高版本 JDK,基礎(chǔ)操作有 2-5 倍性能差異。
?
預(yù)先分配 HashMap 的大小
/**
?*?@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;
????}
}
32 * 0.75 = 24 個元素,所以插入 14 個元素時是不需要擴容操作的。#?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
?
優(yōu)化 HashMap 的 key
/**
?*?@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])));
????????}
????}
}
#?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
?
不使用 ?Enum.values() 遍歷
Enum.values() 進行枚舉類遍歷,但是這樣每次調(diào)用都會分配枚舉類值數(shù)量大小的數(shù)組用于操作,這里完全可以緩存起來,以減少每次內(nèi)存分配的時間和空間消耗。/**
?*?枚舉類遍歷測試
?*
?*?@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());
????????}
????}
}
#?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ù)組的遍歷效率是大于哈希表的。values() 緩存和直接使用 Enum.values() 的效率差異很小,其實在某些調(diào)用頻率很高的場景下是有很大區(qū)別的,在 Spring 框架中,曾使用 Enum.values() 這種方式在每次響應(yīng)時遍歷 HTTP 狀態(tài)碼枚舉類,這在請求量大時造成了不必要的性能開銷,后來進行了 values() 緩存優(yōu)化。
?
使用 Enum 代替 String 常量
提示:不要為了優(yōu)化而優(yōu)化,這會增加代碼的復(fù)雜度。
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;
????/**?隨機數(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));
????????}
????}
}
#?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
?
使用高版本 JDK
/**
?*?@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));
????????}
????}
}
#?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
參考
https://richardstartin.github.io/posts/5-java-mundane-performance-tricks
https://github.com/spring-projects/spring-framework/issues/26842
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
