Ordenación rápida en el momento de la compilación utilizando plantillas variadic C++ 11
metaprogramming quicksort (2)
Acabo de implementar el algoritmo de clasificación rápida mediante el uso de plantillas variadic C ++ 11 para evaluarlo en el momento de la compilación. Sin embargo, encuentro un problema de rendimiento cuando el conjunto de datos es demasiado grande.
#include <iostream>
using namespace std;
template<int... vs>
struct Seq
{};
template<int v1, int...vs>
struct Seq<v1, vs...>{
};
template<typename newT, typename srcT>
struct PushFront{
};
template<int vadded, int...vs>
struct PushFront<Seq<vadded>, Seq<vs...>>{
typedef Seq<vadded, vs...> ResultType;
};
template<typename T>
struct PopFront{
};
template<int v1, int...vs>
struct PopFront<Seq<v1, vs...>>{
typedef Seq<vs...> RemaindType;
typedef Seq<v1> ResultType;
};
template<typename T1, typename T2>
struct CatSeq{};
template<int...v, int...us>
struct CatSeq<Seq<v...>, Seq<us...>>{
typedef Seq< v..., us... > ResultType;
};
template<bool c, typename NewT, typename TrueClsT, typename FalseClsT>
struct Classify{
};
template<typename NewT, typename TrueClsT, typename FalseClsT>
struct Classify<true, NewT, TrueClsT, FalseClsT>{
typedef typename PushFront<NewT, TrueClsT>::ResultType NewTrueClsT;
typedef FalseClsT NewFalseClsT;
};
template<typename NewT, typename TrueClsT, typename FalseClsT>
struct Classify<false, NewT, TrueClsT, FalseClsT>{
typedef TrueClsT NewTrueClsT;
typedef typename PushFront<NewT, FalseClsT>::ResultType NewFalseClsT;
};
template<typename T1, typename T2>
struct Compare{};
template<int v1, int v2>
struct Compare<Seq<v1>, Seq<v2>>{
static const bool result=(v1>=v2);
};
template<typename AnchorT, typename SeqT, typename GESet, typename LSet>
struct PartitionImpl{};
template<typename GESet, typename LSet, int anchorv, int v1>
struct PartitionImpl<Seq<anchorv>, Seq<v1>, GESet, LSet>{
static const bool isge=Compare<typename PopFront<Seq<v1>>::ResultType, Seq<anchorv>>::result;
typedef typename Classify<isge, Seq<v1>, GESet, LSet>::NewTrueClsT RstGESet;
typedef typename Classify<isge, Seq<v1>, GESet, LSet>::NewFalseClsT RstLSet;
};
template<typename GESet, typename LSet, int anchorv, int v1, int...vs>
struct PartitionImpl<Seq<anchorv>, Seq<v1, vs...>, GESet, LSet>{
static const bool isge=Compare<typename PopFront<Seq<v1, vs...>>::ResultType, Seq<anchorv>>::result;
typedef typename Classify<isge, Seq<v1>, GESet, LSet>::NewTrueClsT TmpRstGESet;
typedef typename Classify<isge, Seq<v1>, GESet, LSet>::NewFalseClsT TmpRstLSet;
typedef typename PartitionImpl<Seq<anchorv>, Seq<vs...>, TmpRstGESet, TmpRstLSet>::RstGESet RstGESet;
typedef typename PartitionImpl<Seq<anchorv>, Seq<vs...>, TmpRstGESet, TmpRstLSet>::RstLSet RstLSet;
};
template<typename T>
struct Partition{
};
template<int v1, int v2, int...vs>
struct Partition<Seq<v1, v2, vs...>>{
typedef Seq<v1> AnchorType;
typedef Seq<> GESet;
typedef Seq<> LSet;
typedef typename PartitionImpl<AnchorType, Seq<v1, v2, vs...>, GESet, LSet>::RstGESet RstGESet;
typedef typename PartitionImpl<AnchorType, Seq<v1, v2, vs...>, GESet, LSet>::RstLSet RstLSet;
};
//why introduce this? refer to Sort
template<typename SrcT, typename GESet, typename LSet, template<typename > class SortOp>
struct SortSub{
typedef typename SortOp<GESet>::ResultType TmpGESet2;
typedef typename SortOp<LSet>::ResultType TmpLSet2;
};
template<typename SrcT, typename LSet, template<typename> class SortOp>
struct SortSub<SrcT, SrcT, LSet, SortOp>{
typedef SrcT TmpGESet2;
typedef typename SortOp<LSet>::ResultType TmpLSet2;
};
template<typename SrcT, typename GESet, template<typename> class SortOp>
struct SortSub<SrcT, GESet, SrcT, SortOp>{
typedef typename SortOp<GESet>::ResultType TmpGESet2;
typedef SrcT TmpLSet2;
};
template<typename T>
struct Sort;
template<>
struct Sort<Seq<>>{
typedef Seq<> ResultType;
};
template<int v>
struct Sort< Seq<v> >{
typedef Seq<v> ResultType;
};
template<int v1, int...vs>
struct Sort< Seq<v1, vs...> >{
typedef Seq<v1, vs...> SrcType;
typedef typename Partition< Seq<v1, vs...> >::RstGESet TmpGESet;
typedef typename Partition< Seq<v1, vs...> >::RstLSet TmpLSet;
//to by pass the case SrcType <==> TmpGESet or SrcType <==> TmpLSet
typedef typename SortSub<SrcType, TmpGESet, TmpLSet, Sort>::TmpGESet2 TmpGESet2;
typedef typename SortSub<SrcType, TmpGESet, TmpLSet, Sort>::TmpLSet2 TmpLSet2;
typedef typename CatSeq<TmpGESet2, TmpLSet2>::ResultType ResultType;
};
void dumpSeqTypeImpl(Seq<> ){
}
template<int v1>
void dumpSeqTypeImpl(Seq<v1> ){
cout<<v1<<" ";
}
template<int v1, int...vs>
void dumpSeqTypeImpl(Seq<v1, vs...> ){
cout<<v1<<" ";
dumpSeqTypeImpl( Seq<vs...>() );
}
template<int...vs>
void dumpSeqType(Seq<vs...> ){
cout<<"Seq type < ";
dumpSeqTypeImpl( Seq<vs...>() );
cout<<" >"<<endl;
}
//test data
#include "qsort_input.txt"
int main(){
//Seq<> s0;// aggregate ‘Seq<> s0’ has incomplete type and cannot be defined
Seq<1> s1;
Seq<1, 2> s2;
typedef Seq<5, 5, 5> TestType_SAME;
TestType_SAME same;
dumpSeqType( same );
typename Partition< TestType_SAME >::RstGESet _ts1;
typename Partition< TestType_SAME >::RstLSet _ts2;
dumpSeqType( _ts1 );
dumpSeqType( _ts2 );
#if 1
typedef Seq<4, 7, 3, 9, 1, 2, 5, 5, 19, 5> TestType;
TestType s3;
dumpSeqType( s3 );
typename Partition< TestType >::RstGESet ts1;
typename Partition< TestType >::RstLSet ts2;
dumpSeqType( ts1 );
dumpSeqType( ts2 );
typename Sort<TestType>::ResultType so1;
dumpSeqType( so1 );
#endif
#if 1
typedef Seq<TEST_DATA_100> TAdvanceType;
typename Sort<TAdvanceType>::ResultType soadvance;
dumpSeqType(soadvance);
#endif
return 0;
}
Cuando el conjunto de datos es TEST_DATA_100, se necesitan 1.7s para compilar.
Cuando el conjunto de datos es TEST_DATA_1000, el compilador parece detenerse ...
Estoy usando gcc 4.6.0.
¿También has mirado su consumo de memoria? Tenga en cuenta que la ordenación rápida en sí misma es peor que la lineal, con un tiempo de ejecución de caso bastante peor. Esto se multiplica con el comportamiento de tiempo de ejecución peor que lineal de ciertos pasos de compilación y creación de instancias de plantilla (a veces son exponenciales). Tal vez debería graficar su tiempo de compilación para varios conjuntos de datos para observar la clase de complejidad real de su código. Normalmente, la metaprogramación de plantillas con conjuntos de datos tan grandes no es factible.
Edit: por curiosidad probé el código y descubrí que hasta ~ 500 sigue aproximadamente la fórmula pow (N * log (N), 1.47) * 0.0004 + 0.6, pero luego comienza a ser increíblemente mucho más lento, con 155 segundos para 700 artículos. También alrededor de eso, comienza a comer mucho ram (3GiB por 600), lo que me lleva a la conclusión de que para 1000 elementos necesitará más ram del que la mayoría de la gente y tardará horas en compilar.
Además, tenga en cuenta que el código no funciona cuando no todos los elementos son únicos.
Está utilizando metafunciones recursivas para construir su quicksort. ¿Qué esperabas que sucediera exactamente cuando intentaste empujar 1000 instancias recursivas en el compilador?
El hecho de que una función pueda tomar teóricamente números arbitrarios de argumentos no significa que el compilador realmente pueda manejar números arbitrarios de argumentos. Los compiladores tienen límites.
Además, ¿para qué sirve la clasificación en tiempo de compilación? Podría hacerlo fuera de línea y copiar los datos en el archivo .cpp. O simplemente ejecute std::sort
una vez cuando se inicie el programa.