Program ape Alan 2022-02-13 09:13:18 阅读数:814
This article introduces several Java Can be carried out in development Performance optimization tips , Although in most cases, extreme optimization of code is not necessary , But as a technology developer , We still want to pursue smaller code 、 faster , stronger . If one day you find that the running speed of the program is not satisfactory , You may think of this article .
Tips : We should not optimize for the sake of optimization , This sometimes increases the complexity of the code .
The code in this article is tested in the following environments .
Pass the test of this article , You will find performance differences in the following operations .
Related articles : Use JMH Conduct Java Code performance testing .
HashMap yes Java One of the most commonly used collections in , Most operations are very fast , however HashMap It is very slow and difficult to optimize automatically when adjusting its own capacity , So we are defining a HashMap Before , The capacity should be given as much as possible . give size The load factor should be taken into account ,HashMap The default load factor is 0.75, That is to set size Value to divide by 0.75.
Related articles :HashMap Source code analysis and interpretation
Use JMH Benchmark , The initial capacity of the test is 16 and 32 Of HashMap Insert 14 The efficiency of elements .
/**
* @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<Integer, Integer> getHashMap() {
HashMap<Integer, Integer> map = new HashMap<>(size);
for (int i = 0; i < keys; i++) {
map.put(i, i);
}
return map;
}
}
HashMap The initial capacity of is 16, Responsible factor 0.75, That is, insert up to 12 Elements , When you insert it again, you need to expand the capacity , So insert 14 The capacity needs to be expanded once in the process of one element , But if HashMap It is given during initialization 32 Capacity , Then it can carry at most 32 * 0.75 = 24
Elements , So insert 14 There is no need to expand the capacity when there are two elements .
# 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
You can see in this test , The initial capacity is 32 Of HashMap The specific initial capacity is 16 Of HashMap More operations per second 26% Time , There has been a 1/4 The difference in performance 了 .
If HashMap Of key Multiple values are required String When the string , Take the string as a class attribute , Then use an instance of this class as key It will be more efficient than using string splicing .
The following test uses two string splices as key, And take two strings as MutablePair Property reference of class , And then use MutablePair Object as key Operating efficiency differences .
/**
* @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<String, Object> stringMap;
private Map<Pair, Object> 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 < size; ++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 < prefixes.length; i++) {
bh.consume(stringMap.get(prefixes[i] + ";" + suffixes[i]));
}
}
@Benchmark
@OperationsPerInvocation(1024)
public void pairMap(Blackhole bh) {
for (int i = 0; i < prefixes.length; i++) {
bh.consume(pairMap.get(new MutablePair(prefixes[i], suffixes[i])));
}
}
}
test result :
# 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
You can find that object references are used as key Performance of , It's using String Splice as key Of The performance of the 9.5 times .
We usually use them Enum.values()
Enumeration class traversal , But in this way, each call will allocate an array of the number and size of enumeration class values for operation , It can be cached here , To reduce the time and space consumption of each memory allocation .
/**
* Enumeration class traversal test
*
* @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());
}
}
}
Running results
# 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
Obviously, the traversal speed after using cache is the fastest , Use EnumSet
Ergodic efficiency is the lowest , That makes sense , The traversal efficiency of array is greater than that of hash table .
Maybe you'll feel like using values()
Caching and direct use Enum.values()
The difference in efficiency is small , In fact, there are great differences in some scenes with high call frequency , stay Spring In the frame , Used to Enum.values()
This method iterates through each response HTTP State code enumeration class , This creates unnecessary performance overhead when the number of requests is large , And then it was values()
Cache optimization .
The following is a screenshot of this submission :
Use Enum Enumeration class instead of String Constants have obvious benefits , Enumeration classes force validation , No mistakes , At the same time, it is more efficient to use enumeration classes . Even as Map Of key It's worth seeing , although HashMap The speed has been very fast , But use EnumMap Can be faster .
Tips : Don't optimize for the sake of optimization , This increases the complexity of the code .
The following test uses Enum As key, And use String As key, stay map.get
Performance differences under operation .
/**
* @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;
}
/** You're looking for key The number of */
private static int size = 10000;
/** Random number seed */
private static int seed = 99;
@State(Scope.Benchmark)
public static class EnumMapState {
private EnumMap<AnEnum, String> 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 < size; 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<String, String> 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 < size; 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));
}
}
}
Running results :
# 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
Obviously , Use Enum As key Better performance than using String As key The performance is higher than 1.5 times . However, whether to use... Should still be considered according to the actual situation EnumMap and EnumSet.
String Classes should be Java The most frequently used class in , however Java 8 Medium String Compared with the higher version JDK , It takes up more space , Lower performance .
Next test String turn bytes and bytes turn String stay Java 8 as well as Java 11 Performance overhead in .
/**
* @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 < size; 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));
}
}
}
test result :
# 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
You can see in the bytes turn String Operationally ,Java 17 The performance of Java 8 Of 2.5 About times , and String turn bytes operation ,Java 17 Of Performance is Java 8 Of 5 times . The operation of string is very basic , Can be seen everywhere , It can be seen that the advantages of the high version are very obvious .
As always, , The code examples in the current article are stored in github.com/niumoo/JavaNotes.
Reference resources
Current series :
Hello world : ) This is the end of the article , I'm Alan , Dianzanhe is watching , Infinite power , Please pay attention to .
copyright:author[Program ape Alan],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/02/202202130913159882.html