Уменьшение раздувания шаблона с наследованием - PullRequest
9 голосов
/ 14 июня 2010

Есть ли у кого-нибудь опыт по уменьшению раздувания кода шаблона с использованием наследования?

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

class vectorBase
{
  public:
    int size();
    void clear();
    int m_size;
    void *m_rawData;
    //....
};

template< typename T > 
class vector : public vectorBase
{
    void push_back( const T& );
    //...

};

Я должен сохранять максимальную производительность при сокращении времени компиляции

Мне также интересно, почему реализации stl не используют этот подход

Спасибо за ваши отзывы

Ответы [ 7 ]

3 голосов
/ 14 июня 2010

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

На самом деле вы не можете многое сделать с void *m_rawData, не зная типов вещей внутри него, в основном все операции с ним по крайней мере должны знать размер хранимого типа. Единственное, о чем я могу думать, это то, что вы можете free() сделать это, если знаете, что он не содержит элементов (если он содержит элементы, вы должны вызывать их деструкторы). Выделение, установка и доступ к элементам не работает, если вы не знаете, где начинаются и заканчиваются отдельные элементы. Также реализация всех методов была бы намного чище и проще, если бы m_rawData был бы правильно набран T*.

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

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

1 голос
/ 14 июня 2010

Я понимаю ваш подход.

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

Меня не волнует время компиляции: это смущающе параллельная проблема (не считая ссылки), а distcc и т.д. заботятся обо всех проблемах, которые могут возникнуть дажебольшая кодовая база.И я имею в виду большое, я работаю в компании, которая требовала нового компилятора от HP, потому что версия, которую мы имели, не поддерживала более 128Ko ... в командной строке компоновщика.И это было только одно из приложений, и это было несколько лет назад, и с тех пор, к счастью, они разделили его на несколько частей.

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

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

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

Однако я бы не стал использовать наследование для этой работы.Inheritance - это отношение IS-A: оно определяет интерфейс, а не реализацию.Либо используйте Composition, либо просто бесплатные функции, которые вы храните в пространстве имен утилит (detail как в Boost?).

1 голос
/ 14 июня 2010

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

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

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

РЕДАКТИРОВАТЬ: Я не уверен, что есть много выгоды в некоторых стандартных случаях контейнера, так как им все еще нужно много кода шаблона. Для внутренних классов, которые имеют только небольшое количество специфичного для шаблона кода и много общей логики, это определенно может помочь как сгенерированному коду, так и скорости компиляции. Я подозреваю, что это не часто используется, потому что это более сложно, и выгоды ограничены определенными сценариями.

0 голосов
/ 15 июня 2010

В некоторых реализациях do используется (форма) вышеуказанного подхода.Вот GCC

  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    {
    ... 
    }

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

0 голосов
/ 14 июня 2010

Короче говоря:

Да, этот подход [вероятно] будет работать в ограниченных, специализированных условиях. Я не подозреваю, что std::vector (или остальная часть STL) относятся к таким обстоятельствам.

Длинна этого:

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

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

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

Как указывалось в некоторых ответах, нечего абстрагироваться от std::vector, который мог бы сделать его более справедливым в вашем примере. Конечно, вам нужно иметь возможность создавать и уничтожать отдельные элементы, и любые методы virtual будут препятствовать производительности во время выполнения (что более важно, чем производительность во время компиляции). Кроме того, компилятор потеряет способность оптимизировать библиотеку void* для шаблонного кода, что может привести к значительной потере производительности.

Меня больше интересуют более сложные структуры - выиграет ли std::map от абстракции? Что если вы взяли красно-черное дерево (реализацию SGI) и соединились с библиотекой красно-черного дерева. Вам (вероятно) нужно хранить элементы вне std::map, поэтому не нужно вызывать деструкторы, но это может (опять же) привести к потере производительности во время выполнения из-за удвоения вашей косвенности.

Я вполне уверен, что вы не могли использовать этот метод для улучшения реализации STL.

Если бы вы лучше знали структуры данных, которые вы хранили, или у вас был очень специфический шаблонный тип (не обязательно контейнер), вы, вероятно, могли бы улучшить свою производительность. Но тогда возникает вопрос - как часто вы будете использовать этот новый шаблонный тип, так что накладные расходы на его компиляцию будут заметно улучшены? Конечно, это поможет компилировать время для std::vector, но, возможно, не для my_domain_specific_ptr_container. Если вы сэкономите 50% времени компиляции для my_domain_specific_ptr_container, сколько раз вам придется использовать его, чтобы заметить достаточно значительное ускорение сборки, чтобы оправдать добавленную сложность для класса (и уменьшенную возможность отладки).

Если вы еще этого не сделали, ваше время может быть лучше потрачено на распространение инструментов сборки:)

Если, с другой стороны, вы попробуете это, и это будет работать для контейнеров STL ... пожалуйста, отправьте сообщение обратно. Я хочу быстрее построить! ;)

0 голосов
/ 14 июня 2010

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

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

(Чтобы использовать ваш подход с базовым классом, не связанным с шаблоном, вам нужно сгенерировать столько же машинного кода, но вам нужно написать намного больше кода C ++ вручную.)

Вот почему такой подход не имеет смысла.

0 голосов
/ 14 июня 2010

IIRC, Qt использует (или использовал?) Аналогичный подход для своего QList и др.

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

Это, безусловно, окупается, когда вы можете абстрагироваться от какого-то параметра шаблона (например, set<T>::iterator не нужно знать о компараторе набора)или если вы можете создать полную реализацию для большого класса типов (например, с помощью тривиальных copy-ctor и dtor).

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