Vector Elementzugriff - Index vs. Iterator



  • Hi,

    ich versuche gerade eine schnellere Lösung für den Zugriff auf Elemente eines zweidimensionalen Vektors zu finden. Der Indexoperator erzeugt nach meinem Verständnis bei jedem Zugriff einen neuen begin() Iterator, also müsste es ja eigentlich schneller sein, den Iterator nur einmal zu erzeugen und zu speichern, richtig?

    Also ab zum Testen mit folgender Datenstruktur:

    typedef vector< vector<char> > intvec2D;
    typedef vector< vector<char>::iterator > intvec2D_it;
    
    // define 2D-array for visibility data
    // also define array of iterators for faster access to ar_vis
    intvec2D ar_vis(pbW); 
    intvec2D_it ar_vis_its(pbW);
    
    for (int i = 0; i < pbW; i++)
    {
    	ar_vis[i].resize(pbH);
    	ar_vis_its[i] = ar_vis[i].begin();
    }
    
    // finally define an iterator for that iterator array
    intvec2D_it::iterator ar_vis_it = ar_vis_its.begin();
    

    Jetzt greife ich auf die Elemente zu, um Index vs. Iterator zu testen:

    for (int i = 0; i < pbW; ++i) 
    {
    	for (int j = 0; j < pbH; ++j) 
    	{
    		//const char t = ar_vis[i][j];
    		const char t = *(*(ar_vis_it + i) + j);
    		if (t >= 1) ++ar_res[ t-1 ];
    	}
    }
    

    "ar_vis" ist dabei ziemlich groß, in etwa 1000 * 1000. Der Test mit dem Iterator braucht jetzt in etwa 4x so lang wie das gleiche mit Indexoperator (die auskommentierte Zeile). Diese sequenzielle Schleife kann man ja durchaus noch etwas schneller machen:

    for (int i = 0; i < pbW; ++i) 
    {
    	vector<char>::iterator tIt = *(ar_vis_it + i);
    	for (int j = 0; j < pbH; ++j) 
    	{
    		const char t = *(tIt + j);
    		if (t >= 1) ++ar_res[ t-1 ];
    	}
    }
    

    Ist aber immer noch deutlich langsamer. Außerdem brauche ich für reale Anwendungsfälle einen breiter gestreuten Elementzugriff, also nicht mehr sequenziell. Dadurch wird die zweite Methode unbrauchbar.

    Ich verstehe nun nicht warum der Zugriff über Iterator so langsam ist. Also was mache ich falsch?

    Vielen Dank im Voraus,
    Luzzifus.



  • Benutze einen Profiler.



  • Wie bitte?



  • Luzzifus schrieb:

    Der Indexoperator erzeugt nach meinem Verständnis bei jedem Zugriff einen neuen begin() Iterator,

    Nein.



  • Ein Profiler sagt dir unter anderem wie oft er welche Funktion/welchen Operator genutzt und wie viel Zeit damit verbraucht wurde.



  • Das weiß ich doch längst, ich weiß bloß nicht warum b langsamer ist als a.

    CptC schrieb:

    Luzzifus schrieb:

    Der Indexoperator erzeugt nach meinem Verständnis bei jedem Zugriff einen neuen begin() Iterator,

    Nein.

    Aha. Trotzdem müsste er in etwa das gleiche tun was ich manuell versuche, oder?



  • Übersetz mal im Release-Mode.



  • Tachyon schrieb:

    Übersetz mal im Release-Mode.

    Iteratoren immer noch langsamer als Index, allerdings ist der Unterschied geringer.



  • Luzzifus schrieb:

    Tachyon schrieb:

    Übersetz mal im Release-Mode.

    Iteratoren immer noch langsamer als Index, allerdings ist der Unterschied geringer.

    Wenn Du den Microsoftschen Compiler benutzt, sind vielleicht noch checked Iterators an. Guck Dir in dem Fall mal diesen Artikel an.



  • Tachyon schrieb:

    Wenn Du den Microsoftschen Compiler benutzt, sind vielleicht noch checked Iterators an. Guck Dir in dem Fall mal diesen Artikel an.

    Danke für den Artikel, sehr interessant! Ja, ich benutzt Visual Studio 2005.

    Ich habs mal explizit deaktiviert. Im Debug Mode hats keinen Unterschied gebracht, im Release Mode sind jetzt alle drei Varianten zumindest gleich schnell.



  • Luzzifus schrieb:

    Tachyon schrieb:

    Wenn Du den Microsoftschen Compiler benutzt, sind vielleicht noch checked Iterators an. Guck Dir in dem Fall mal diesen Artikel an.

    Danke für den Artikel, sehr interessant! Ja, ich benutzt Visual Studio 2005.

    Ich habs mal explizit deaktiviert. Im Debug Mode hats keinen Unterschied gebracht, im Release Mode sind jetzt alle drei Varianten zumindest gleich schnell.

    Schneller als äquivalente Indexzugriffe kanns auch nicht sein. Unter der Haube läuft schließlich das Selbe ab.



  • Ich hatte halt in einer seriös wirkenden Quelle gelesen dass der Index-Operator bei jedem Zugriff einen neuen begin() Iterator erzeugt, daher hatte ich durchaus Hoffnungen. 😃



  • Luzzifus schrieb:

    Ich hatte halt in einer seriös wirkenden Quelle gelesen dass der Index-Operator bei jedem Zugriff einen neuen begin() Iterator erzeugt, daher hatte ich durchaus Hoffnungen. 😃

    Dann hat die Quelle aber nur seriös gewirkt. Warum sollte da jedesmal ein iterator erzeugt werden?
    std::vector ist ja nicht viel mehr als ein gekapseltes dynamisches Array. der Indexzugriff dürfte daher ein entsprechender Indexzugriff auf das zugrundeliegende Array sein. Da std::vector ein Template ist wird der Compiler alle Möglichkeiten haben diesen Indexzugriff zu inlinen.
    Ein std::vector<>::iterator dürfte kaum mehr sein als ein gekapselter Pointer auf das Array. Wenn du die ganzen Sicherheitsabfragen ausbaust, die beim MSVC drin sind, dürfte im Releasemodus nach dem (relativ einfachen) Wegoptimieren der ganzen Indirektionen über die Iteratormethoden auch nurnoch ein entsprechend indizierter Zugriff über den internen Pointer des Iterators übrigbleiben.



  • Luzzifus schrieb:

    Ich hatte halt in einer seriös wirkenden Quelle gelesen dass der Index-Operator bei jedem Zugriff einen neuen begin() Iterator erzeugt, daher hatte ich durchaus Hoffnungen. 😃

    Das ist eigentlich nicht falsch. operator[] macht ja eigentlich nix anderes, als (vorrausgesetzt, die Iteratoren seien einfache Pointer):

    reference operator[] ( size_type n )
    {
        return *(begin() + n); // begin() wird hier eventuell direkt durch den internen Anfangspointer ersetzt
    }
    

    Iteriert werde dabei z.B. folgendermaßen:

    vector<int> v;
    
    // []-Variante
    for(size_t i = 0; i < v.size(); ++i) cout << v[i] << endl;
    
    // Iteratorvariante
    for(vector<int>::iterator i = v.begin(); i != v.end(); ++i) cout << *i << endl;
    

    Bei der []-Variante muss also bei jedem Iterationschritt eine Addition mit 0 bis v.size()-1 durchgeführt werden, bei der Iteratorvariante jeweils eine Addition mit 1 (da ++i). Ich kenne mich jetzt mit den CPU-Implementierung für die Addition nicht so aus, aber wenn es da einen Unterschied gibt, wird der denk ich minimal sein (wobei dann wahrscheinlich die Iteratorvariane sogar schneller wäre).



  • XMaster schrieb:

    Luzzifus schrieb:

    Ich hatte halt in einer seriös wirkenden Quelle gelesen dass der Index-Operator bei jedem Zugriff einen neuen begin() Iterator erzeugt, daher hatte ich durchaus Hoffnungen. 😃

    Das ist eigentlich nicht falsch. operator[] macht ja eigentlich nix anderes, als (vorrausgesetzt, die Iteratoren seien einfache Pointer):

    reference operator[] ( size_type n )
    {
        return *(begin() + n); // begin() wird hier eventuell direkt durch den internen Anfangspointer ersetzt
    }
    

    Iteriert werde dabei z.B. folgendermaßen:

    vector<int> v;
    
    // []-Variante
    for(size_t i = 0; i < v.size(); ++i) cout << v[i] << endl;
    
    // Iteratorvariante
    for(vector<int>::iterator i = v.begin(); i != v.end(); ++i) cout << *i << endl;
    

    Bei der []-Variante muss also bei jedem Iterationschritt eine Addition mit 0 bis v.size()-1 durchgeführt werden, bei der Iteratorvariante jeweils eine Addition mit 1 (da ++i). Ich kenne mich jetzt mit den CPU-Implementierung für die Addition nicht so aus, aber wenn es da einen Unterschied gibt, wird der denk ich minimal sein (wobei dann wahrscheinlich die Iteratorvariane sogar schneller wäre).

    Im Prinzip richtig. Aber das AMD Assembler-Optimierungshandbuch sagt, es ist im Prinzip Wurstegal, weil genau gleichschnell, außer natürlich, wenn man einen Schritt weiterdenkt, und bemerkt, daß die []-Variante gerne mal ein Register weniger braucht, und weniger register pressure ist was tollen, dadurch können globalere Sachen in Register hüpfen.


Log in to reply