The performance improvement skills of five codes in Java can be improved nearly 10 times at most

Program ape Alan 2022-02-13 09:13:18 阅读数:814

performance improvement skills codes java

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 .

  • JMH version: 1.33(Java Benchmarking framework )
  • VM version: JDK 17, OpenJDK 64-Bit Server VM, 17+35-2724

Pass the test of this article , You will find performance differences in the following operations .

  1. Pre allocation HashMap Size , Improve 1/4 Performance of .
  2. Optimize HashMap Of key, Performance difference 9.5 times .
  3. Don't use  Enum.values() Traverse ,Spring It has been so optimized .
  4. Use Enum Instead of String Constant , Performance is better than 1.5 times .
  5. Use a higher version JDK, Basic operations include 2-5 Multiple performance differences .

Related articles : Use JMH Conduct Java Code performance testing .

Pre allocation HashMap Size

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 了 .

Optimize HashMap Of key

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 .

Don't use  Enum.values() Traverse

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 :

Spring Enum.values changes

Use Enum Instead of String Constant

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.

Use a higher version JDK

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

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

Current series :

  1. Use JMX Monitoring and management Java Program

  2. Java Overview of monitoring and management principles in
---- E ND ----

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