Ładowanie
popup

C++11: Jak się pozbyć placeholderów?

2013-01-03 13:41:57

Bindując funkcje do obiektów std::function jesteśmy zmuszeni do podawania tzw. placeholderów. Choć niekiedy ich "mieszanie" może być przydatne, jednak zazwyczaj ustawia się je po kolei (_1, _2, _3, ...). Niestety nie ma prostego sposobu żeby pominąć tą czynność dla tych właśnie przypadków. Rozwiązaniem może być specjalna funkcja szablonowa, która po "wyliczeniu" ilości argumentów obiektu std::function (które są podawane jako argument szablonu) i dopasowaniu do niego odpowiedniej funkcji. Każda z tych funkcji różni się ilością placeholderów w argumentach funkcji std::bind.

template<typename S,typename I,typename FN>struct bind{ template<int N> static typename std::enable_if<N==0>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref);} template<int N> static typename std::enable_if<N==1>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1);} template<int N> static typename std::enable_if<N==2>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1,std::placeholders::_2);} ... };

Nie da się niestety napisać różnych wersji tej funkcji automatycznie, ale wątpię że ktokolwiek będzie miał potrzebę używania więcej niż kilku argumentów ;) Jak widać jest tu użyta klasa std::enable_if, (która też jest częścią nowego standardu). Jest to prosta klasa która pozwala wyeliminować funkcje których nie potrzebujemy w zależności od podanego warunku (kompilator na to pozwala, można poczytać o tym więcej tu SFINAE)

Dzięki takiej funkcji (i z pomocą C++11) można łatwo zbudować klasę eventów bardzo przypominającą tę z C# bez kombinowania z castowaniem wskaźników lub budowaniem jej od zera na template-ach. Dla uproszczenia podam prostą implementację klasy "delegate" korzystająca z tej funkcji:

template<typename... P> class delegate{ static const int argsCount=sizeof...(P); // tutaj wyliczamy ilość argumentów szablony przekazanych do klasy typedef std::function<void(P...)> tdSlot; // typ funkcji, dla uproszczenia zwraca void tdSlot m_slot; public: delegate(){} ~delegate(){} void connect(const tdSlot &f){ m_slot=f; } template<typename MP,typename TP>void connect(MP mp,TP *tp){ bind<MP,TP,tdSlot>::template get<argsCount>(mp,tp,m_slot); // po prostu wywołujemy odpowiednią funkcję w zależności od ilości argumentów } void fire(P... args){ m_slot(args...); // odpala delagata z podanymi argumentami (przekazuje je dalej) } void operator=(const tdSlot &f){return connect(f);} };

Mam w tej klasie 2 funkcje connect, pierwsza może służyć do bindowania lambdy/"zwykłej" funkcji lub przyjmowania rezultatu std::bind, druga służy do bindowania metod wewętrznych klas - pierwszy argument to wskaźnik do metody, druga to obiekt na którym wywołamy delegata. Prosty przykład użycia:

class foo{ public: void zzz(int &x){ std::cout<<"\nfoo::zzz="<<x; } void zzzff(float x,float y){ std::cout<<"\nfoo::zzzf="<<x<<", "<<y; } }; int main(int argc, char**argv){ foo obiekt; delegate<float,float> df; df.connect(&foo::zzzff,&obiekt); df.fire(5,10); df.connect(std::bind(&foo::zzzff,&obiekt,std::placeholders::_1,std::placeholders::_2)); // można też tak df.fire(10,15); int intArg=66; delegate<int&> di; di=[](int &o){std::cout<<"\ninside lambda: "<<o; ++o;}; di.fire(intArg); di.fire(intArg); std::cout<<"\nchanged value: "<<intArg; di.connect(&foo::zzz,&obiekt); di.fire(intArg); return 0; }

Tagi: Programowanie C++ Templates | Dodaj komentarz

Jak (nie) optymalizować kodu #1

2009-10-18 01:22:00

Pierwszą próbą optymalizacji bylo użycie FPU (czyli jednostka zmiennoprzecinkowa CPU) do zbudowania funkcji zastepujacych odpowiedniki z cmath. Pierwsze testy w trybie debug z domyslnymi ustawieniami wygladaly bardzo pozytywnie. Liczyłem w pętli kilka operacji, uzywajac odpowiednio funkcji z biblioteki standardowej cmath i z moich funkcji z uzyciem FPU. Czasy moich funkcji byly około 3 razy krótsze. Po włączeniu optymalizacji w kompilatorze - /fp:fast i /Oi (ten przełacznik włącza wstawianie instrinsic-ów w miejsce niektórych funkcji, w tym wiekszości tych z cmath) czasy moich funkcji byly juz tylko 2 razy krotsze. Po przełączeniu do trybu release stało sie to czego sie spodziewałem(?), cmath był szybszy... o wiele. Opcje /fp:precise i /Ox dały od kilku do kilkusetkrotne przyspieszenie. Nie pomogło też wstawienie do funkcji __forceinline. Przełączyłem więc się na podgląd instrukcji Asemblera. Hmm... kod dla funkcji z cmath nie byl wogóle generowany (nadal mowa o trybie Release). Stąd takie kosmiczne czasy ;) Rozwiązaniem tego 'problemu' jest wyłączenie optymalizacji (C++/Optimalization/Optimalization=Disabled (/Od)) lub zmiana sposobu mierzenia obliczeń. Dla przykładu w takim kodzie NIE zostanie wywolana funkcja sqrt:

float x=std::sqrt(xxx); x=sth;

kompilator zapewne ufa że brak wywołania funkcji sqrt nie spowoduje jakiegoś błedu w dalszych obliczeniach, bo za chwile w miejsce zmiennej x zostanie zapisana nowa wartość. Więc na przykład taka metoda mierzenia wydajności jest chyba bez sensu:

TIMER_START; for(int i=0;i<99999;++i){ xx3=std::sqrt(rrr); rrr+=0.56f; } TIMER_END;

Zmiana ustawień kompilatora moze spowodować że wyniki będa troche niesprawiedliwe. Pozostaje zmiana metody mierzenia. Ustawiem spowrotem opcje (C++/Optimalization/Optimalization=Full Optimization (/Ox)). Trzeba zmusic kompilator do uruchomienia funkcji z cmath. Kod:

float sth=...; TIMER_START; for(int i=0;i<99999;++i){ xx3=std::sqrt(rrr); sth+=xx3; // np. tak rrr+=0.56f; } TIMER_END;

Dodałem zmienna sth ktora będzie zainteresowana wynikiem funkcji std::sqrt. Ale to za mało nadal funkcja sqrt nie bedzie wywołana, ba - wogóle cała pętla będzie 'wyrzucona'. Działanie programu nie zmieni sie czy będzie ta pętla czy nie. Wystarczy wypisac dalej printf("%f",sth) i juz działa... Swoją drogą spryciarz z tego kompilatora :) Ostateczne pomiary sa pozytywne:

fpu_math time= 0.004849s cmath time= 0.005963s fpu_math time= 0.004900s cmath time= 0.010668s fpu_math time= 0.001266s cmath time= 0.002462s ...

Ostatecznie można powiedzieć że przyspieszenie jest, choć nie wiem czy to wszystko warte zachodu (dla satysfakcji można duzo zrobic) :P Jeszcze dodam kilka przykładów funkcji których użyłem w teście jak kogoś to interesuje (w komentarzach bardziej złożonych funkcji opisałem zawartości stosu):

float Cosf(float x) { __asm { fld x fcos } } float Sqrtf(float x) { __asm { fld x fsqrt } } // oszczedze miejsce (wiekszosc wyglada jak powyzsze) :) i podam dalej tylko te trudniejsze do napisania: float Powf(float x,float y) { __asm { fld y fld x fyl2x ;log2(x)*y fst st(1) ;log,log frndint ;int(log),log fxch st(1) fsub st(0),st(1) ;float(log),int(log) f2xm1 ;2^float(log)-1,int(log) fld1 faddp st(1),st(0) ;2^float(log),int(log) fscale ;2^int(log)*2^float(log) } } float Expf(float x) { // exp(x) == e^x __asm { ;y==e fld x fld GE_E fyl2x ;log2(x)*y fst st(1) ;log,log frndint ;int(log),log fxch st(1) fsub st(0),st(1) ;float(log),int(log) f2xm1 ;2^float(log)-1,int(log) fld1 faddp st(1),st(0) ;2^float(log),int(log) fscale ;2^int(log)*2^float(log) } } float Logf(float x) { // ln(x) (naturalny!) __asm { fld1 fld x fyl2x ;log2(x) fldl2e ;log2(e) fdivp st(1),st(0) } } float Log10f(float x) { __asm { fld1 fld x fyl2x ;log2(x) fldl2t ;log2(10) fdivp st(1),st(0) } }

Dodatkowo funkcje których nie ma w standardowej bibliotece matematycznej, a mogą sie przydać jak ktoś sie interesuje tematem :) :

void Sincosf(float x,float *oSin,float *oCos) { // sinus i cosinus od x __asm { mov eax,oSin mov edx,oCos fld x fsincos fstp dword ptr[edx] fstp dword ptr[eax] } } float Log2f(float x) { // log2(x) __asm { fld1 fld x fyl2x ; sciagnie sam st(1) } } float Logxf(float x,float y) { /* oblicza logarytm dowolnej podstawy: logx(y) sposob liczenia: logx(y)=log2(y)/log2(x) */ __asm { fld1 fld y fyl2x ;log2(y) zwija 1 ze stosu fld1 fld x fyl2x ;log2(x) zwija 1 ze stosu fdivp st(1),st(0) } }

Tagi: Programowanie C++ Optymalizacja Asembler Visual Studio | Dodaj komentarz

Dynamiczne tablice wielowymiarowe "w jednym kawałku pamięci"

2009-08-24 22:18:00

Jak wiadomo tworzenie wielu "małych" obiektów na stercie (new) może powodować znaczna fragmentacje pamięci, wiec należy tego unikać. Dla bardzo dużej ilości obiektów, potrzebującej bardzo dużej ilości operacji zwalniania i alokowania można stosować ręcznie "placement new" lub bardziej zaawansowane "upraszczacze" jak np. "FreeList". a co ze zwykła tablica? Standardowa funkcja alokująca tablice 2-wymiarowa może wyglądać tak:

template<typename T>T **alloc2d(int width,int height){ T **mem=new T*[width]; for(int x=0;x<width;++x) mem[x]=new T[height]; return mem; }

jak widać tablice mamy w pamięci 'w kawałkach' o rozmiarze odpowiadającym zmiennej height. Jak to zmienić? Trzeba zastosować "placement new". Nowa wersja wygląda tak (typ int8 odpowiada 1 bajtowi):

template<typename T>T **alloc2d(int iX,int iY){ // alokuje wszystko w jednym bloku (dane i wskazniki na dane) int8 *raw=new int8[iX*iY*sizeof(T)+iX*sizeof(T*)]; // przydziela pamiec z bloku dla wskaznikow // wskazniki sa przed danymi-stad offset o rozmiarze wskaznikow ponizej T **ret=new(raw) T*[iX]; // przydziela pamiec z bloku dla danych (znajduja sie za wskaznikami) T *data=new(&raw[iX*sizeof(T*)]) T[iX*iY]; // przypisuje do wskaznikow adresy danych for(int i=0;i<iX;++i,data+=iY) ret[i]=data; return ret; }

Co nam to daje (oprócz umiejscowienia w jednym kawałku pamięci)?

Jak sie dostać bezpośrednio do danych takiej tablicy?. Można sie posłużyć taka prosta funkcja:

template<typename T>T *Get2d(T **iA,int iX){ // mijamy wskazniki return (T*)(((int8*)iA)+iX*sizeof(T*)); }

Na koniec trzeba usunac taka tablice:

template<typename T>void Free2d(T **iA){ int8 *ptr=(int8*)iA; delete []ptr; iA=NULL; }

jak można sie domyślić można sobie łatwo zrobić tez wersje 3,4,... wymiarowa.

Tagi: Programowanie C++ | Dodaj komentarz