2016-07-22 3 views
2

Я создал этот класс для того неизменны и имеющий свободно API:Будет ли JIT оптимизировать новые объекты

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("[email protected]") 
    .assignee("[email protected]") 
    .subject("subj"); 

Есть ли что-нибудь, что можно получить от создания отдельного объекта изменяемого объекта-строителя?

Обновление 2: После прочтения ответа apangin мой критерий признан недействительным. Я буду держать его здесь для справки о том, как не для сравнения :)

Update: я взял на себя смелость измерения это сам с этим кодом:

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 приведет к тому, что первый быть немного медленнее (я пробовал в обоих направлениях).

С «новой строкой» код был в целом много раз медленнее (мне пришлось уменьшить n), возможно, указывая на то, что происходит некоторая оптимизация «нового сообщения», но не от " новая строка ".

+3

Вы также можете получить от строителя, чтобы избежать необходимости переписывать методы N при добавлении (N + 1) -го поля к вашему классу. – khelwood

+1

Вот почему шаблон строителя не делает это так ... в конце концов, вы создаете много новых объектов; поэтому, если этот код работает «часто»; он будет постоянно создавать «мусор». – GhostCat

+1

@GhostCat Это моя забота, но возможно, что jit может оптимизировать это. Если так, я предпочитаю этот путь. – morten

ответ

7

Да, HotSpot JIT может устранить избыточные распределения в местном контексте.

Эта оптимизация обеспечивается с помощью Escape Analysis с JDK 6u23. Его часто путают с распределением по стеклу, но на самом деле он намного более мощный, поскольку он позволяет не только выделять объекты в стеке, но и полностью исключать выделение путем замены полей объектов переменными (Scalar Replacement), которые подлежат дальнейшему оптимизаций.

Оптимизация контролируется опцией JVM -XX:+EliminateAllocations, которая по умолчанию включена.


Благодаря оптимизации элиминации распределения, как ваши примеры эффективного создания Message объекта работы на один и тот же путь. Они не выделяют промежуточные объекты; просто последний.

Ваш тест показывает ошибочные результаты, потому что он собирает много common pitfalls из microbenchmarking:

  • он включает в себя несколько тестов в одном методе;
  • он измеряет 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 
+1

Я изменил принятый ответ на этот вопрос. Спасибо, я узнал гораздо больше, чем просил прочитать этот ответ. – morten

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("[email protected]") 
          .withEscalation("escalation") 
       .validate() 
       .build(); 
+0

Как он будет работать не так, как я ожидаю? Вы говорите, что Builder необходим для производительности? – morten

+0

Buikder необходим для производительности и памяти здесь. Вы создадите отдельный объект в конце операции сборки. Также вы будете строить атомарно. Вы даже можете поставить проверку с помощью метода проверки, если хотите, и сборка не будет продолжена, если объект недействителен. Он чрезвычайно гибкий. –

+0

Реализация генерирует новый объект для каждого набора полей. Нехорошо. Не говоря уже о том, что нет возможности выполнить проверку или что-то более сложное. Еще одно преимущество шаблона Builder заключается в том, что у вас есть возможность иметь множество разных строителей на основе вашего типа объекта. Таким образом, это может пригодиться, когда у вас много параметров по умолчанию. –

-1

В строитель шаблон, вы должны сделать так:

Message msg = Message.new() 
.email("[email protected]") 
.assignee("[email protected]") 
.subject("subj").build(); 

Какой Message.new() создаст объект класса строитель, функция email(..) и assignee(...) вернется this. И последняя функция build() создаст объект на основе ваших данных.

-1

будет оптимизатор сможет избежать многих создания объекта

Нет, но Инстанциация очень дешевая операция на JVM. Беспокойство об этой потере производительности будет типичным примером преждевременной оптимизации.

Есть ли что-нибудь, что можно получить от создания отдельного объекта изменчивого строителя?

Работа с неизменяемыми обычно является хорошим подходом. С другой стороны, строители также не повредят вам, если вы используете экземпляры компоновщика в небольшом контексте, поэтому их изменяемое состояние доступно только в небольшом локальном окружении. Я не вижу серьезных недостатков на любой стороне, это действительно зависит от ваших предпочтений.

+0

Будьте осторожны. Да, создание новых объектов «дешево»; но он по-прежнему стоит дорого. И умышленное создание ** мусора ** никогда не является хорошей идеей. Если у вас нет контекста, и вы не знаете, как часто такой код будет использоваться ... Вы знаете, это не похоже на то, что его дизайн намного лучше, чем он реализовал свой не-строитель. – GhostCat

+0

Я принял этот ответ, поскольку он кажется наиболее согласующимся с тем, что я узнал, пытаясь измерить это. – morten

+2

Почему вы говорите «нет»? HotSpot JIT ** делает ** устранение локальных распределений путем замены полей объектов на переменные. Оптимизация называется Scalar Replacement. Для него есть флаг JVM '-XX: + EliminateAllocations', и по умолчанию он включен. – apangin

0

Во-первых, ваш код не имеет подхода к построению и генерирует много объектов, но уже есть пример построителя, поэтому я не буду добавлять еще один.

Затем, относительно JIT, короткий ответ НЕТ (нет оптимизации создания нового объекта, кроме мертвого кода) ... длинный ответ нет, но ... есть другой механизм, который будет оптимизировать материал в JVM/

Существует пул строк, который позволяет избежать создания нескольких строк при использовании строковых литералов. Существует также пул объектов для каждого примитивного типа обертки (поэтому, если вы создаете объект Long с Long.valueOf, это тот же объект, который возвращается каждый раз, когда вы просите о том же длинном ...). Что касается строк, есть также механизм дедупликации строк, встроенный в сборщик g1 garbadge в обновлении java 8 20. Вы можете протестировать его со следующими параметрами JVM, если вы используете недавнюю JVM: -XX: + UseG1GC -XX: + UseStringDeduplication

Если вы действительно хотите оптимизировать создание нового объекта, вам нужно реализовать какой-то пул объектов, и ваш объект будет неизменным. Но будьте осторожны, что это не простая задача, и вы получите много кода, связанного с созданием объекта и управляющим размером пула, чтобы не переполнять вашу память. Поэтому я советую вам сделать это, только если это действительно необходимо.

Наконец, экземпляр объекта в куче является дешевой операцией, если вы не создаете миллионы объектов за секунду, а JVM делает много оптимизаций во многих полях, поэтому, если какой-либо хороший тест производительности (или профилирование памяти) доказать, что у вас есть проблема с объектом конкретизации не думать об этом слишком много;)

с уважением,

Лоик