Almacenamiento de objetos con plantilla de C ++ como del mismo tipo

I have a class that is a core component of a performance sensitive code path, so I am trying to optimize it as much as possible. The class used to be:

class Widget
    Widget(int n) : N(n) {}
    .... member functions that use the constant value N ....
    const int N;                // just initialized, will never change

The arguments to the constructor are known at compile time, so I have changed this class to a template, so that N can be compiled into the functions:

template<int N>
class Widget
   .... member functions that use N ....

I have another class with a method:

Widget & GetWidget(int index);

However, after templating Widget, each widget has a different type so I cannot define the function like this anymore. I considered different inheritance options, but I'm not sure that the performance gain from the template would outweigh the cost of inherited function invocations.

SO, my question is this:

I am pretty sure I want the best of both worlds (compile-time / run-time), and it may not be possible. But, is there a way to gain the performance of knowing N at compile time, but still being able to return Widgets as the same type?


preguntado el 08 de enero de 11 a las 15:01

5 Respuestas

The issue here is that if you store the widgets as the same type, then the code that retrieves the widgets from that store (by calling GetWidget) no se know N at compile time[*]. The code that calls the constructor knows N, but the code that uses the object has to cope with multiple possibilities.

Since the performance hit (if any) is likely to be in the code that uses the widgets, rather than the code that creates them, you can't avoid doing algo in the critical code that depends on runtime information.

It pueden be that a virtual call to a function implemented in your class template, is faster than a non-virtual call to a function that uses N without knowing the value:

class Widget {
    virtual ~Widget() {}
    virtual void function() = 0;

template <int N>
class WidgetImpl : public Widget {
    virtual void function() { use N; }

The optimizer can probably do its best job when N is known, since it can optimally unroll loops, transform arithmetic, and so on. But with the virtual call you're looking at one big disadvantage to start with, which is that none of the calls can be inlined (and I would guess a virtual call is less likely to be predicted than a non-virtual call when not inlined). The gain from inlining with unknown N could be more than the gain of knowing N, or it could be less. Try them both and see.

For a more far-fetched effort, if there are a reasonably small number of common cases you might even see an improvement by implementing your critical widget function as something like:

switch(n) {
    case 1: /* do something using 1 */; break;
    case 2: /* do the same thing using 2 */; break;
    default: /* do the same thing using n */; break;

"do something" for all cases but the default could be a call to a function templated on the constant, then the default is the same code with a function parameter instead of a template parameter. Or it could all be calls to the same function (with a function parameter), but relying on the compiler to inline the call before optimization in the cases where the parameter is constant, for the same result as if it was templated.

Not massively maintainable, and it's usually a bad idea to second-guess the optimizer like this, but maybe you know what the common cases are, and the compiler doesn't.

[*] If the calling code does know the value of N at compile time, then you could replace GetWidget with a function template like this:

template <int N>
Widget<N> &getWidget(int index) {
    return static_cast<Widget<N> &>(whatever you have already);

But I assume the caller doesn't know, because if it did then you probably wouldn't be asking...

Respondido el 09 de enero de 11 a las 22:01

+1 for "try both and see". It depends what the code is actually doing with N. One case where having multiple functions for specific N does help performance is the implementation of libfftw - it can store values of cos y sin of (2*M_PI/n) as constants, for one thing. - aschepler

You need to declare a non-templated type from which the templated type inherits, and then store the widgets as pointers to the non-templated base class. That is the only (type-safe) way to accomplish what you are looking for.

However, it is probably cleaner to keep the non-templated version. Have you profiled your code to see that the loops on the runtime-configured version are actually a bottleneck?

Respondido el 08 de enero de 11 a las 18:01

I have not profiled this specifically, but we have been looking at this code for some time and every minor change we make actually does impact performance (it is used that frequently, i.e. 300,000 times / sec). - JaredC

@JaredC then you'll have to use the base class method. I encountered a similar scenario once - 3D rendering and I needed a list of faces, some of which had 3 vertices and some 4. Template class with a base class having a pure virtual render method allowed me to decrease heap allocations & use loop unrolling, resulting in notable speedup. - Michael Ekstrand

I guess the following is not an option?

template <int N>
Widget<N> & GetWidget();

Anyway, as soon as you’re managing several widget types together you cannot make them templated anymore since you can’t store objects of different type in one container.

The non-templated base class proposed by Michael is a solution but since it will incur virtual function call costs I’m guessing that making the class templated hasn’t got any benefits.

Respondido el 08 de enero de 11 a las 19:01

If your types are finite and known, podrías usar un boost::variant as an argument to your constructor.

The variant class template is a safe, generic, stack-based discriminated union container, offering a simple solution for manipulating an object from a heterogeneous set of types in a uniform manner. Whereas standard containers such as std::vector may be thought of as "multi-value, single type," variant is "multi-type, single value."

here is some pseudo code

boost::variant< int, double, std::string > variant;
const variant foo( 1 );
const variant bar( 3.14 );
const variant baz( "hello world" );

const Widget foo_widget( foo );
const Widget bar_widget( bar );
const Widget baz_widget( baz );

Alternativamente, puede usar un boost::any con mas flexibilidad.

Respondido el 08 de enero de 11 a las 19:01

Widget isn't parameterized on a type, though, just on an int. - Steve Jessop

this would have been almost the perfect solution if N were a type. Bummer. Would something like boost::mpl::int_<5> allow me to parameterize on a type and get the same effect? - JaredC

@JaredC I am not familiar with boost::mpl, you might want to ask another question and tag it as such. - Sam Miller

You could write a templated GetWidget function. That would require you to know the type when you call GetWidget:

w = GetWidget<Box>(index);

Respondido el 08 de enero de 11 a las 19:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.