jit会优化新对象吗?

时间:2016-07-22 08:05:40

标签: java immutability compiler-optimization jit fluent

我创建了这个类,因为它是不可变的,并且具有流畅的API:

public final class Message {
    public final String email;
    public final String escalationEmail;
    public final String assignee;
    public final String conversationId;
    public final String subject;
    public final String userId;

    public Message(String email, String escalationEmail, String assignee, String conversationId, String subject, String userId) {
        this.email = email;
        this.escalationEmail = escalationEmail;
        this.assignee = assignee;
        this.conversationId = conversationId;
        this.subject = subject;
        this.userId = userId;
    }

    public Message() {
        email = "";
        escalationEmail = "";
        assignee = "";
        conversationId = "";
        subject = "";
        userId = "";
    }

    public Message email(String e) { return new Message(e, escalationEmail, assignee, conversationId, subject, userId); }
    public Message escalationEmail(String e) { return new Message(email, e, assignee, conversationId, subject, userId); }
    public Message assignee(String a) { return new Message(email, escalationEmail, a, conversationId, subject, userId); }
    public Message conversationId(String c) { return new Message(email, escalationEmail, assignee, c, subject, userId); }
    public Message subject(String s) { return new Message(email, escalationEmail, assignee, conversationId, s, userId); }
    public Message userId(String u) { return new Message(email, escalationEmail, assignee, conversationId, subject, u); }

}

我的问题是,当像这样创建一个新对象时,优化器是否能够避免大量的对象创建:

Message m = new Message()
    .email("foo@bar.com")
    .assignee("bar@bax.com")
    .subject("subj");

是否可以通过创建单独的可变构建器对象获得任何东西?

更新2: 在阅读了apangin的答案后,我的基准无效。我将它留在这里,以供参考如何不进行基准测试:)

更新 我冒昧地用这段代码自己测量:

public final class Message {
public final String email;
public final String escalationEmail;
public final String assignee;
public final String conversationId;
public final String subject;
public final String userId;

public static final class MessageBuilder {
    private String email;
    private String escalationEmail;
    private String assignee;
    private String conversationId;
    private String subject;
    private String userId;

    MessageBuilder email(String e) { email = e; return this; }
    MessageBuilder escalationEmail(String e) { escalationEmail = e; return this; }
    MessageBuilder assignee(String e) { assignee = e; return this; }
    MessageBuilder conversationId(String e) { conversationId = e; return this; }
    MessageBuilder subject(String e) { subject = e; return this; }
    MessageBuilder userId(String e) { userId = e; return this; }

    public Message create() {
        return new Message(email, escalationEmail, assignee, conversationId, subject, userId);
    }

}

public static MessageBuilder createNew() {
    return new MessageBuilder();
}

public Message(String email, String escalationEmail, String assignee, String conversationId, String subject, String userId) {
    this.email = email;
    this.escalationEmail = escalationEmail;
    this.assignee = assignee;
    this.conversationId = conversationId;
    this.subject = subject;
    this.userId = userId;
}

public Message() {
    email = "";
    escalationEmail = "";
    assignee = "";
    conversationId = "";
    subject = "";
    userId = "";
}

public Message email(String e) { return new Message(e, escalationEmail, assignee, conversationId, subject, userId); }
public Message escalationEmail(String e) { return new Message(email, e, assignee, conversationId, subject, userId); }
public Message assignee(String a) { return new Message(email, escalationEmail, a, conversationId, subject, userId); }
public Message conversationId(String c) { return new Message(email, escalationEmail, assignee, c, subject, userId); }
public Message subject(String s) { return new Message(email, escalationEmail, assignee, conversationId, s, userId); }
public Message userId(String u) { return new Message(email, escalationEmail, assignee, conversationId, subject, u); }


static String getString() {
    return new String("hello");
    // return "hello";
}

public static void main(String[] args) {
    int n = 1000000000;

    long before1 = System.nanoTime();

    for (int i = 0; i < n; ++i) {
        Message m = new Message()
                .email(getString())
                .assignee(getString())
                .conversationId(getString())
                .escalationEmail(getString())
                .subject(getString())
                .userId(getString());
    }

    long after1 = System.nanoTime();

    long before2 = System.nanoTime();

    for (int i = 0; i < n; ++i) {
        Message m = Message.createNew()
                .email(getString())
                .assignee(getString())
                .conversationId(getString())
                .escalationEmail(getString())
                .subject(getString())
                .userId(getString())
                .create();
    }

    long after2 = System.nanoTime();



    System.out.println("no builder  : " + (after1 - before1)/1000000000.0);
    System.out.println("with builder: " + (after2 - before2)/1000000000.0);
}


}

如果字符串参数不是新对象,但我发现差异很大(构建器更快),但所有相同(请参阅getString中的注释代码)

我认为这是一个更现实的场景,当所有字符串都是新对象时,差异可以忽略不计,JVM启动会导致第一个字符串变慢(我尝试了两种方式)。

使用&#34; new String&#34;代码总共慢了很多倍(我不得不减少n),也许表明有一些优化的&#34;新消息&#34;继续,但不是&#34;新String&#34;。

5 个答案:

答案 0 :(得分:9)

是的,HotSpot JIT可以消除本地环境中的冗余分配。

此优化由JDK 6u23启用的Escape Analysis提供。它经常与堆栈分配混淆,但实际上它更强大,因为它不仅允许在堆栈上分配对象,而且通过用变量(标量替换)替换对象字段来完全消除分配。优化

优化由-XX:+EliminateAllocations JVM选项控制,默认情况下为ON。

感谢分配消除优化,您创建Message对象的示例都以相同的方式有效地工作。他们不分配中间对象;只是最后一个。

您的基准测试显示误导性结果,因为它收集了许多common pitfalls微基准测试:

  • 它在一个方法中包含了几个基准;
  • 它测量OSR stub而不是最终编译版本;
  • 它没有做热身迭代;
  • 它不会消耗结果等。

让我们用JMH正确测量它。作为奖励,JMH具有分配探查器(-prof gc),其显示每次迭代实际分配的字节数。我添加了第三个测试,该测试在禁用EliminateAllocations优化时运行,以显示差异。

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";
    }
}

以下是结果。 builderimmutable都执行相同的操作,每次迭代只分配40个字节(恰好是一个Message对象的大小)。

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

答案 1 :(得分:0)

我的理解是JIT编译器通过重新安排现有代码执行基本统计分析来工作。我不认为JIT编译器可以优化对象分配。

您的构建器不正确,您的流畅API将无法按预期工作(每个构建只创建一个对象)。

你需要有类似的东西:

  public class Message () {
     public final String email;
     public final String escalationEmail;

  private Message (String email,String escalationEmail) {
     this.email = email;
     this. escalationEmail = escalationEmail;
  }

  public static class Builder {
       public String email;
       public String escalationEmail;

       public static Builder createNew() {
           return new Builder();
       }

       public Builder withEmail(String email) {
          this.email = email;
          return this;
       }

       public Builder withEscalation(String escalation) {
          this.escalation = escalation;
          return this;
       }

       public Builder validate() {
          if (this.email==null|| this.email.length<7) {
             throw new RuntimeException("invalid email");
          }
       }


       public Message build() {¨
         return new Message(this.email,this.escalation);
       }

    } 

}

然后你可以有类似的东西。

Message.Builder.createNew()
                           .withEmail("exampple@email.com")
                           .withEscalation("escalation")
               .validate()
               .build();

答案 2 :(得分:0)

首先,你的代码没有构建器方法并且生成了很多对象,但是已经有一个构建器的例子,所以我不会再添加一个。

然后,关于JIT,简短回答NO(除了死代码之外没有新对象创建的优化)...长回答否但是......还有其他机制可以优化JVM中的东西/ / p>

使用字符串文字时,有一个字符串池可以避免创建多个字符串。每个原始包装器类型还有一个对象池(因此,如果使用Long.valueOf创建一个Long对象,则每次请求相同的长度时返回的是同一个对象...)。 关于字符串,在Java 8更新20中的G1 garbadge收集器中还集成了一个字符串重复数据删除机制。如果您使用的是最新的JVM,可以使用以下JVM选项对其进行测试:-XX:+ UseG1GC -XX:+ UseStringDeduplication

如果您真的想要优化新的对象创建,则需要实现某种对象池并使对象不可变。但要小心,这不是一个简单的任务,你最终会有很多代码处理对象创建和管理池大小,以免溢出内存。所以我建议你只有在真的有必要时才这样做。

最后,堆中的对象实例化是一种廉价的操作,除非你在一秒钟内创建了数百万个对象,而JVM在很多领域都进行了大量的优化,所以除非有一些好的性能基准测试(或内存分析)证明你有一个对象实例化的问题,不要考虑太多;)

此致

卢瓦克

答案 3 :(得分:-1)

在构建器模式中,您应该这样做:

Message msg = Message.new()
.email("foo@bar.com")
.assignee("bar@bax.com")
.subject("subj").build();

哪个Message.new()将创建构建器类的对象,函数email(..)assignee(...)将返回this。最后一个build()函数将根据您的数据创建对象。

答案 4 :(得分:-1)

  

优化器能否避免大量对象创建

不,但实例化是JVM上非常便宜的操作。担心这种性能损失将是过早优化的典型例子。

  

是否可以通过创建单独的可变构建器对象获得任何东西?

使用immutables通常是一种很好的方法。另一方面,如果你在一个小的环境中使用构建器实例,那么构建器也不会伤害你,所以它们的可变状态只能在一个小的本地环境中访问。我认为任何方面都没有任何严重的缺点,这完全取决于你的偏好。