Java, наскільки дорогим є виклик методу


82

Я новачок і завжди читав, що погано повторювати код. Однак, здається, щоб не робити цього, вам зазвичай доведеться мати додаткові виклики методів. Скажімо, у мене такий клас

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


4
Але почекайте, якщо ви запустите метод зараз, ми додамо ДРУГИЙ виклик методу, абсолютно безкоштовно! Оплачуйте лише доставку та обробку! Однак серйозно. У викликах методів є накладні витрати, так само, як і накладні витрати на те, щоб завантажити більше коду. У певний момент одне стає дорожчим за інше. Єдиний спосіб це визначити - це порівняльний аналіз вашого коду.
Marc B

11
передчасні котирування оптимізації за 3 ... 2 ... 1

22
Я не бачу причини цього голосу - хлопець задає цілком законне запитання. Це може бути очевидною відповіддю для деяких, але це не робить це поганим питанням!
Michael Berry

3
Дійсно, це цілком законне питання, лише причиною для голосування проти може бути, якщо є точний дублікат.
Arafangion

1
так, вибачте, якщо це очевидно, але я вчусь сам, і займаюся цим лише кілька місяців. Деякі речі, які я бачив у зразку коду в Інтернеті, я виявив невдалою практикою, тому я просто хочу ще раз перевірити.
jhlu87

Відповіді:


74

Чи було б для мене оптимальніше просто скопіювати та вставити два рядки мого методу clear () у конструктор, а не викликати власне метод?

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

Якщо так, то наскільки велика різниця?

Виміряйте це. Часто ви виявите, що це не має значення. І якщо ви вважаєте, що це гаряча точка продуктивності, ви шукаєте не в тому місці; тому вам потрібно буде це виміряти.

Що робити, якщо мій конструктор здійснив 10 викликів методів, кожен з яких просто встановив значення змінної екземпляра?

Знову ж таки, це залежить від сформованого байт-коду та будь-якої оптимізації виконання, виконаної віртуальною машиною Java. Якщо компілятор / JVM може вбудувати виклики методу, він виконає оптимізацію, щоб уникнути накладних витрат на створення нових кадрів стека під час виконання.

Яка найкраща практика програмування?

Уникання передчасної оптимізації. Найкраща практика - це написання читабельного та добре розробленого коду, а потім оптимізація для гарячих точок продуктивності у вашому додатку.


який хороший спосіб порівняння? чи є якесь програмне забезпечення, яке я можу завантажити, чи ви маєте на увазі просто використовувати System.nanoTime () на початку та в кінці та надрукувати різницю?
jhlu87

System.nanoTime()або System.currentTimeMillisє поганим способом складання профілю. Ви можете отримати список профілістів з відповіді на це запитання Stackoverflow . Я б порекомендував VisualVM, оскільки зараз він постачається з JDK.
Vineet Reynolds

2
@ jhlu87: Я думаю, вам буде дуже важко отримати точну оцінку накладних витрат на виклик методу. Мікробенчмаркінг дуже важко отримати правильно, та навіть тоді, як правило, не дуже корисний у загальній схемі речей. Прочитайте це .
ColinD

@VineetReynolds пов'язане питання мертве.
dim8

19

Те, що всі інші сказали про оптимізацію, є абсолютно правдою.

З точки зору ефективності немає причини вбудовувати метод. Якщо це проблема продуктивності, 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 вам слід продовжувати і робити всі потрібні виклики методів.


1
Я не думаю, що він просив поради щодо стилю кодування, а навпаки, знаючи, дорогий виклик методу чи ні
Асаф Месіка,

3
Він чітко запитав: "Яка найкраща практика програмування?" - як найкраща практика, це цілком доречно.
Даніель Мартін,

2
Якщо взяти лише останнє речення, ви повністю втрачаєте контекст цього питання. Він хоче знати, чи дорого розбивати ваш великий метод на багато менших, оскільки виклик методу має ціну. Додавання відповіді, яка описує анти-шаблон для виклику не остаточного методу з конструктора, не враховується як відповідь на його запитання в цілому. О, і перевірте заголовок питання "наскільки дорогим є метод виклику"
Асаф Месіка,

6

Я точно залишив би його як є. Що, якщо змінити clear()логіку? Було б недоцільно знаходити всі місця, де ви скопіювали 2 рядки коду.


4

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

Чому? Тому що компілятор / точка доступу буде робити такі види оптимізації для вас на льоту, і багато, багато іншого. Якщо що, під час спроби оптимізації за такими типами ліній (хоча і не в цьому випадку) ви, мабуть, зробите все повільніше. Точка доступу розуміє загальні ідіоми програмування, якщо ви спробуєте зробити цю оптимізацію самостійно, вона, ймовірно, не зрозуміє, що ви намагаєтесь зробити, тому вона не зможе її оптимізувати.

Існує також набагато більша вартість обслуговування. Якщо ви почнете повторювати код, тоді це буде набагато більше зусиль для підтримання, що, ймовірно, буде набагато більше клопоту, ніж ви можете подумати!

Окрім того, ви можете дійти до деяких моментів у своєму житті кодування, де вам потрібно зробити оптимізацію низького рівня - але якщо ви досягнете цих моментів, ви точно будете точно знати, коли настане час. А якщо цього не зробити, ви завжди можете повернутися і оптимізувати пізніше, якщо вам потрібно.


3

Найкраща практика - виміряти двічі і один раз вирізати.

Як тільки ви витратите час на оптимізацію, ви більше ніколи не зможете повернути його назад! (Тож спочатку виміряйте його і запитайте себе, чи варто це оптимізувати. Скільки фактичного часу ви заощадите?)

У цьому випадку Java VM, мабуть, вже виконує оптимізацію, про яку ви говорите.


3

Вартість виклику методу є створення (видалення і обробка ) кадру стека і деякі додаткові вирази байт коду , якщо вам потрібно передати значення методу.


1

Шаблон, якого я дотримуюся, полягає в тому, чи задовольняв би цей метод чи ні одне з наступного:

  • Чи було б корисно, щоб цей метод був доступний поза цим класом?
  • Чи було б корисно, щоб цей метод був доступний в інших методах?
  • Чи було б неприємно переписувати це щоразу, коли мені це було потрібно?
  • Чи можна підвищити універсальність методу, використовуючи декілька параметрів?

Якщо що-небудь із наведеного вище відповідає дійсності, його слід обернути своїм власним методом.


Легше не задавати ці питання, а просто ввести проклятий код у свій власний метод!
Arafangion

2
Запитувач цікавиться, якою має бути деталізація його викликів методів. Немає необхідності створювати метод для збільшення цілого числа, якщо ви можете просто використовуватиi++;
Peaches491

Насправді створення методу має велику цінність, навіть якщо немає шансів його колись використати повторно. Просто присвоєння імені блоку коду та створення загальної структури само по собі є великою перевагою.
Джоффрі,

1

Зберігайте clear()метод, коли це допомагає читати. Наявність неможливого коду дорожче.


1

Оптимізаційні компілятори зазвичай роблять досить непогану роботу, видаляючи надмірність від цих "зайвих" операцій; у багатьох випадках різниця між "оптимізованим" кодом і кодом, написаним просто так, як ви хочете, і запущеним через оптимізуючий компілятор відсутня; тобто, оптимізуючий компілятор зазвичай робить настільки ж хорошу роботу, як і ви, і робить це, не викликаючи погіршення стану вихідного коду. Насправді, багато разів "оптимізований вручну" код виявляється менш ефективним, оскільки компілятор враховує багато речей при оптимізації. Залиште свій код у зручному для читання форматі і не турбуйтеся про оптимізацію до пізнішого часу.

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


0

Я б не хвилювався про виклик методу стільки, скільки логіку методу. Якби це були критично важливі системи, і тоді система мала бути "швидкою", я б розглянув питання оптимізації кодів, на виконання яких потрібно багато часу.


0

Враховуючи пам’ять сучасних комп’ютерів, це дуже недорого. Завжди краще розбити ваш код на методи, щоб хтось міг швидко прочитати, що відбувається. Це також допоможе при звуженні помилок у коді, якщо помилка обмежена одним методом із тілом із декількох рядків.


0

Як вже говорили інші, вартість виклику методу є тривіальною, оскільки компілятор оптимізує її для вас.

Тим не менш, існують небезпеки у виклику методів методів екземпляра з конструктора. Ви ризикуєте пізніше оновити метод екземпляра, щоб він міг спробувати використати змінну екземпляра, яку ще не ініціював конструктор. Тобто, вам не обов’язково потрібно відокремлювати будівельну діяльність від конструктора.

Інше питання - ваш метод clear () встановлює для кореня значення EMPTY, яке ініціалізується при створенні об’єкта. Якщо ви потім додасте вузли до EMPTY, а потім викличете clear (), ви не скинете кореневий вузол. Це така поведінка, яку ви хочете?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.