LINUX.ORG.RU

Как смержить дублирующийся код? (решение: использовать gold)

 ,


0

2

Заглянул тут в вывод «objdump -C» и немало удивился: оказалось, что для различных параметров шаблона генерируются отдельные копии кода, даже если сам код абсолютно идентичный.

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

#include <cstddef>
#include <ctime>

template<int I, class T>
struct V {
	typedef T value_type;
};


template<class T>
struct func {
void __attribute__((noinline))
operator()(typename T::value_type& v) { v+= 1; } 
};


int main() {
	int i = time(NULL);
	func< V<10,int> >()(i);
	func< V<20,int> >()(i);
	return i;
}

Есть ли хитрые флаги для gcc и/или линкера чтобы экземпляры созданные из одного шаблона при возможности не дублировались исполняемом файле?

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

«Я познаю мир».

Есть ли хитрые флаги для gcc и/или линкера чтобы экземпляры созданные из одного шаблона при возможности не дублировались исполняемом файле?

Нет.

DELIRIUM ☆☆☆☆☆ ()

Есть ли хитрые флаги для gcc и/или линкера чтобы экземпляры созданные из одного шаблона при возможности не дублировались исполняемом файле?

C++ так не работает. У V<10,int> и V<20,int> всегда будет отдельная копия. Более того, целочисленные шаблонные параметры обычно применяются специально для отпимизации, так как в первом случае компилятор подставляет вместо I число 10, а во втором 20.

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

annulen ★★★★★ ()

Есть ли хитрые флаги для gcc и/или линкера чтобы экземпляры созданные из одного шаблона при возможности не дублировались исполняемом файле?

есть. Не юзать шаблоны.

emulek ()

Есть ли хитрые флаги для gcc и/или линкера

да, эти хитрые флаги называются флагами оптимизации, -Ofast и -flto например

wota ★★ ()
Ответ на: комментарий от annulen

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

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

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

  • Ставим gold
  • Компилируем с
    mkdir -p /tmp/gold
    ln -s /usr/bin/gold /tmp/gold/ld
    g++ -B/tmp/gold -Wl,--icf=all -O3 -ffunction-sections -fdata-sections -o /tmp/b /tmp/b.cpp
    
  • В objdump видим, что создался только один экземпляр функции.
redbaron ★★ ()
Последнее исправление: redbaron (всего исправлений: 1)

Искал такие флаги довольно внимательно (включая то, что современный линкер может сделать с -flto), не нашёл; похоже, проблема никуда не делась. Тут выше написали, что «C++ так не работает», но довольно очевидно, что никто не запрещает компилятору так работать, просто руки не дошли (что как раз неудивительно, поскольку с C++ других забот по горло).

Пример с attribute((noinline)) не особо показателен, правда: вы прямо-таки явно заказываете «совершенно честный экземпляр функции» на каждый экземпляр шаблона; компилятор мог бы сделать одну из них jmp на другую, но вот перенаправить вызовы на один и тот же код было бы с его стороны некрасиво. А если инлайн разрешить, на вашем примере уже ничего в коде не разглядишь. Лучше тогда внутрь шаблона что-то посложнее (подсчет Фибоначчи вместо инкремента), плюс вызовы к разным экземплярам шаблона делать не из main, а из двух функций (экспортируемых). Предрекаю в этом случае два экземпляра кода даже без noinline.

LeninGad ()
Ответ на: комментарий от redbaron

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

Тогда стоит общую часть вынести в базовый клас, а шаблон класса наследовать от него.

mashina ★★★★★ ()

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

Enjoy your C++. You're welcome.

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

buddhist ★★★★★ ()

Это называется code bloat. Одно из решений, это вынести функционал, независящий от параметра шаблона в нешаблонный базовый класс и унаследовать шаблонный класс от него.

anonymous ()
Ответ на: комментарий от redbaron

Зачем первые две команды?

ln -sf ld.gold /usr/bin/ld

(При условии, что ваш binutils не архаичен и собран с помощью ./configure --enable-gold --enable-plugins)

intelfx ★★★★★ ()
Ответ на: комментарий от LeninGad

Как оказалось проблема (проблема ли впрочем? :) ) решается при использовании gold линкера.

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

redbaron ★★ ()
Ответ на: комментарий от intelfx

Не рискну заменять системный линкер, пока дистре это не сделают официально :) Сам gold идет в отдельном пакете в OpenSUSE, так что мне пришлось его доустановить.

redbaron ★★ ()
Ответ на: комментарий от redbaron

Ну в принципе, тулчейн мало для чего нужен системе как таковой. Он нужен разработчику.

Ну вот лично у меня в /usr/bin есть три компоновщика - ld.bfd, ld.gold и ld, являющийся хардлинком на ld.bfd. Так что в принципе это недеструктивная операция.

intelfx ★★★★★ ()
Ответ на: комментарий от anonymous

Не все так просто и очевидно. Методы класса о которых я говорю не то, чтобы фиксированы, они зависят все же от параметра шаблона, от его внутренних typedef'ов. Так уж получилось, что при N встречающихся парметров, всего M typedef'ов (M<N) которые получаются в результате жуткой шаблонной оргии :)

Да и вообще, С++ и так многословен, плодить еще базовые классы, только чтобы попричесанней было... и так писанины нудной много :(

redbaron ★★ ()
Ответ на: комментарий от wota

Очень интересно, давай разбираться.

Комплирую так:

g++-4.8 -Ofast -flto -o /tmp/b /tmp/b.cpp

В objdump вижу, что вызывает 2 разных адреса:

  400544:       e8 27 01 00 00          callq  400670 <func<V<10, int> >::operator()(int&) [clone .isra.0.2380.2388]>
  400549:       48 8d 7c 24 0c          lea    0xc(%rsp),%rdi
  40054e:       e8 0d 01 00 00          callq  400660 <func<V<20, int> >::operator()(int&) [clone .isra.1.2388.2380]>

Пробовал и clang++3.3, та же история.

Ты чем и как компилируешь?

redbaron ★★ ()
Ответ на: комментарий от intelfx

пробовал. Пробовал и компилировать сначала в .o файл и потом линковать в бинарник - всё одно.

redbaron ★★ ()
Ответ на: комментарий от redbaron

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

annulen ★★★★★ ()
Ответ на: комментарий от buddhist

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

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

annulen ★★★★★ ()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от annulen

Код зависит от шаблонных параметров, просто при N параметрах встречающихся в программе, генерируется M версий (на бинарном уровне) кода некоторых методов (M < N).

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

redbaron ★★ ()
Ответ на: комментарий от redbaron

как может сложиться ситуация, что коду необходимо обращаться к конкретному экземпляру шаблона из разделяемой библиотеки?

При явном инстанцировании

annulen ★★★★★ ()
Ответ на: комментарий от redbaron

В objdump видим, что создался только один экземпляр функции.

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

Pavval ★★★★★ ()
Ответ на: комментарий от redbaron

А что мешает твоему базовому классу быть шаблонным, как раз тики, с M параметрами?

Как-то так:

template<typename S1, typename S2, /* ... */ typename SM>
class B { /* ... */ };

template<typename T1, typename T2, /* ... */ typename TN>
class C : public B<F<T1, T2, /* ... */ TN>::S1, F<T1, T2, /* ... */ TN>::S2, /* ... */ F<T1, T2, /* ... */ TN>::SM>
{ /* ... */ };

За F здесь спрятана твоя шаблонная оргия с тайпдефами.

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

anonymous ()
Ответ на: комментарий от Pavval

Интересное замечание, не задумывался об этом. Судя по gold --help на этот случай есть "--icf=safe"

redbaron ★★ ()
Ответ на: комментарий от anonymous

Прошу прощения, забыл про typename перед F<T1, T2, /* ... */>::S1 и др.

anonymous ()
Ответ на: комментарий от anonymous

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

Более близкий к моему случай будет таким:

template <typename T> //в конкретный бинарник попадает N возможных T
class A {
  typedef typename T::x x_t; //для всех N возможных T, всего Mx возможных значений T::x (Mx < N)
  typedef typename T::y y_t; // My < N
  typedef typename F<x_t, y_t>::type xy_t; //Mxy < N
  x_t funcX();
  y_t funcY();
  xy_t funcXY(x_t, y_t);
}

Твое предложение, если я правильно понял выносить каждую группу методов в отдельный базовый класс (funcX_base, funcY_base, funcXY_base), но такое деление не продиктовано ничем, кроме распухания кода (о чем и топик собственно). Ну не принадлежат эти методы разным классом, так получилось, что некоторые M меньше N (а некоторые нет), но это не причина растаскивать все по частям. Завтра разработчик нагенерит новых T с бОльшим разнообразем T::y и T::x и все - никаких примуществ от множества базовых классов не останется, зато веселья, если вдруг funcXY() захочет вызвать funcX() будет много.

Может гуру С++ и готовы на любые лишения ради эффективности, но для меня пока проще иметь тип T в нем пачка typedef'ов и таскать его по разным кускам кода, используя его подобно struct, где каждый typedef как поле структуры.

Зато темплейтная жуть в одном месте и подальше от глаз, в то время, как остальной код просто дергает нужные «поля» уже скомпонованные и готовые к применению.

redbaron ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.