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 и/или линкера чтобы экземпляры созданные из одного шаблона при возможности не дублировались исполняемом файле?

★★

Последнее исправление: redbaron (всего исправлений: 1)

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

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

Есть ли хитрые флаги для 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 ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.