The article is continuously updated, you can follow the public program Alang or visit unread code blog .
This article Github.com/niumoo/JavaNotes has been included, welcome to Star.
This article introduces several tips to optimize the performance . Although it is not necessary to optimize the code in most cases, as a technical developer, we still want to pursue a smaller and more compact code. Faster and stronger. If one day you find that the running speed of the program is not satisfactory, you may think of this article.
Tip: We should not optimize for the sake of optimization, which sometimes increases the complexity of the code.
The code in this article is tested in the following environment.
- JMH version: 1.33 (Java benchmarking framework)
- VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724
Through the test of this article, the performance difference of the following operations will be found.
- Pre-allocate the size of HashMap to improve performance by 1/4.
- Optimize the key of HashMap, the performance difference is 9.5 times.
- Do not use Enum.values() to traverse, Spring has also been optimized in this way.
- Using Enum instead of String constant, the performance is 1.5 times higher.
- With a higher version of JDK, there is a 2-5 times performance difference in basic operations.
The current article belongs to the Java performance analysis and optimization series of articles, click to view all articles.
The test in the current article uses the JMH benchmark test, the related article: uses JMH for Java code performance test .
Pre-allocate the size of the HashMap
HashMap is one of the most commonly used collections in Java. Most of the operations are very fast, but HashMap is very slow and difficult to optimize automatically when adjusting its own capacity. Therefore, we should give as much as possible before defining a HashMap. Calculate its capacity. The load factor should be considered when giving the size value. The default load factor of HashMap is 0.75, that is, the size value to be set must be divided by 0.75.
Related articles: HashMap source code analysis and interpretation
Next, use JMH for benchmarking to test the efficiency of inserting 14 elements into HashMap with initial capacities of 16 and 32 respectively.
/**
* @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;
}
}
The initial capacity of HashMap is 16, which is responsible for the factor of 0.75, which means that 12 elements are inserted at most, and expansion is required when inserting. Therefore, the capacity needs to be expanded once in the process of inserting 14 elements. However, if the HashMap is initialized with a capacity of 32, the maximum is It can carry 32 * 0.75 = 24
elements, so there is no need to expand the capacity when inserting 14 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
Can be seen in this test, an initial capacity of the HashMap 32 can be more than the initial capacity of 16 operations per second is 26% HashMap times it has performance differences 1/4 a.
Optimize the key of HashMap
If the key value of the HashMap needs to use multiple String strings, it is more efficient to use the string as a class attribute and then use the instance of this class as the key than to use string splicing.
The following test uses two strings splicing as the key, and the two strings are referenced as attributes of the MutablePair class, and then the operation efficiency difference using the MutablePair object as the 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<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 Results:
# 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
It can be found using the object reference as a performance key is to use String as a key splicing of performance of 9.5 times .
Do not use Enum.values() to traverse
We usually use Enum.values()
for enumeration class traversal, but in this way, each call will allocate an array of the size of the enumeration class value for operation, which can be cached here to reduce the time and space consumption of each memory allocation.
/**
* 枚举类遍历测试
*
* @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());
}
}
}
operation result
# 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 the cache is the fastest, and the EnumSet
is the lowest. It is easy to understand that the traversal efficiency of the array is greater than that of the hash table.
You may think that values()
cache and directly using Enum.values()
is very small. In fact, there is a big difference in some scenes with high frequency of calls. In the Spring framework, Enum.values()
was used every time. When responding, it traverses the HTTP status code enumeration class, which causes unnecessary performance overhead when the request volume is large. Later, the values()
cache optimization was performed.
Here is this submission screenshot:
Use Enum instead of String constant
Using Enum enumeration class instead of String constant has obvious advantages. Enumeration class enforces verification and will not make mistakes. At the same time, it is more efficient to use enumeration class. Even as the key value of the Map, although the speed of HashMap is already very fast, the speed of using EnumMap can be faster.
Tip: Don't optimize for the sake of optimization, which will increase the complexity of the code.
The following test uses Enum as the key and String as the key, the performance difference under 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 的数量 */
private static int size = 10000;
/** 随机数种子 */
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));
}
}
}
operation result:
# 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, the performance of using Enum as the key is 1.5 times higher than the performance of using String as the key. But still have to consider whether to use EnumMap and EnumSet according to the actual situation.
Use a higher version of JDK
The String class should be the most frequently used class in Java, but the String implementation in Java 8 takes up more space and has lower performance compared to higher versions of JDK.
Let's test the performance overhead of String to bytes and bytes to String in Java 8 and Java 11.
/**
* @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 Results:
# 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
It can be seen that in terms of bytes to String operations, the performance of Java 17 is about 2.5 times that of Java 8, while for String to bytes operations, the is 5 times that of Java 8's . The operation on strings is very basic and 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 at github.com/niumoo/JavaNotes .
reference
- https://richardstartin.github.io/posts/5-java-mundane-performance-tricks
- https://github.com/spring-projects/spring-framework/issues/26842
- https://github.com/spring-projects/spring-framework/commit/7f1062159ee9926d5abed7cadc2b36b6b7fc242e
Subscribe to
You can search for program Alang or visit Program Alang blog read.
This article Github.com/niumoo/JavaNotes has been included, there are many knowledge points and series of articles, welcome to Star.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。