[C++, STL] optymalizacja algorytmow, duzy zbior danych

0

Skleciłem taki oto kod:

std::string BaseParser::BuildName(unsigned int& i)
{
	unsigned int start = i;
	while (isalnum(parseMem[i++]));
	i--;
	return std::string(&parseMem[start], i-start);
}
[...]
std::string Name = BuildName(i);

Moje pytanie: Czy tak w ogóle powinno się pisać jeżeli zależy mi na efektywności i szybkości wykonywania jak i na poprawności danych? Jak najlepiej stosować takie funkcje?

0

nie widze tutaj nic do czego moznaby sie przyczepic. wyszukiwanie liniowe musi byc. zwracanie przez kopie (dobrze ze tail'owe) tez - o ile potrzebujesz wartosc zwracana miec modyfikowalna/niezalezna.. dziwnie wyglada int& w param, ale za pewne to dlatego, ze 'nowa' wartosc i musisz przekazac na zewnatrz z powrotem, wiec tak najprosciej i tego sie nie ruszy..
co bys wiecej chcial osiagnac?

o co pytasz pod slowami 'stosowanie takich funkcji' ?

0

Cóż, "early optimization is the root of all evil"... Chodziło mi o to, że mam takich funkcji jak ta dość dużo, z tym że budują nie tylko stringi, ale też całkiem potężne obiekty.
Całe moje pytanie sprowadza się do tego, jak używając podobnych funkcji, które zwracają właściwie kopie dużych obiektów, możliwie jaknajbardziej uniknąć ich kopiowania, jednocześnie zachowując sobie zalety nie stosowania wskaźników. A może po prostu stosować wskaźniki ze wspomaganiem, tzn. inteligentne wskaźniki?

0

Mnie ciekawi po co robisz tak:

        while (isalnum(parseMem[i++]));
        i--;

nie lepiej:

        while (isalnum(parseMem[i+1]));

// sorry nie zauważyłem [wstyd]

0

bo

while (isalnum(parseMem[i+1]));

nie działa?

wyjaśnienie:
załóżmy string "Dupa 1 23 456", oto co zwróci funkcja używając:

while (isalnum(parseMem[i++]));
i--;

"Dupa"

while (isalnum(parseMem[i+1]));

(zwis, nie modyfikuje i)

while (isalnum(parseMem[i+1]))i++;

"Dup"

while (isalnum(parseMem[++i]));

"Dupa"

0_o o.o o_O HA! dając sampla zrobiłem coś o jeszcze mniejszej ilości instrukcji... Wii! jedna instrukcja w plecy ;)

no more samples...

0

while (isalnum(parseMem[++i]));

chcialem Ci to podrzucic, ale nie jest to rownowazne. w momencie gdzy 'i' pokazuje od razu na sarcie na nie-alnum, to oryginalna zwraca pusty string, a z ++i zwraca jednoelementowy z nie-alnumem. pierwotna wersja byla lepsza, wiec nie podrzucilem ostatecznie

możliwie jaknajbardziej uniknąć ich kopiowania

kopiowania przy zwracaniu przez wartosc nie unikniesz. ostatecznie zwracanie wskaznika tez jest kopiowaniem -- wskaznika :)
zwroc natomiast uwage, ze w momencie kiedy zwracasz cos przez NOWA wartosc (return typ(params); ) i w wywolujacym natychmiast to odbierasz do NOWEJ zmiennej ( typ x(func()); ew typ x = func(); ) to kompilator powinien zoptymalizowac wywolanie funkcji tak, zeby obiekt nie byl tworzony w return i kopiowany cctorem, ale zeby byl Od razu tworzony w 'docelowym' miejscu

0

Funkcja na starcie ma zapewnione, że i pokazuje na jakiegoś alnuma, specyfikacja wymaga, tak więc pierwotna robi tak jak trzeba ale jest bardziej "skrupulatna" podczas gdy nie jest to wymagane...

Co do kopiowania to właśnie tak robię, czyli konstruuje na podstawie funkcji, tak aby kompilator mógł zoptymalizować.

--
Miałem napisać o nowym problemie powodującym dość dziwne skutki, ale okazało się że źle napisałem konstruktor kopiujący - zapamiętać na przyszłość - pobierać w konstruktorze kopiującym stałą referencje a nie samą referencje.

Z innej beczki... Jak lepiej napisać takiego potworka?

TArea( const char* strArea, int ilin ):
	x1(
		strArea + strcspn(strArea, "0123456789.%"),
		strspn( strArea+strcspn(strArea, "0123456789.%"), "0123456789.%" ) ),
	y1(
		strchr(strArea, ',') + strcspn(strchr(strArea, ','), "0123456789.%"),
		strspn( strchr(strArea, ',')+strcspn(strchr(strArea, ','), "0123456789.%"), "0123456789.%" ) ),
	x2(
		strchr(strchr(strArea, ',')+1, ',') + strcspn(strchr(strchr(strArea, ',')+1, ','), "0123456789.%"),
		strspn(strchr(strchr(strArea, ',')+1, ',')+strcspn(strchr(strchr(strArea, ',')+1, ','),"0123456789.%"),"0123456789.%" )
		),
	y2(
		strchr(strchr(strchr(strArea, ',')+1, ',') +1, ',') + strcspn(strchr(strchr(strchr(strArea, ',')+1, ',') +1, ','), "0123456789.%"),
		strspn( strchr(strchr(strchr(strArea, ',')+1, ',') +1, ',')+strcspn(strchr(strchr(strchr(strArea, ',')+1, ',') +1, ','), "0123456789.%"), "0123456789.%" )
		)
	{};

Oczywiście uwzględniając że technicznie rzecz biorąc xy i ygreki powinny być zainicjalizowane w liście inicjalizacji?

//ps. czasami wydaja mi się że standardy które mi są narzucone powodują niezwykłe potworki techniczne...</quote>

0

yhm :O
to zależy, co znaczy "lepiej" - w sam zapis rozumiem możemy ingerować, bo cała konwencja już ustalona: lista inicjalizacyjna, argumenty konstruktorów xów i ygreków? Jak tylko zapis, to ja osobiście bym nie wytrzymał:

#define _(t) strchr(t,',')
#define s(t)  strspn(t, "0123456789.%")
#define cs(t) strcspn(t, "0123456789.%")

TArea( const char* strArea, int ilin ):
    x1(
            strArea + cs(strArea),
            s(strArea + cs(strArea) ) ),
    y1(
            _(strArea) + cs(_(strArea)),
            s(_(strArea) + cs(_(strArea))) ),
    x2(
            _(_(strArea)+1) + cs(_(_(strArea)+1)),
            s(_(_(strArea)+1) + cs(_(_(strArea)+1)))
            ),
    y2(
            _(_(_(strArea)+1)+1) + cs(_(_(_(strArea)+1)+1)),
            s(_(_(_(strArea)+1)+1) + cs(_(_(_(strArea)+1) +1)) )
            )
    {};

// dopisane (bo zapomniałem posprzątać):
#undef _
#undef s
#undef cs

przyznam szczerze, przed zastosowaniem define, to nie widziałem, że to takie ładne. zdaje się, można to by było uogólnić jakimś rekurencyjnym templatem (bo rozumiem, że zwykła funkcja rekurencyjna odpada z miejsca).

dopisane:
dobra, niech będzie widać ewolucję myśli mojej. po pierwsze, po dopisaniu szablonu nabiera to ludzkiego wyglądu:

template<int N>
inline const char* nchar(const char* text, int ch=',') {
    return nchar<N-1>( strchr(text, ch) +1, ch );
    }

template<>
inline const char* nchar<1>(const char* text, int ch) {
    return strchr(text, ch);
    }

template<>
inline const char* nchar<0>(const char* text, int ch) {
    return text;
    }

//----------------------------

TArea( const char* strArea, int ilin ):
    x1(
               nchar<0>(strArea) + cs(nchar<0>(strArea)),
            s( nchar<0>(strArea) + cs(nchar<0>(strArea)) )
            ),
    y1(
               nchar<1>(strArea) + cs(nchar<1>(strArea)),
            s( nchar<1>(strArea) + cs(nchar<1>(strArea)) )
            ),
    x2(
               nchar<2>(strArea) + cs(nchar<2>(strArea)),
            s( nchar<2>(strArea) + cs(nchar<2>(strArea)) )
            ),
    y2(
               nchar<3>(strArea) + cs(nchar<3>(strArea)),
            s( nchar<3>(strArea) + cs(nchar<3>(strArea)) )
            )
    {};

po drugie, teraz to już jak w pysk strzelił widać, że mamy z X razy wywoływane strchr na tych samych danych w kółko. I nie ma bata, zdrowy rozsądek mówi, że listę inicjalizacyjną olać i zrobić to w konstruktorze właściwym.


template<int N>
inline const char* skipsep(const char* text, int sep=',') { return strchr(text+1, sep); }

template<>
inline const char* skipsep<1>(const char* text, int sep) { return strchr(text, sep); }

template<>
inline const char* skipsep<0>(const char* text, int sep) { return text; }



TArea( const char* strArea, int ilin) {
    const char* i = strArea;
    size_t n;
    static const char* nums = "0123456789.%";

    n = strcspn( i=skipsep<0>(i), nums);
    x1.first = i+n;
    x1.second = strspn(i+n, nums);

    n = strcspn( i=skipsep<1>(i), nums);
    y1.first = i+n;
    y1.second = strspn(i+n, nums);

    n = strcspn( i=skipsep<2>(i), nums);
    x2.first = i+n;
    x2.second = strspn(i+n, nums);

    n = strcspn( i=skipsep<3>(i), nums);
    y2.first = i+n;
    y2.second = strspn(i+n, nums);
    }

w zasadzie tylko dla estetyki dodałem funkcję skipsep (nie jest ona w żadnym wypadku rekurencyjna). Na podstawie numeru 0,1,2,3,4,5 sama decyduje jak ten n-ty argument ominąć. Dla zerowego robi przypisanie, dla 1-szego strchr(text), dla nastęnych strchr(text+1). Dzięki temu te 4 bloki wyglądają niemal identycznie i (pozornie) proszą o pętlę. Ale wygląd to #@$ pies. Najważniejsze, że uniknęliśmy X-krotnych wywołań takiego samego strchr, strspn itp. Tylko konstruktor domyślny dla ygreków i xów zrób dobry (najlepiej pusty, nie będzie nawet zerował składowych - zero narzutu. Jak domyślny ci potrzebny do czego innego, to zrób taki:

struct MyX {
   enum fastinit_type { fastinit };
   MyX(fastinit_type) { }
   // ... 
   }

i konstruktor "nasz" uzupełnij:

TArea( const char* strArea, int ilin) : x1(MyX::fastinit) /*itd...*/ {
0

Po dłuuugim namyśle doszedłem do wniosku, że obciążanie konstruktorów jakąś logiką interpretacji stringów; uzależnienie od poprawności danych itp to głupota, jeśli kiedykolwiek trzeba będzie coś zmienić. Wiadomo że styl zapisu liczb się nie zmieni (przynajmniej mam nadzieję), więc konstruktor ma tylko zinterpretować poprawnie to co dostał i się cieszyć, a to funkcja budująca ma się martwić o poprawność danych. Daje to i większą swobodę dla wejścia i większą kontrole nad tym co się dzieje z danymi... no i oczywiście odpada z konstruktorów niepotrzebne męczenie się.

Taka "mała" zmiana a jaka różnica...

Dzięki Ranides, nie pomyślałem o template, a jeśli już to zbędnie pomyślałbym o rekurencyjnym...

0

nie zaglebialem sie w kod, ale tak na oko - skoro tok wyszukiwania pozycji w stringu leci pokolei - najpierw x1, potem y1, potem ... to mozna to polamac w liscie inicjalizacyjnej, bez zagniezdzania/rekursji

jezeli zmienne w obiekcie sa w definicji klasy ulozone:

class X
{
     char* x1;
     char* y1;
     char* x2;
     char* y1;
}

to nie wazne jaka kolejnosc na liscie init, inicjalizowac beda sie w tej kolejnosci x1y1x2y2, dokladnie tak jak kolejnosc w definicji klasy.
stad, po okresleniu x1, mozna y1 inicjalizowac w opariu o niego, nastepnie x2 w oparciu o wiedze x1y1, etc..

oczywiscie bedzie to kruche jak jasna cholera i za nic nie polecam, ale napomykam tylko ze -mozna-

a polecam mimo wszystko podejscie konstruktora z logika.. tutaj w momencie konstrukcji obiekt dysponuje juz wszystkimi potrzebnymi mu danymi -- czemu mialby nie sprawdzic natychmiast czy te dane w ogole maja sens? kontrola danych tak wczesnie jak tylko to mozliwe zazwyczaj ma same plusy -- z jednym waznym wyjatkiem, jesli te dane moga sie okazac totalnie niepotrzebne za moment ;] no, ale to juz tak naprawde jak komu wygodniej

0

meh, true, ale zazwyczaj konstruktor ma mieć już sprawdzone dane, a jak dane są "z księżyca" to nic się nie stanie, bo funkcje wykorzystujące dane muszą je sprawdzać...

Teraz znowu mam dziwnego potworka, ale to już ja nic nie kumam...
jest taki sobie ciąg wywołań(oczywiście uproszczony, ale oddaje całość problemu):

class TCmd
  {public:
    TCmd(const char op, const int val): Op_(op), Val_(val){};
    char getOp(){ return Op; };
  }

class TRet
  {public:
    void addCmd( TCmd const & cmd )
      { char c (cmd.getOp()); c+=2;
      }
  }

TRet oRet("2", "12", "74", "99");
oRet.addCmd( TCmd( 'a', 7) );

w takiej postaci krzaczy się z błędem error: no matching function for call to ‘TCmd::getOp() const’</code> oczywiście dając że addCmd to near match. Zmieniając zgodnie z zaleceniem getOp na <code class="cpp"> char getOp()const{return Op_;} niby hula, ale czemu tak jest? Gdy zmienię addCmd tak żeby było bez const to getOp przejdzie ale nie przejdzie przekazanie przez copy constructor... Pewnie znowu będzie tak łopatologiczny błąd jak ostatnio, gdy nie rozumiałem dziedziczenia -- z tym że teraz nie rozumiem modyfikatorów dostępu.

0
  1. zapomniales '=' po char c
  2. konstruktor kopiujacy z definicji musi miec argument T const &
  3. obiekt jest const, to mozna na nim wywolywac jedynie metody oznaczone jako const, czyli nie-modyfikujace go
  4. czyli: tego nie przeskoczysz*). getOp MUSI byc const zebys mogl jej uzyc. zmien char getOp(){..} na chat getOp() const {...} i juz

*) oczywiscie mozna przeskoczyc.. istnieje chocby const_cast, ale nie o to tutaj chodzi:)

0

a... ha. Teeraz rozumiem. Kurde, niby to oczywiste a jak ktoś wyjaśni, to od razu lepiej...

Dzięki Wielkie [browar]


Edit-Bump(doklejone)

Ponieważ temat jest "Jak to lepiej napisać" więc pytanie moje natury optymalizacyjnej:

Załóżmy że mamy 2 wymiarową tablicę intów, cholernie dużą(jeśli to ważne)(powiedzmy ~4MB) i mamy listę modyfikatorów(może być ich jeden, może być 100) pojedynczych wartości tej tablicy(znaczy modyfikują każdy element). Modyfikatory te muszą być wykonane po kolei( tzn mod1(val); mod2(val) nie da tych samych wyników co mod2(val); mod1(val)). Teraz, co jest szybsze (dostęp do pamięci, cache miss itp):

for(int i=0; i<sx; i++)
  for(int j=0; j<sx; j++)
    for(int k=0; k<modnum;k++)
      (*mods[k])('a', tab[i][j], 10);

czy

for(int k=0; k<modnum;k++)
  for(int i=0; i<sx; i++)
    for(int j=0; j<sx; j++)
      (*mods[k])('a', tab[i][j], 10);

czy może są jakieś jeszcze szybsze możliwości?

0

IMO wersja druga jest ładniejsza, bo się operator / akcja / kod funkcji nie zmienia non-stop, w najbardziej zagnieżdżonym punkcie - w iteracji właściwej. Tylko dane. Funkcja się zmienia w tej najbardziej zewnętrznej, czyli rzadko.

W pierwszym przypadku i dane, i akcja, w każdej iteracji są inne. Fu.

Ale to eksperymentalnie chyba sprawdzać...

0

Hmm... Myślałem że będzie odwrotnie, tzn wersja 2 będzie gorsza, a tu zonk, bo wersja 2 jest szybsza o całą sekundę przy już tylko 1MB danych i 1k funkcji (robiących to samo, żeby nie miało to wpływu na test)...

0

jesli kod wykonywanego kawalka programu (->funkcji/modyfikatora) rzadziej sie zmienia, to 2jka musiala byc szybsza.. dane jak dane, ale przeciez procek musi zaladowac wykonywany kod do cache'a, wiec kazda zmiana *-na funkcje wykonywana to potencjalny cachemiss. podejrzewam ze Twoje dane jest latwiej zcacheowac poniewaz pojedynczy element jest duzo mniejszy niz kod funkcji do wywolania.. ale podkreslam ze nie jestem tu ekspertem:)

0

wow, czyli trafiłem - coś jeszcze ze mnie może będzie ;)

0

ano ;)

Po wyciągnięciu ciężkich dział (gprof, cachegrind, callgrind itp) jak się okazało w obu przypdakach mimo że jest 1073741824 wywołań, to jednak cache miss jest relatywnie mało -- 66835 dla wersji 2 i 309042 dla wersji 1 (pojedyńcze testy, nie chciało mi się więcej zapuszczać, powinno się wyciągać raczej średnie) -- czyli widać jak na dłoni że przy ciągłych zmianach operacji jest tak 4x-5x więcej cache miss... nieźle... czyli dla cholernie dużych danych nieźle by to spowolniło...

btw, czytałem że takie pętle w kontenerach po iteratorach można znacznie przyśpieszyć używając algorytmu for_each i funktorów... tylko że ja kompletnie nie pojmuje całego tego działania i nie wiem jak uogólnić na mój przypadek... dla pewności, dam kod pętli:

void applyCmds( double& val, double bottom, double top )
{
	Worker<double> w(top, bottom, cCond, dCondVal);
	for( std::list< TCmd >::iterator i(lCommands.begin()); i!=lCommands.end(); ++i)
		{
			w.apply( i->getOp(), val, i->getdVal());
		}
}

--

Miałem fantazje to sprawdziłem co daje wywalenie jednego kontenera gdy sprawa jest (relatywnie) prosta... Pamiętacie mój post z szablonami, dzedziczeniem itd? http://4programmers.net/Forum/viewtopic.php?id=137015 ? no cóż... wywalenie map i wstawienie zamiast tego operator[] dało... no cóż, wyjście z profilera samo mówi za siebie:

przed:

  %   cumulative   self              self     total           
time   seconds   seconds    calls  ms/call  ms/call  name    
 25.00      0.01     0.01   229392     0.00     0.00  std::_Rb_tree<>::lower_bound(char const&)
 25.00      0.02     0.01   133812     0.00     0.00  std::map<>::key_comp() const
 12.50      0.03     0.01   618084     0.00     0.00  std::_Rb_tree<>::_S_right(std::_Rb_tree_node_base*)
 12.50      0.03     0.01   420552     0.00     0.00  std::allocator<> const&)
 12.50      0.04     0.01    19116     0.00     0.00  std::map<>::map()
 12.50      0.04     0.01    19116     0.00     0.00  std::_Rb_tree<>::~_Rb_tree_impl()

po:

  %   cumulative   self              self     total           
time   seconds   seconds    calls  ms/call  ms/call  name    
100.01      0.01     0.01    25494     0.00     0.00  std::_List_iterator<>::_List_iterator(std::_List_node_base*)
  0.00      0.01     0.00    57360     0.00     0.00  std::_List_iterator<>::_List_iterator(std::_List_node_base*)
  0.00      0.01     0.00    57348     0.00     0.00  std::_List_iterator<>::operator->() const
  0.00      0.01     0.00    38234     0.00     0.00  std::list<>::end()
  0.00      0.01     0.00    38232     0.00     0.00  TOption::inName(char)
  0.00      0.01     0.00    38232     0.00     0.00  std::_List_iterator<>::operator!=(std::_List_iterator<> const&) const

(podane pierwsze 6 zajmujące najwięcej czasu)
hmm... no... Cóż... na tych samych danych zcięcie duuużej ilości zbędnych funkcji i przyśpieszenie niesamowite( gdzieś tak 10x - nie robiłem time, więc nie wiem), więc co tu robić... teraz chyba tylko pozostają funktory, bo w założeniu zetną duuużo czasu z pętli... jaka szkoda że nie umiem funktorów ;(

0

Funktor jest najzwyklejszą klasa z przeciążonym operatorem() lub jakaś funkcja. W Twoim wypadku będzie to jednak klasa, ponieważ operacje dla każdego elementu są zależne poprzez wykorzystanie tego samego egzemplarza Worker<double> i double val. Idąc tym tropem proponuję coś na wzór:

class WorkerApply
{
private:
	Worker<double>& m_Worker;
	double& m_dVal;
public:
	WorkerApply(Worker<double>& w,double& v) : m_Worker(w), m_dVal(v) {}
	void operator()(TCmd& cmd) {
		m_Worker.apply(cmd.getOp(), m_dVal, cmd.getVal);
	}
};

void applyCmds(double& val, double bottom, double top)
{
	Worker<double> w(top, bottom, cCond, dCondVal);
	std::for_each(lCommands.begin(), lCommands.end(), WorkerApply(w,val));
}
0

dyskusja ciekawa, wiec pozwolilem sobie zmienic temat zeby kogos kosz/delete nie korcilo kiedys.. moze przychodzi komus lepszy na mysl?

0

8-O

No cóż, po zastosowaniu funktorów i for_each w jednym tylko miejscu (ale dość krytycznym) prog delikatnie rzecz mówiąc przyśpieszył. Podczas gdy chodził około 5 sekund na samplowych danych na moim kompie (Athlon 3200+) to teraz się wbił w klik-gotowe na P3 600... jestem w szoku. Jutro postnę wyniki z profilera, bo też są masakryczne...

0

porownaj tez wielkosc wynikowych exekow.. moze przy okazji zamiany kompilatorowi udalo sie wyrzucic troche niepotrzebnego kodu.. ew. rozwinac petle

0

Ech cholera :/ tak to jest jak się nie robi save co chwila...

Wyżej napisałem jakie to szybkie, jeszcze później dopisałem trochę zmian i wynikowy exec miał rozmiar 467kb (debug symbols itp) a wcześniejsza przed zastosowaniem funktorów miała jakieś 616kB. Wyniki z profilera... no cóż, mam... tylko że do poprawionej jeszcze lepiej wersji.. mam tylko tą po zastosowaniu funktórów... która bądź co bądź jest szybsza, ale zajmuje już 510kB. nie wiem jakie poprawki naniosłem, bo jak się okazało padła bateria w lapie a ja głupi myślałem że nic nie zmieniłem, dopiero jak kompilowałem na stacjonarnym to zauważyłem różnice...
[sciana]

Teraz może ktoś wie... mam skompilowane pliki *.o i execa z opcją -g (do generowania symboli) i teraz moje pytanie - jak wyciągnąć stamtąd te symbole do pliku? dzieki temu mógłbym odtworzyć kod który był najlepszy...

Proszę, pomóżcie.

//EDIT:

a tak, mam też profile dla wersji tej lekko poprawionej i tej tylko z for_each:

poprawiona:

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 33.33      0.01     0.01    74640     0.00     0.00  Worker<int>::Worker(int, int, char, int)
 33.33      0.02     0.01    74640     0.00     0.00  Worker<int>::operator[](char)
 33.33      0.03     0.01    22756     0.00     0.00  FilterIntf::getRGBData(unsigned int, unsigned int, int&, int&, int&)
  0.00      0.03     0.00   125616     0.00     0.00  TOption::inName(char)
  0.00      0.03     0.00    74640     0.00     0.00  Worker<int>::Work(char, int&, int)
  0.00      0.03     0.00    74640     0.00     0.00  Worker<int>::apply(char, int&, int)
  0.00      0.03     0.00    74640     0.00     0.00  Worker<int>::~Worker()
  0.00      0.03     0.00    74640     0.00     0.00  TOption::WorkerApply<int>::operator()(TCmd&)
  0.00      0.03     0.00    74640     0.00     0.00  TOption::applyCmds(int&, int, int)
  0.00      0.03     0.00    74640     0.00     0.00  WorkBase<int>::~WorkBase()

ta którą mam:

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
100.01      0.01     0.01    49152     0.00     0.00  WorkBase<int>::div(int&, int)
  0.00      0.01     0.00   200256     0.00     0.00  std::_List_iterator<TOption>::operator->() const
  0.00      0.01     0.00   149310     0.00     0.00  std::_List_iterator<TCmd>::_List_iterator(std::_List_node_base*)
  0.00      0.01     0.00   149280     0.00     0.00  std::_List_iterator<TCmd>::operator!=(std::_List_iterator<TCmd> const&) const
  0.00      0.01     0.00   125616     0.00     0.00  TOption::inName(char)
  0.00      0.01     0.00    87397     0.00     0.00  std::_List_iterator<TOption>::_List_iterator(std::_List_node_base*)
  0.00      0.01     0.00    87384     0.00     0.00  TFilterInfo::getOptions()
  0.00      0.01     0.00    74665     0.00     0.00  std::list<TCmd, std::allocator<TCmd> >::begin()
  0.00      0.01     0.00    74645     0.00     0.00  std::list<TCmd, std::allocator<TCmd> >::end()
  0.00      0.01     0.00    74640     0.00     0.00  Worker<int>::Work(char, int&, int)

jak widać na tych samych danych jest cała operacja... teraz muszę tylko wyciągnąć źródła ze skompilowanych.. wiem że się da bo przecież GProf daje annotated source code... no chyba że z annotated source code będę musiał wyciągnąć dane mi potrzebne...

1 użytkowników online, w tym zalogowanych: 0, gości: 1