Означает ли кодирование к интерфейсу, а не к реализации, снижение производительности? - PullRequest
5 голосов
/ 06 мая 2009

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

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

У кого-нибудь есть статистика? Я не вижу хорошего способа проверить / измерить это, так как не знаю, какие последствия я должен иметь в виду с таким игровым (графическим) объектом.

Ответы [ 8 ]

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

Кодирование интерфейса всегда будет проще, просто потому что интерфейсы, если все сделано правильно, намного проще. Заметно проще написать правильную программу, используя интерфейс.

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

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

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

Что стоит в управляемом коде

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

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

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

Но для правильной работы всегда требуется больше времени.

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

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

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

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

Также я бы предположил, что если бы это снижение производительности было правдой, большинство игр не использовали бы ООП-подход с C ++, но это не тот случай, в этой статье об этом немного говорится.

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

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

Интерфейсы обычно подразумевают несколько падений производительности (однако это может измениться в зависимости от используемого языка / среды выполнения):

  1. Методы интерфейса обычно реализуются компилятором через виртуальный вызов. Как указывает другой пользователь, они не могут быть встроены компилятором, поэтому вы потеряете этот потенциальный выигрыш. Кроме того, они добавляют как минимум несколько инструкций (переходы и доступ к памяти), чтобы получить нужный ПК в сегменте кода.
  2. Интерфейсы на многих языках также подразумевают граф и требуют DAG (ориентированный ациклический граф) для правильного управления памятью. В разных языках / средах исполнения вы можете фактически получить утечку памяти в управляемой среде, имея циклический граф. Это создает большую нагрузку (очевидно) на сборщик мусора / память в системе. Остерегайтесь циклических графиков!
  3. Некоторые языки используют интерфейс в стиле COM в качестве базового интерфейса, автоматически вызывая AddRef / Release, когда интерфейс назначается локальному или передается по значению функции (используется для управления жизненным циклом). Эти вызовы AddRef / Release могут складываться и быть довольно дорогостоящими. Некоторые языки учитывают это и могут позволить вам передавать интерфейс как const, который не будет генерировать пару AddRef / Release, автоматически сокращающую эти вызовы.

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

interface Parent {
  Child c;
}

interface Child {
  Parent p;
}

function createGraph() {
  ...
  Parent p = ParentFactory::CreateParent();
  Child c = ChildFactory::CreateChild();

  p.c = c;
  c.p = p;      
  ...  // do stuff here

  // p has a reference to c and c has a reference to p.  
  // When the function goes out of scope and attempts to clean up the locals
  // it will note that p has a refcount of 1 and c has a refcount of 1 so neither 
  // can be cleaned up (of course, this is depending on the language/runtime and
  // if DAGS are allowed for interfaces).  If you were to set c.p = null or
  // p.c = null then the 2 interfaces will be released when the scope is cleaned up.
}
1 голос
/ 16 января 2018

IMO да, но по фундаментальной причине проектирования, гораздо более тонкой и сложной, чем запросы виртуальной диспетчеризации или COM-подобные интерфейсы или метаданные объекта, необходимые для информации о типе среды выполнения или чего-либо подобного. Все это связано с накладными расходами, но во многом зависит от используемого языка и компилятора (ов), а также от того, может ли оптимизатор устранить такие издержки во время компиляции или компоновки. Тем не менее, по моему мнению, существует более широкая концептуальная причина, по которой кодирование интерфейса подразумевает (а не гарантирует) снижение производительности:

Кодирование интерфейса подразумевает наличие барьера между вами и конкретные данные / память, к которым вы хотите обращаться и преобразовывать.

Это основная причина, которую я вижу. В качестве очень простого примера, скажем, у вас есть интерфейс абстрактного изображения. Он полностью абстрагирует свои конкретные детали, такие как формат пикселей. Проблема здесь в том, что часто наиболее эффективные операции с изображениями нуждаются в этих конкретных деталях. Мы не можем реализовать наш пользовательский фильтр изображений с эффективными инструкциями SIMD, например, если нам нужно было getPixel по одному за раз и setPixel по одному за раз, не обращая внимания на основной формат пикселей.

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

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

Как правило, на каком-то уровне часто приходится писать наиболее эффективный код на основе конкретных деталей, например, код, написанный специально для плавающей запятой одинарной точности, код, написанный специально для 32-битных изображений RGBA, код, написанный специально для графического процессора, специально для AVX-512, специально для мобильного оборудования и т. д. Таким образом, существует фундаментальный барьер, по крайней мере с имеющимися у нас инструментами, где мы не можем абстрагироваться от всего этого и просто кодировать интерфейс без подразумеваемых штрафов .

Конечно, наша жизнь стала бы намного проще, если бы мы могли просто написать код, не обращая внимания на все такие конкретные детали, как, например, имеем ли мы дело с 32-битным SPFP или 64-битным DPFP, пишем ли мы шейдеры на ограниченное мобильное устройство или высококлассный рабочий стол, и все это должно быть наиболее конкурентоспособным кодом. Но мы далеки от этой стадии. Наши современные инструменты все еще часто требуют от нас написания кода, критичного к производительности, с учетом конкретных деталей.

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

1 голос
/ 06 мая 2009

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

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

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

0 голосов
/ 06 мая 2009

это будет означать снижение производительности

Дизайнер должен иметь возможность доказать свое мнение.

0 голосов
/ 06 мая 2009

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

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