Will the jit optimize new objects

前端 未结 5 1432
北荒
北荒 2020-11-30 13:27

I created this class for being immutable and having a fluent API:

public final class Message {
    public final String email;
    public final String escalat         


        
5条回答
  •  [愿得一人]
    2020-11-30 13:43

    Yes, HotSpot JIT can eliminate redundant allocations in a local context.

    This optimization is provided by the Escape Analysis enabled since JDK 6u23. It is often confused with on-stack allocation, but in fact it is much more powerful, since it allows not only to allocate objects on stack, but to eliminate allocation altogether by replacing object fields with variables (Scalar Replacement) that are subject to further optimizations.

    The optimization is controlled by -XX:+EliminateAllocations JVM option which is ON by default.


    Thanks to allocation elimination optimization, both your examples of creating a Message object work effectively the same way. They do not allocate intermediate objects; just the final one.

    Your benchmark shows misleading results, because it collects many common pitfalls of microbenchmarking:

    • it incorporates several benchmarks in a single method;
    • it measures an OSR stub instead of the final compiled version;
    • it does not do warm-up iterations;
    • it does not consume results, etc.

    Let's measure it correctly with JMH. As a bonus, JMH has the allocation profiler (-prof gc) which shows how many bytes are really allocated per iteration. I've added the third test that runs with EliminateAllocations optimization disabled to show the difference.

    package bench;
    
    import org.openjdk.jmh.annotations.*;
    
    @State(Scope.Benchmark)
    public class MessageBench {
    
        @Benchmark
        public Message builder() {
            return Message.createNew()
                    .email(getString())
                    .assignee(getString())
                    .conversationId(getString())
                    .escalationEmail(getString())
                    .subject(getString())
                    .userId(getString())
                    .create();
        }
    
        @Benchmark
        public Message immutable() {
            return new Message()
                    .email(getString())
                    .assignee(getString())
                    .conversationId(getString())
                    .escalationEmail(getString())
                    .subject(getString())
                    .userId(getString());
        }
    
        @Benchmark
        @Fork(jvmArgs = "-XX:-EliminateAllocations")
        public Message immutableNoOpt() {
            return new Message()
                    .email(getString())
                    .assignee(getString())
                    .conversationId(getString())
                    .escalationEmail(getString())
                    .subject(getString())
                    .userId(getString());
        }
    
        private String getString() {
            return "hello";
        }
    }
    

    Here are the results. Both builder and immutable perform equally and allocate just 40 bytes per iteration (exactly the size of one Message object).

    Benchmark                                        Mode  Cnt     Score     Error   Units
    MessageBench.builder                             avgt   10     6,232 ±   0,111   ns/op
    MessageBench.immutable                           avgt   10     6,213 ±   0,087   ns/op
    MessageBench.immutableNoOpt                      avgt   10    41,660 ±   2,466   ns/op
    
    MessageBench.builder:·gc.alloc.rate.norm         avgt   10    40,000 ±   0,001    B/op
    MessageBench.immutable:·gc.alloc.rate.norm       avgt   10    40,000 ±   0,001    B/op
    MessageBench.immutableNoOpt:·gc.alloc.rate.norm  avgt   10   280,000 ±   0,001    B/op
    

提交回复
热议问题