Java, как дорого стоит вызов метода

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

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

Было бы более оптимальным для меня просто копировать и вставлять две строки в свой метод clear() в конструктор вместо вызова фактического метода? Если да, то какая разница? Что делать, если мой конструктор сделал 10 вызовов методов, каждый из которых просто установил переменную экземпляра в значение? Какая лучшая практика программирования?

Ответ 1

Было бы более оптимальным для меня просто копировать и вставлять две строки в свой метод clear() в конструктор вместо вызова фактического метода?

Компилятор может выполнить эту оптимизацию. И так может JVM. Терминология, используемая автором компилятора и авторами JVM, - это "встроенное расширение".

Если это так, то какая разница?

Измерьте это. Часто вы обнаружите, что это не имеет значения. И если вы считаете, что это горячая точка производительности, вы смотрите не в то место; что вам нужно его измерить.

Что делать, если мой конструктор выполнил 10 вызовов методов, каждый из которых просто установил переменную экземпляра в значение?

Опять же, это зависит от сгенерированного байт-кода и любых оптимизаций во время выполнения, выполняемых виртуальной машиной Java. Если компилятор /JVM может встроить вызовы метода, он будет выполнять оптимизацию, чтобы избежать накладных расходов на создание новых кадров стека во время выполнения.

Какая лучшая практика программирования?

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

Ответ 2

То, что все остальные говорили об оптимизации, абсолютно верно.

С точки зрения производительности нет причин для встроенного метода. Если это проблема с производительностью, JIT в вашей JVM включит ее. В java вызовы методов настолько близки к свободным, что не стоит об этом думать.

Говоря, здесь есть и другая проблема. А именно, плохой практикой программирования является вызов переопределяемого метода (т.е. Того, который не является final, static или private) из конструктора. (Эффективная Java, 2-е изд., Стр. 89 в элементе под названием "Дизайн и документ для наследования или запрет на него" )

Что произойдет, если кто-то добавит подкласс BinarySearchTree под названием LoggingBinarySearchTree, который переопределяет все общедоступные методы с кодом вроде:

public void clear(){
  this.callLog.addCall("clear");
  super.clear();
}

Тогда LoggingBinarySearchTree никогда не будет конструктивным! Проблема в том, что this.callLog будет null, когда работает конструктор BinarySearchTree, но вызываемый clear, который вызывается, является переопределенным, и вы получите NullPointerException.

Обратите внимание, что Java и С++ отличаются здесь: в С++ конструктор суперкласса, который вызывает метод virtual, заканчивает вызов того, что определено в суперклассе, а не переопределенном. Люди, переключающиеся между двумя языками, иногда забывают об этом.

Учитывая это, я думаю, что, возможно, в вашем случае, возможно, более чистое, чтобы встроить метод clear при вызове из конструктора, но в целом на Java вы должны пойти и сделать все вызовы методов, которые вы хотите.

Ответ 3

Я бы определенно оставил его как есть. Что делать, если вы измените логику clear()? Было бы нецелесообразно находить все места, где вы скопировали 2 строки кода.

Ответ 4

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

Как только вы потратили время на оптимизацию времени, вы никогда не сможете вернуть его снова! (Сначала измерьте это и спросите себя, стоит ли оптимизировать. Сколько фактического времени вы сохраните?)

В этом случае Java VM, вероятно, уже делает оптимизацию, о которой вы говорите.

Ответ 5

Стоимость вызова метода - это создание (и удаление) фрейма стека и некоторых дополнительных выражений кода байта, если вам нужно передать значения методу.

Ответ 6

Вообще говоря (и как новичок это всегда означает!), вы никогда не должны делать микрооптимизации, как тот, который вы рассматриваете. Всегда поддерживайте читабельность кода над такими вещами.

Почему? Потому что компилятор/точка доступа сделает эти виды оптимизаций для вас "на лету" и многие, многие другие. Во всяком случае, когда вы пытаетесь сделать оптимизацию по этим типам строк (хотя и не в этом случае), вы, вероятно, будете делать что-то медленнее. Точка зрения понимает общие идиомы программирования, если вы попытаетесь сделать эту оптимизацию самостоятельно, она, вероятно, не поймет, что вы пытаетесь сделать, поэтому она не сможет ее оптимизировать.

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

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

Ответ 7

Образец, который я следую, заключается в том, удовлетворяет ли этот метод одному из следующих:

  • Было бы полезно, чтобы этот метод был доступен вне этого класса?
  • Было бы полезно иметь этот метод в других методах?
  • Было бы неудобно переписывать это каждый раз, когда мне это нужно?
  • Могла ли универсальность метода быть увеличена с использованием нескольких параметров?

Если любое из приведенных выше значений истинно, оно должно быть завершено в его собственном методе.

Ответ 8

Сохраняйте метод clear(), когда он помогает читать. Недопустимый код более дорогой.

Ответ 9

Оптимизация компиляторов обычно делает довольно хорошую работу по удалению избыточности из этих "дополнительных" операций; во многих случаях разница между "оптимизированным" кодом и кодом просто написана так, как вы хотите, и работать с оптимизирующим компилятором - нет; то есть оптимизирующий компилятор обычно выполняет такую ​​же хорошую работу, как и вы, и делает это, не вызывая ухудшения исходного кода. На самом деле, много раз, "оптимизированный вручную" код заканчивается ЛЕГКО эффективным, потому что компилятор учитывает многие вещи при выполнении оптимизации. Оставьте свой код в читаемом формате и не беспокойтесь об оптимизации до более позднего времени.

"Преждевременная оптимизация - это корень все зло". - Дональд Кнут

Ответ 10

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

Ответ 11

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

Ответ 12

Как говорили другие, стоимость вызова метода тривиально-на-нада, поскольку компилятор оптимизирует его для вас.

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

Другой вопрос: ваш метод clear() устанавливает корень в EMPTY, который инициализируется при создании объекта. Если вы затем добавите узлы в EMPTY, а затем вызовите clear(), вы не сбросите корень node. Это поведение, которое вы хотите?