1
头图
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.

  1. Pre-allocate the size of HashMap to improve performance by 1/4.
  2. Optimize the key of HashMap, the performance difference is 9.5 times.
  3. Do not use Enum.values() to traverse, Spring has also been optimized in this way.
  4. Using Enum instead of String constant, the performance is 1.5 times higher.
  5. 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:

Spring Enum.values 改动

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

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.

image.png


程序猿阿朗
376 声望1.8k 粉丝

Hello world :)