Затраты на реализацию интерфейса - PullRequest
12 голосов
/ 21 мая 2009

Один из моих коллег сказал мне, что реализация интерфейсов приводит к накладным расходам. Это правда?

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

Ответы [ 12 ]

17 голосов
/ 21 мая 2009

не выдержал и протестировал его, и похоже, что почти нет накладных расходов.

Участники:

Interface IFoo    defining a method
class Foo: IFoo   implements IFoo
class Bar         implements the same method as Foo, but no interface involved

так я определил

Foo realfoo = new Foo();
IFoo ifoo = new Foo();
Bar bar =  new Bar();

и вызвал метод, который выполняет 20 конкатенаций строк 10 000 000 раз для каждой переменной.

realfoo:   723 Milliseconds
ifoo:      732 Milliseconds
bar:       728 Milliseconds

Если метод ничего не делает, фактические вызовы выделяются немного больше.

  realfoo: 48 Milliseconds
  ifoo: 62 Milliseconds
  bar: 49 Milliseconds
12 голосов
/ 21 мая 2009

Если говорить с точки зрения Java , то, по крайней мере в последних версиях Hotspot, ответ обычно небольшой или вообще не накладные расходы, когда это имеет значение .

Например, предположим, у вас есть такой метод, как следующий:

public void doSomethingWith(CharSequence cs) {
  char ch = cs.charAt(0);
  ...
}

CharSequence - это, конечно, интерфейс. Таким образом, вы можете ожидать, что метод должен будет выполнить дополнительную работу, проверить, какой тип объекта, найти метод, и при этом, возможно, выполнить поиск интерфейсов и т. Д. И т. Д. - по сути, все страшные истории, которые вы можете себе представить ...

Но на самом деле виртуальная машина может быть намного умнее этого. Если получится, что на практике вы всегда передаете объекты определенного типа, тогда он не только может пропустить проверку типа объекта, но и может даже встроить метод. Например, если вы вызываете метод, такой как приведенный выше, в цикле для ряда строк, Hotspot может фактически встроить вызов charAt (), так что получение символа буквально становится парой инструкций MOV - другими словами, вызов метода в интерфейсе может превратиться даже в отсутствие вызова метода . (P.S. Эта информация основана на выводе сборки из отладочной версии 1.6 обновления 12.)

9 голосов
/ 21 мая 2009

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

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

В подавляющем большинстве случаев преимущества использования вызовов виртуальных функций значительно уменьшают недостатки.

6 голосов
/ 21 мая 2009

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

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

4 голосов
/ 21 мая 2009

Если у вас нет особых требований (то есть «мы создаем игру, и она должна работать со скоростью 60 кадров в секунду, компоновка данных, согласованность кэша и преобразование упомянутых данных невероятно важны»), я бы даже не стал думать о дополнительные затраты производительности на интерфейсный вызов при написании программного обеспечения. Это будет заметно только в определенных обстоятельствах.

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

Кроме того, ничто не мешает вам изменить контракт интерфейса, чтобы сделать вызов более грубым, например, IMyInterface.Process(Car[] cars) вместо IMyInterface.Process(Car car)

В Code Complete Стив Макконнелл советует против постоянной микрооптимизации. Он говорит, что лучше всего написать программу с использованием передового опыта (т. Е. Сконцентрироваться на удобстве обслуживания, удобочитаемости и т. Д.), А затем, как только вы закончите, если производительность не достаточно хорошая, профилировать ее и предпринять шаги для устранения основных узких мест.

Нет смысла спекулятивно оптимизировать весь ваш код только потому, что он может быть быстрее. Если 80% вашего времени выполнения тратится на выполнение 20% кода, очевидно, что глупо жертвовать слабой связью повсюду «просто потому, что» это может сократить время на 10 микросекунд здесь или там. Таким образом, вы экономите 10 микросекунд, но ваша программа не будет работать быстрее, если какая-то другая функция поглотит процессор.

Если вы работаете с критически важным программным обеспечением, я бы классифицировал отказ от интерфейсов как микрооптимизацию. Это также то, что может быть легко удалено впоследствии при необходимости (при условии, что вы не отправляете программное обеспечение в ближайшее время). Большинству программ не нужна высокая скорость. Существуют исключения (игры, симуляторы, торговля акциями в реальном времени и т. Д.), Но даже в этом случае виноваты не всегда интерфейсы.

4 голосов
/ 21 мая 2009

Да, интерфейсы несут накладные расходы. Фактически, каждый слой, который вы добавляете между вашей логикой и процессором, добавляет накладные расходы. Очевидно, что вы должны писать все на ассемблере, потому что это единственное, что не требует дополнительных затрат. GUI также несет накладные расходы, поэтому не используйте их.

Я веду себя шутливо, но дело в том, что вы должны найти свой собственный баланс между понятным, понятным, поддерживаемым кодом и производительностью. Для 99,999% (повторяющихся, конечно) приложений, если вы помните, чтобы избежать ненужного повторения выполнения любого из ваших более дорогих методов, вы никогда не добьетесь того, чтобы вам было что-то сложнее поддерживать просто ради того, чтобы он работал быстрее.

3 голосов
/ 21 мая 2009

К сожалению, для Java есть еще некоторая оптимизация, которая может быть сделана для улучшения производительности интерфейса. Да, в инструкциях invokevirtual и invokeinterface «почти нет накладных расходов» по ​​сравнению с invokespecial, но есть проект Da Vinci , который нацелен на снижение производительности при очень распространенном тривиальном использовании интерфейсов: объект, который реализует только один интерфейс и никогда не перегружается.

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

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

3 голосов
/ 21 мая 2009

накладные расходы по сравнению с чем?

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

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

2 голосов
/ 08 марта 2011

Из любопытства я реализовал небольшой тест . Код, согласно моей идее, должен блокировать JVM от результатов разрешения метода кэширования и, таким образом, должен показывать четкую разницу между invokeinterface и invokevirtual в Java.

Результатом теста является то, что invokeinterface на 38% медленнее, чем invokevirtual .

2 голосов
/ 21 мая 2009

Хотя интерфейс НЕ ДОЛЖЕН нести накладные расходы, так или иначе это происходит. Я не знаю этого напрямую, но из вторых рук, мы работаем над кабельными коробками, и они настолько слабы, что мы тестируем разные сценарии производительности. (Установленное количество классов имеет ОГРОМНУЮ разницу).

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

И все же, это влияет на производительность. Я предполагаю, что это связано с метаданными / отражением, потому что если вам не нужны метаданные, то ТОЛЬКО время, когда будет использоваться интерфейс, - это приведение от менее специфичного интерфейса к этому интерфейсу, а затем потребуется только быть тегом, чтобы проверить и посмотреть, если это возможно.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...