2014-12-25 10 views
-1

Иногда в ходе алгоритма нам нужно вычислить или просто сохранить несколько значений, которые зависят друг от друга или не имеют никакого отношения друг к другу.Может ли JVM опустить создание недолговечных объектов, поэтому я могу делать рефакторинг без ущерба для производительности?

Подобно тому, как (довольно бессмысленный, но то, что вопросы, простой) пример позволяет найти два различных значения в int[], которые находятся ближе всего к числу 3:

int a = values[0]; 
int b = values[1]; 
for (int value : values) { 
    int distance = Math.abs(value-3); 
    if (value != b) { 
     if (distance < Math.abs(a-3)) { 
      a = value; 
     } 
    } 
    if (value != a) { 
     if (distance < Math.abs(b-3)) { 
      b = value; 
     } 
    }   
} 
takeSausageSliceBetween(a, b); 

Мы не можем просто сделать методы computeA() и computeB() в том же классе, потому что вычисления для a и b являются взаимозависимыми. Так вычисления a и b просто просят, чтобы быть переработан в отдельный класс:

class DistinctNumbersNearestTo3 { 
    final int a; 
    final int b; 
    DistinctNumbersNearestTo3(int[] values) { 
     // same algorithm 
    } 
} 

поэтому выше код уровня становится красиво и ясно:

DistinctNumbersNearestTo3 nearest = new DistinctNumbersNearestTo3(values); 
takeSausageSliceBetween(nearest.a, nearest.b); 
// nearest never leaves the method it was declared in 

Однако (пожалуйста, поправьте меня, если я ошибаюсь) он вводит, по крайней мере для некоторых уровней оптимизации, экземпляр, а затем сбор мусора (только от eden, но в любом случае) от нового объекта только для того, чтобы быть понятным с вашим кодом. А также поиск int в стеке превращается в поиск объекта в куче.

Вопрос: Является ли JVM достаточно умным, чтобы в конечном итоге оптимизировать реорганизованный код для работы так же, как и нефакторизованный код?

Другой случай, когда я хотел бы кучу вместе несколько переменных в новый объект только для чистого кода, когда я хочу, чтобы реорганизовать длинный метод a() в несколько новых методов путем введения нового класса C который содержит данные, относящиеся к a() и поэтому содержит несколько новых методов для работы с этими данными. Я наткнулся на такой случай при реализации класса, представляющего набор цепей, к которым мы можем добавлять новые ссылки (пары узлов), поэтому расширяя существующую цепочку, объединяя две цепи вместе в одну или создавая новую цепочку из двух узлов. Давайте обсудим этот конкретный класс только для некоторых перспективы:

public class SetOfChains { 
    List<List<Node>> chains; 
    public void addLink(Node a, Node b) { 
     // Very long method that mutates field this.chains. Needs refactoring. 
    } 
} 

Чтобы правильно разделить addLink на несколько методов SetOfChains, каждый из этих новых методов будет иметь много параметров, потому что есть данные, относящиеся к addLink() вызова, что необходимо на каждом этапе addLink(). Сохранение этих данных в выделенных областях SetOfChains было бы возможно, но вонючим и невыразительным (потому что снова это имеет смысл только во время вызова метода), поэтому, очевидно, я создаю новый внутренний класс для хранения этих данных в своих полях и выполняю весь алгоритм шаги, мутирует внешнее поле chains как unrefactored addLink() делает:

class SetOfChains{ 
    List<List<Node>> chains; 
    void addLink(Node a, Node b) { 
     new LinkAddition(a,b).compute(); 
    } 
    class LinkAddiditon { // Instances never leave addLink()'s scope 
     private final Something bunch, of, common, fields; 
     compute() { 
      this.properly(); 
      this.isolated(); 
      this.steps(); 
      this.of(); 
      this.the(); 
      this.refactored(); 
      this.method(); 
     } 
     // There be code for the properly isolated steps... 
    } 

Но так fuctionally, что эквивалентно тому, что мой старый длинный unrefactored addLink(), может JVM сделать полученные инструкции быть оптимальным, так как в unrefactored случае, как если бы не было объектов LinkAddition?

Таким образом, чтобы нарисовать нижнюю черту под этой стеной текста: Если я стремлюсь к легко понять код путем извлечения данных и методов к новым классов, а не методы, это обязательно ввести снижение производительности?

И правильно ли я понимаю, что описанные случаи - это именно то, о чем escape analysis?

ответ

2

Говорят, что «преждевременная оптимизация - это корень всего зла».

В целом, лучше сделать код более понятным и сделать его более сложным и трудным для понимания после обнаружения узкого места производительности.

Большинство кода Java создают тонны короткоживущих объектов, которые быстро становятся недоступными. По этой причине я считаю, что сборщик мусора HotSpot является «поколением», что означает, что он разделяет кучу на две отдельные области: молодое поколение и старое поколение.

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

  • Старое поколение - более долговечные объекты в конечном итоге «продвигаются» из молодого поколения в старое поколение. Уборка мусора в этом районе дороже.

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

Кроме того, простой скрипт для HotSpot проще анализировать и потенциально inline/unroll/optimize, который также поддерживает создание множества простых объектов и методов.

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

+0

Думаю, вы хотели сослаться на Кнута с цитатой в первом предложении. Однако фактическая цитата говорит о том, что преждевременная оптимизация является корнем всего зла и, как правило, не осуждает оптимизацию. Я согласен с другими частями вашего ответа. – box

+1

Да, вы правы, я знаю, что это был Кнут, и я должен был поставить «преждевременный» в цитате. Я обновлю спасибо – dkatzel