производительность парадигмы интерфейса (динамическое связывание или общее программирование) - PullRequest
7 голосов
/ 19 сентября 2011

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

Пример кода (только для справки)

A) динамическое связывание

namespace DB {
  // interface
  class CustomCode {
    public:
      virtual void operator()(char) const = 0;
  };
  class Lib {
    public:
      void feature(CustomCode const& c) {
        c('d');
      }
  };

  // user code
  class MyCode1 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib lib;
    lib.feature(MyCode1());
    lib.feature(MyCode2());
  }
}

B) общее программирование

namespace GP {
  //interface
  template <typename CustomCode> class Lib {
    public:
      void feature(CustomCode const& c) {
        c('g');
      }
  };

  // user code
  class MyCode1 {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib<MyCode1> lib;
    lib.feature(MyCode1());
    //lib.feature(MyCode2());  <-- illegal
  }
}

Вопрос

Некоторые мысли

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

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

Core

  1. Существуют ли какие-либо общие результаты / сравнительные исследованияэти парадигмы?
  2. Значительно ли ускорение?
  3. А как насчет времени компиляции?
  4. Какое значение имеет дизайн для интерфейсов в более крупных системах (я в основном использовал A для моих межмодульных интерфейсов, и я до сих пор не реализовал действительно большие проекты)?

Редактировать

Примечание: динамическое связывание лучше, потому что оно более«Мощный» вовсе не является ответом, потому что предварительное условие состоит в том, что у вас есть случай, когда оба подхода применимы (иначе нет свободы выбора - по крайней мере, не разумно).

Ответы [ 2 ]

6 голосов
/ 19 сентября 2011

Существуют ли общие результаты / сравнительные исследования этих парадигм?

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

Значительно ли ускорение?

короткий ответ: это зависит.

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

расширение «это зависит»: да, скорость может варьироваться от неизмеримой до огромной. Вы должны (и, вероятно, уже) осознавать, что компилятору может быть предоставлено невероятное количество информации при компиляции через дженерики. Затем он может использовать эту информацию для более точной оптимизации вашей программы. Один хороший пример этого - использование std::array против std::vector. вектор добавляет гибкости во время выполнения, но стоимость может быть довольно значительной. вектор должен реализовывать больше для изменения размера, потребность в динамическом распределении может быть дорогостоящей. Есть и другие отличия: резервное распределение массива не изменится (оптимизация ++), число элементов является фиксированным (оптимизация ++), и снова - во многих случаях нет необходимости вызывать new.

теперь вы можете думать, что этот пример значительно отклонился от первоначального вопроса. во многих отношениях это на самом деле не так уж и отличается: компилятор все больше и больше узнает о вашей программе по мере ее сложности. эта информация может удалить несколько частей вашей программы (мертвый код), и, используя в качестве примера std::array, информация, которую предоставляет тип, достаточна для того, чтобы компилятор мог легко сказать: «О, я вижу, что размер этого массива составляет семь элементов, соответственно развернете цикл ", и у вас будет меньше инструкций, и вы устраните неправильные прогнозы. это намного больше, но в случае массива / вектора, я видел, что размер исполняемых оптимизированных программ уменьшился до 20% при преобразовании из vector в интерфейс, подобный array. также код может работать в несколько раз быстрее. фактически некоторые выражения могут быть полностью вычислены при компиляции.

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

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

А как насчет времени компиляции?

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

Какое значение имеет дизайн для интерфейсов в более крупных системах (я в основном использовал A для своих межмодульных интерфейсов, и я до сих пор не реализовывал действительно большие проекты)?

это действительно зависит от ожиданий вашей программы. я пишу virtual меньше с каждым годом (как и многие другие). среди других подходов шаблоны становятся все более распространенными. честно говоря, я не понимаю, как B является «анархическим». для меня A немного анахронично, так как есть множество подходящих альтернатив. в конечном счете, это выбор дизайна, который может потребовать много внимания для хорошей разработки больших систем. хорошая система будет использовать здоровую комбинацию возможностей языка. история доказывает, что для написания нетривиальной программы не требуется никаких функций в этом обсуждении, но все функции были добавлены, потому что кто-то видел лучшие альтернативы в некоторых конкретных целях. вы также должны ожидать, что лямбды заменят виртуальные в более чем 50% их текущего использования в некоторых (не всех ) командах / кодовых базах.

Обобщения:

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

Итог большая, хорошо спроектированная современная система будет эффективно и одновременно использовать много парадигм. если вы используете виртуалы большую часть времени в настоящее время, вы (imo) делаете это неправильно - особенно если это все еще тот подход, когда вы успели освоить c ++ 11. если скорость, производительность и / или параллелизм также являются важными проблемами, то шаблоны и лямбды заслуживают того, чтобы быть вашими близкими друзьями.

1 голос
/ 19 сентября 2011

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

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

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

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

...