Wednesday, March 03, 2010

Faster meta-programs using gcc 4.5 and C++0x

One of the practical issues with C++ meta-programming is its speed. C++ programs that use heavy meta-programming can be notoriously slow to compile on contemporary compilers. Things are changing, however. Check the following comparison of gcc 4.5 against gcc 4.4.3.
The first graph is obtained from a program that creates a binary tree of template instantiations. The x-axis shows the number of instantiations when value of N goes from 8 to 17. I could not build up patience for gcc 4.4.3 beyond 16363 instantiations (N=13). On the other hand, gcc 4.5 does pretty good and its increase in compilation time is indeed linear as mentioned here. Here is the program that creates a binary tree of template instantiations.
template <int Depth, int A, typename B>
struct Binary 
{
  enum { value = 1 +
         Binary<depth-1, 0, Binary>::value +
         Binary<depth-1, 1, Binary>::value };
};

template<int a, typename B>
struct Binary<0, A, B> 
{
  enum { value = 1 };
};

int main(void) 
{
  static const int N = 10;
  const int instantiations = Binary<N,0,int>::value;
}
The second graph is obtained from a program that finds an intersection of two MPL vectors. Again gcc 4.5 shows linear increase in compilation time as opposed to gcc 4.4.3. Here is the intersection program.
template <class V1, class V2>
struct Intersection 
{
  typedef typename
     boost::mpl::copy_if<V1,
     boost::mpl::contains<V2, boost::mpl::placeholders::_1> >::type type;
};
While all that is already exciting, it fades in comparison to the performance of variadic templates in C++0x. The green line in the second graph shows negligible effect on performance with the increasing number of template parameters. Here is my intersection metaprogram using variadic templates.
struct null_type {};
template <typename... Arg> struct vector {};

template <typename V> struct front;
template <typename V> struct pop_front;

template <typename Head, typename... Tail>
struct front <vector <Head, Tail...> > 
{
  typedef Head type;
};

template <>
struct front <vector <> > 
{
  typedef null_type type;
};

template <typename Head, typename... Tail>
struct pop_front <vector <Head, Tail...> > 
{
  typedef vector<Tail...> type;
};

template <>
struct pop_front <vector <> > 
{
  typedef vector<> type;
};

template <typename Vector, typename T> struct push_back;

template <typename T, typename... Args>
struct push_back < vector<Args...>, T> 
{
  typedef vector<Args..., T> type;
};

template <typename Vector> struct size;

template <typename... Args>
struct size <vector <Args...> > 
{
  typedef size type;
  enum { value = sizeof...(Args) };
};

template <typename Vector, typename What> struct contains;

template <typename What, typename Head, typename... Tail>
struct contains < vector<Head, Tail...>, What> : 
  std::conditional < std::is_same<Head, What>::value,
                     std::true_type,
                     contains < vector<Tail...>, What> >::type
{
  typedef contains type;
};

template <typename What>
struct contains <vector<>, What> 
{
  typedef contains type;
  enum { value = 0 };
};

template <class V1, class V2>
struct Intersection;

template <class V1, class V2, unsigned int N>
struct Intersection_impl
{
  typedef typename front<V2>::type Head;
  typedef typename pop_front<V2>::type Tail;
  typedef typename Intersection<V1, Tail>::type I;

  typedef typename 
    std::conditional<contains<V1, Head>::value,
                     typename push_back<I, Head>::type,
                     I >::type type;
};

template <class V1, class V2>
struct Intersection_impl <V1, V2, 0> 
{
  typedef vector<> type;
};

template <class V1, class V2>
struct Intersection 
{
  typedef typename Intersection_impl<V1, V2, 
          size<V1>::value * size<V2>::value>::type type;
};


So long story short, seems like better days are ahead for C++ meta-programming!