jit会优化新对象吗
Will the jit optimize new objects
我创建这个 class 是因为它是不可变的并且具有流畅的 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");
创建一个单独的可变构建器对象有什么好处吗?
更新二:
阅读 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 启动会导致第一个稍微慢一点(两种方式我都试过了)。
有了 "new String",代码也慢了很多倍(我不得不减少 n
),也许表明 "new Message" 有一些优化上,但不是 "new String".
我的理解是,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();
在建造者模式中,你应该这样做:
Message msg = Message.new()
.email("foo@bar.com")
.assignee("bar@bax.com")
.subject("subj").build();
其中 Message.new()
将创建生成器 class 的对象,函数 email(..)
和 assignee(...)
将 return this
。最后一个 build()
函数将根据您的数据创建对象。
will the optimizer be able to avoid lots of object creation
不,但是在 JVM 上实例化是一个非常便宜的操作。担心这种性能损失将是过早优化的典型示例。
Is there anything to be gained from making a separate mutable builder object instead?
使用不可变通常是一种很好的方法。另一方面,如果您在小型上下文中使用构建器实例,构建器也不会伤害您,因此它们的可变状态只能在小型本地环境中访问。我看不出任何严重的缺点,这真的取决于你的喜好。
首先,您的代码没有构建器方法并生成很多对象,但是已经有一个构建器示例,所以我不会再添加一个。
然后,关于 JIT,简短回答否(没有新对象创建的优化,死代码除外)...长回答否但是...还有其他机制可以优化 JVM 中的东西/
有一个字符串池可以避免在使用字符串文字时创建多个字符串。每个原始包装器类型也有一个对象池(因此,如果您使用 Long.valueOf 创建一个 Long 对象,那么每次您请求相同的 long ... 时都会返回同一个对象)。
关于字符串,在 java8 update 20 的 G1 垃圾收集器中还集成了字符串重复数据删除机制。如果您使用的是最新的 JVM,则可以使用以下 JVM 选项对其进行测试:-XX:+UseG1GC - XX:+UseStringDeduplication
如果你真的想优化新对象的创建,你需要实现某种对象池并让你的对象不可变。但请注意,这不是一项简单的任务,您最终将拥有大量处理对象创建和管理池大小的代码,以免内存溢出。所以我建议你只有在真正需要的时候才去做。
最后,堆中的对象实例化是一种廉价的操作,除非你在一秒钟内创建数百万个对象,并且 JVM 在很多领域都做了很多优化,除非有一些好的性能基准(或内存分析) ) 证明你在对象实例化方面有问题不要想太多;)
此致,
洛伊克
是的,HotSpot JIT 可以消除本地上下文中的冗余分配。
此优化由自 JDK 6u23 起启用的 Escape Analysis 提供。它经常与栈上分配相混淆,但实际上它更强大,因为它不仅允许在栈上分配对象,而且通过用进一步分配的变量(标量替换)替换对象字段来完全消除分配优化。
优化由 -XX:+EliminateAllocations
JVM 选项控制,默认情况下处于启用状态。
感谢分配消除优化,创建 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";
}
}
这是结果。 builder
和 immutable
性能相同,每次迭代仅分配 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
我创建这个 class 是因为它是不可变的并且具有流畅的 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");
创建一个单独的可变构建器对象有什么好处吗?
更新二: 阅读 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 启动会导致第一个稍微慢一点(两种方式我都试过了)。
有了 "new String",代码也慢了很多倍(我不得不减少 n
),也许表明 "new Message" 有一些优化上,但不是 "new String".
我的理解是,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();
在建造者模式中,你应该这样做:
Message msg = Message.new()
.email("foo@bar.com")
.assignee("bar@bax.com")
.subject("subj").build();
其中 Message.new()
将创建生成器 class 的对象,函数 email(..)
和 assignee(...)
将 return this
。最后一个 build()
函数将根据您的数据创建对象。
will the optimizer be able to avoid lots of object creation
不,但是在 JVM 上实例化是一个非常便宜的操作。担心这种性能损失将是过早优化的典型示例。
Is there anything to be gained from making a separate mutable builder object instead?
使用不可变通常是一种很好的方法。另一方面,如果您在小型上下文中使用构建器实例,构建器也不会伤害您,因此它们的可变状态只能在小型本地环境中访问。我看不出任何严重的缺点,这真的取决于你的喜好。
首先,您的代码没有构建器方法并生成很多对象,但是已经有一个构建器示例,所以我不会再添加一个。
然后,关于 JIT,简短回答否(没有新对象创建的优化,死代码除外)...长回答否但是...还有其他机制可以优化 JVM 中的东西/
有一个字符串池可以避免在使用字符串文字时创建多个字符串。每个原始包装器类型也有一个对象池(因此,如果您使用 Long.valueOf 创建一个 Long 对象,那么每次您请求相同的 long ... 时都会返回同一个对象)。 关于字符串,在 java8 update 20 的 G1 垃圾收集器中还集成了字符串重复数据删除机制。如果您使用的是最新的 JVM,则可以使用以下 JVM 选项对其进行测试:-XX:+UseG1GC - XX:+UseStringDeduplication
如果你真的想优化新对象的创建,你需要实现某种对象池并让你的对象不可变。但请注意,这不是一项简单的任务,您最终将拥有大量处理对象创建和管理池大小的代码,以免内存溢出。所以我建议你只有在真正需要的时候才去做。
最后,堆中的对象实例化是一种廉价的操作,除非你在一秒钟内创建数百万个对象,并且 JVM 在很多领域都做了很多优化,除非有一些好的性能基准(或内存分析) ) 证明你在对象实例化方面有问题不要想太多;)
此致,
洛伊克
是的,HotSpot JIT 可以消除本地上下文中的冗余分配。
此优化由自 JDK 6u23 起启用的 Escape Analysis 提供。它经常与栈上分配相混淆,但实际上它更强大,因为它不仅允许在栈上分配对象,而且通过用进一步分配的变量(标量替换)替换对象字段来完全消除分配优化。
优化由 -XX:+EliminateAllocations
JVM 选项控制,默认情况下处于启用状态。
感谢分配消除优化,创建 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";
}
}
这是结果。 builder
和 immutable
性能相同,每次迭代仅分配 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