Раздувание кода шаблона c неупорядоченной картой
интересно, если unordered_map
реализовано с использованием стирания типа, так как unordered_map<Key, A*>
и unordered_map<Key, B*>
может использовать точно такой же код (кроме литья, который является no-op в машинном коде). То есть реализация обоих может быть основана на unordered_map<Key, void*>
для сохранения размера кода.
обновление: этот метод обычно называют Тонкий Шаблон Идиома (спасибо комментаторам ниже за указание на это).
обновление 2: я бы particlarly интересует Говард Объектовмнение. Будем надеяться,он прочтет это.
поэтому я написал этот небольшой тест:
#include <iostream>
#if BOOST
# include <boost/unordered_map.hpp>
using boost::unordered_map;
#else
# include <unordered_map>
using std::unordered_map;
#endif
struct A { A(int x) : x(x) {} int x; };
struct B { B(int x) : x(x) {} int x; };
int main()
{
#if SMALL
unordered_map<std::string, void*> ma, mb;
#else
unordered_map<std::string, A*> ma;
unordered_map<std::string, B*> mb;
#endif
ma["foo"] = new A(1);
mb["bar"] = new B(2);
std::cout << ((A*) ma["foo"])->x << std::endl;
std::cout << ((B*) mb["bar"])->x << std::endl;
// yes, it leaks.
}
и определяется размер скомпилированного вывода с различными настройками:
#!/bin/sh
for BOOST in 0 1 ; do
for OPT in 2 3 s ; do
for SMALL in 0 1 ; do
clang++ -stdlib=libc++ -O${OPT} -DSMALL=${SMALL} -DBOOST=${BOOST} map_test.cpp -o map_test
strip map_test
SIZE=$(echo "scale=1;$(stat -f "%z" map_test)/1024" | bc)
echo boost=$BOOST opt=$OPT small=$SMALL size=${SIZE}K
done
done
done
оказывается, что со всеми настройками я пробовал, много внутреннего кода unordered_map
кажется, что экземпляр дважды:
With Clang and libc++:
| -O2 | -O3 | -Os
-DSMALL=0 | 24.7K | 23.5K | 28.2K
-DSMALL=1 | 17.9K | 17.2K | 19.8K
With Clang and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 23.9K | 23.9K | 32.5K
-DSMALL=1 | 17.4K | 17.4K | 22.3K
With GCC and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 21.8K | 21.8K | 35.5K
-DSMALL=1 | 16.4K | 16.4K | 26.2K
(С компиляторами из Xcode от Apple)
теперь вопрос: есть ли какая-то убедительная техническая причина, по которой разработчики решили опустить эту простую оптимизацию?
также: Почему, черт возьми, эффект -Os
ровно противоположное тому, что рекламируется?
обновление 3:
как предложил Николь Болас, я повторил измерения с shared_ptr<void/A/B>
вместо голых указателей (созданных с помощью make_shared
и бросил с static_pointer_cast
). Тенденция в результатах же:
With Clang and libc++:
| -O2 | -O3 | -Os
-DSMALL=0 | 27.9K | 26.7K | 30.9K
-DSMALL=1 | 25.0K | 20.3K | 26.8K
With Clang and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 35.3K | 34.3K | 43.1K
-DSMALL=1 | 27.8K | 26.8K | 32.6K
2 ответов
так как меня специально попросили прокомментировать, я буду, хотя я не уверен, что мне есть что добавить, чем уже было сказано. (извини, мне потребовалось 8 дней, чтобы добраться сюда)
я реализовал идиому тонкого шаблона раньше, для некоторых контейнеров, а именно vector, deque и list. В настоящее время я не реализовал его для любого контейнера в libc++. И я никогда не использовал его для неупорядоченных контейнеров.
он экономит на размере кода. Это также добавляет сложности, много больше, чем ссылка на ссылку wikibooks. Можно также сделать это не только для указателей. Вы можете сделать это для всех скаляров, которые имеют одинаковый размер. Например, почему существуют разные экземпляры для int
и unsigned
? Даже ptrdiff_t
может храниться в том же экземпляре, что и T*
. В конце концов, это всего лишь мешок на дне. Но очень сложно получить шаблоны членов, которые принимают ряд итераторов правильно при воспроизведении этих трюков.
там есть недостатки, хотя (помимо сложности реализации). Он не играет почти так же хорошо с отладчиком. По крайней мере, это значительно затрудняет отладчику отображение внутренностей контейнера. И хотя экономия размера кода может быть значительной, я бы не стал называть экономию размера кода драматической. Особенно по сравнению с памятью, необходимой для хранения фотографий, анимаций, аудиоклипов, карт улиц, лет электронной почты со всеми вложениями из вашего лучшие друзья и семья, и т. д. Т. е. оптимизация размера кода is важно. Но вы должны учитывать, что во многих приложениях сегодня (даже на встроенных устройствах), если вы сократите размер кода наполовину, вы можете сократить размер приложения на 5% (статистика, по общему признанию, вытащена из воздуха).
моя текущая позиция заключается в том, что эта конкретная оптимизация лучше всего оплачивается и реализуется в компоновщике, а не в контейнере шаблонов. Хотя я знаю, что это нелегко реализовать в linker я слышал об успешных реализациях.
это, как говорится, я все еще пытаюсь сделать оптимизацию размера кода в шаблонах. Например, в вспомогательных структурах libc++, таких как __hash_map_node_destructor
шаблонизированы как можно меньше параметров, поэтому, если какой-либо из их кода будет изложен, более вероятно, что один экземпляр помощника может служить более одного экземпляра unordered_map
. Этот метод является дружественным к отладчику, и не так сложно получить право. И даже может иметь некоторые положительные побочные эффекты для клиента при применении к итераторам (N2980).
в общем, я бы не держал его против кода для прохождения дополнительной мили и реализации этой оптимизации. Но я также не стал бы классифицировать его как высокий приоритет, как десять лет назад, и потому, что технология компоновщика прогрессировала, и отношение размера кода к размеру приложения имело тенденцию довольно резко уменьшаться.
когда у вас есть параметр void*, во время компиляции нет проверки типа.
такие карты, как те, которые вы предлагаете, будут недостаток в программе, так как они будут принимать элементы значений типа A*, B* и еще более невообразимые причудливые типы, которые не будут иметь ничего общего с этой картой. ( например, int*, float*; std:: string*, CString*, CWnd*... представьте себе беспорядок на вашей карте...)
ваша оптимизация преждевременна. И преждевременная оптимизация-это корень все зло.