2016-09-05 2 views
1

Я пытаюсь сохранить объекты в одном из нескольких типов контейнеров, тип контейнера известен во время выполнения. Интерфейс контейнеров такой же, но контейнеры не полиморфный.Неполиморфные контейнеры во время выполнения

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

Считают, что у меня есть контейнеры:

class Vector1<T> 
{ 
    T get(); 
} 
class Vector2<T> 
{ 
    T get(); 
} 

И я хочу, чтобы иметь контейнер, либо Vector1 или Vector2 в зависимости от некоторого параметра времени выполнения.

Так что мой класс должны быть:

Class VectorWrapper 
{ 
     Vector1<SomeType> v1; 
     Vector2<Sometype> v2; 

     inline Sometype get() 
     { 
      **how do I choose between v1 and v2, without the extra jump?** 
     } 
} 
+2

Это звучит как преждевременная оптимизация. Вы измерили? –

+0

Да, конечно. Эта оптимизация связана с тем, что производительность этой функции обнаруживается на критическом пути, где каждый скачок, который мы делаем, учитывается, следовательно, код, который может быть встроен, повышает производительность. Мы видели фактические пробелы в производительности при попытке прототипа, который использует, например, только Vector1, а не вариант, который использует полиморфную версию Vector1, Vector2 –

+2

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

ответ

0

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

template<class Container> 
void criticalPath() 
{ 
    // Create and use Container objects a lot 
} 


void enterCriticalPath(bool useVector1) { 
    if (useVector1) { 
     criticalPath<Vector1<SomeType>>(); 
    } else { 
     criticalPath<Vector2<SomeType>>(); 
    } 
} 
+0

, но вместо этого вы вынуждаете «init» выполнять каждый вызов критического пути. Это то же самое, что и if/else внутри критического пути. Если вы не можете каким-либо образом представить способ хранения указателя на контейнер-шаблон, я не смог бы похудеть. –

+0

@MaxShifrin: «* Это то же самое, что иметь if/else внутри критического пути. *« Как * начать * критический путь сам по себе частью критического пути? –

+0

@ Никол Болас, очевидно, что инициализация критического пути не ограничена по времени, но предположение, приведенное выше, заставило меня иметь if if else во всех местах, которые я хочу назвать своей функциональностью по критическому пути. enterCriticalPath не сохраняет экземпляр конкретного класса в качестве переменной, он просто вызывает соответствующую функцию в соответствии с вводом, здесь не исключается прыжок. –

3

Чтобы избежать необходимости решать во время выполнения, вы должны принять решение во время компиляции. Это означает использование шаблонов. И если вы так ограничены во времени, что на критический путь действительно не должно быть никаких несущественных прыжков, вы должны продолжать повышать темппатность выше и выше в своей программе, пока не достигнете точки, где дополнительный прыжок может быть сделал. Да, это теоретически может означать, что <= 90% вашего кода в шаблонах, но если вы действительно нужно избегать прыжков, это цена.

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

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

+0

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

+0

@MaxShifrin Затем создайте шаблон всей программы и выполните решение во время init. Или это не вариант по какой-то причине? – Angew

+0

Ну, templatting все не может быть и речи, поскольку весь проект состоит из нескольких миллионов строк кода :-) –

1

Ваш вопрос неясен. Поэтому все, что я могу предложить, - это расплывчатые решения.

Первый способ, который стоит рассмотреть, - это руководство vtable. Это устраняет косвенную память по сравнению с автоматическим vtable. В основном вы храните указатель на правильную реализацию метода, и вы непосредственно следуете этому указателю функции.

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

Представьте, что вы были

void foo() { 
    bool which_branch = decide(); 
    VectorWrapper wrap(decide); 
    wrap.populate(); 
    for (auto x : some_loop_domain) { 
    x.set(wrap.get()); 
    } 
} 

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

Если мы сможем переместить ветку за пределы цикла, стоимость будет выплачена один раз, а не один раз за элемент в some_loop_domain.

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

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

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

void foo() { 
    bool which_branch = decide(); 

    auto body = [&](auto which_branch) { 
    VectorWrapper wrap(which_branch); 
    wrap.populate(); 
    for (auto x : some_loop_domain) { 
     x.set(wrap.get()); 
    } 
    }; 
    if (which_branch) { 
    body(std::true_type{}); 
    } else { 
    body(std::false_type{}); 
    } 
} 

теперь в пределах body, which_branch является константой времени компиляции. Все само по себе это может привести к ветви будучи поднятым, но это может потребовать:

auto body = [&](auto which_branch) { 
    static_assert(decltype(which_branch){} || !decltype(which_branch){}); 
    VectorWrapper<decltype(which_branch)> wrap; 
    wrap.populate(); 
    for (auto x : some_loop_domain) { 
     x.set(wrap.get()); 
    } 
    }; 

где мы делаем VectorWrappertemplate на классе, когда экземпляр и читать в bool контексте возвращает true или false как constexpr.

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

Этот метод может быть очень навязчивым или вообще не работать. Он может принимать, казалось бы, заполненный веткой код и поднимать ветви.

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

+0

спасибо за продуманный ответ, но есть некоторые предположения, которые не верны, такие как: код не должен запускаться в цикле, он может быть повторенным доступом независимо от loop per say, также, как я уже говорил, нажав на шаблоны достаточно высоко, иерархия на самом деле не вариант. Я считаю, что руководство vtable концепции довольно интересно, но у него все еще есть накладные расходы на 1 прыжок, по сравнению с 2 в обычном vtable. –

+0

@max очень мало программ, где некоторый бит кода запускается много раз (достаточно того, что после указателя на современное оборудование будет иметь значение), которые не содержат цикл. Возможно, у вас такой случай, но ваш пассивный голос указывает на то, что вы, похоже, не знаете. Такие случаи встречаются редко, поэтому я сомневаюсь. Наличие конкретных конкретных случаев, когда вы обнаружили проблему производительности путем профилирования, сделало бы вашу проблему более реалистичной, а затем вы бы знали, что нет петли или нет. (И если бы вы знали, что не было петли, я бы сказал, что вы должны сомневаться в своей уверенности!) – Yakk

+0

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

Смежные вопросы