Java, насколько дорогой вызов метода - PullRequest
75 голосов
/ 27 июня 2011

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

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 вызовов метода, каждый из которых просто установил значение переменной экземпляра?Какая лучшая практика программирования?

Ответы [ 12 ]

70 голосов
/ 27 июня 2011

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

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

Если да, то в чем разница?

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

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

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

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

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

18 голосов
/ 27 июня 2011

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

Нет смысла с точки зрения производительности для включения метода. Если это проблема с производительностью, 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 и C ++ различаются: в C ++ конструктор суперкласса, который вызывает метод virtual, в конечном итоге вызывает тот, который определен в суперклассе, а не переопределенный. Люди, переключающиеся между двумя языками, иногда забывают об этом.

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

5 голосов
/ 27 июня 2011

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

3 голосов
/ 27 июня 2011

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

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

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

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

3 голосов
/ 27 июня 2011

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

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

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

2 голосов
/ 27 июня 2011

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

1 голос
/ 27 июня 2011

Оптимизирующие компиляторы обычно неплохо справляются с удалением избыточности из этих «лишних» операций;во многих случаях разница между «оптимизированным» кодом и кодом, написанным просто так, как вы хотите, и выполнением через оптимизирующий компилятор - ничтожна;то есть оптимизирующий компилятор обычно выполняет ту же работу, что и вы, и делает это без какого-либо ухудшения исходного кода.Фактически, часто «оптимизированный вручную» код оказывается МЕНЬШЕ эффективным, потому что компилятор учитывает многие вещи при выполнении оптимизации.Оставьте свой код в удобочитаемом формате и не беспокойтесь об оптимизации до более позднего времени.

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

1 голос
/ 27 июня 2011

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

1 голос
/ 27 июня 2011

Шаблон, который я придерживаюсь, заключается в том, удовлетворяет ли данный метод одному из следующих:

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

Если что-либо из вышеперечисленного является верным, его следует обернуть в свой собственный метод.

0 голосов
/ 27 июня 2011

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

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

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

...