shared_ptr per referenz übergeben?



  • ich benutze seit gestern boost für ein grösseres projekt. wenn ich einen shared_ptr an eine funktion übergebe, sollte ich dies dann per const-reference tun oder ganz einfach als wert?



  • Geht beides, die Copysemantik des Sharedpointers ist nicht allzu teuer,
    aber performanter wäre natürlich eine Referenz.



  • Die Frage habe ich mir auch mal gestellt, als ich den Fall hatte, dass der shared_ptr durch mehrere Instanzen nur durchgereicht wurde, aber keine Kopie an dieser Stelle benötigt wurde.
    Habe das ganze dann mal getestet und ich konnte keinen Unterschied feststellen, die Kosten sind so gerig gewesen, dass sie in den Zeitschwankungen durch das Multitasking untergegangen sind.
    Hab am Ende dann allerdings die const-Referenz genommen, da sie deutlich ausdrückt, dass an dieser Stelle keine eigene Referenz auf das Objekt benötigt wird.



  • Äh. Nen shared_ptr zu by value übergeben ist sogar sehr teuer, daher immer per const-ref.
    Natürlich muss man das immer in Relation sehen, ne Kopie eines shared_ptr ist verglichen mit dem anlegen eines neuen Threads z.B. sehr billig. Verglichen mit dem rumreichen eines normalen Pointers allerdings sehr sehr teuer.



  • hustbaer schrieb:

    Äh. Nen shared_ptr zu by value übergeben ist sogar sehr teuer, daher immer per const-ref.

    Kannst Du das durch Messungen belegen? Sollte ja kein Problem sein, wenn es nicht nur teuer sondern sogar sehr teuer ist. Dann wundert mich nur, dass lolz keinen Unterschied messen konnte.


  • Mod

    Das ist doch eigentlich - wie immer - weniger eine Performancefrage als eine der Korrektheit und der Ausdrücklichkeit. Es gibt zudem noch die dritte Möglichkeit, nur den blanken Pointer aus get() zu übergeben. Alle 3 Varianten haben unterschiedliche Eigenschaften:
    - bei Übergabe als Value shared_ptr ist es möglich, dass das Übergabe-Argument wärend der Ausführung der Funktion zerstört wird (unmöglich in Standard C++, aber shared_ptr werden ja häufig mit threadübergreifenden Objekten eingesetzt), bei den anderen Varianten darf das nicht passieren
    - Die Übergabe als blanker Pointer wiederum macht es unmöglich, diesen Pointer wieder in einen shared_ptr zu stecken und etwa in einem Container unterzubringen
    Es ist also in Wirklichkeit eine Frage der Bedingungen, die die Funktion an ihre Parameter stellt und stellen darf. Grundsätzlich würde ich deshalb zunächst mit blanken Pointern arbeiten (weil das mit den meisten Restriktionen verbunden ist) und nur dannn auf andere Varianten ausweichen, wenn dies unbedingt nötig ist. Schließlich ist es immer gut, wenn solche Sachen durch den Code selbst, und nicht erst durch ein Kommentar geklärt werden. Aber das bin bloß ich.



  • Ich weiß nicht ob es am Debug-Build lag, aber shared_ptr-Parameter by-Ref habe ich schon mal die Ref-Counts anzeigen lassen. Dabei konnte ich beobachten, das trotz by-Ref die Ref-Counts hochgezählt wurden. Komisch, oder? Meine daraufhin gestellte Frage in der Boost-Mailingliste wurde damit beantwortet, das es überhaupt keinen Sinn macht, shared_ptr by-Ref zu übergeben. Ich habe leider den Wortlaut nicht mehr, das müsste ich zu Hause nochmal nachschauen.



  • Jester schrieb:

    hustbaer schrieb:

    Äh. Nen shared_ptr zu by value übergeben ist sogar sehr teuer, daher immer per const-ref.

    Kannst Du das durch Messungen belegen? Sollte ja kein Problem sein, wenn es nicht nur teuer sondern sogar sehr teuer ist. Dann wundert mich nur, dass lolz keinen Unterschied messen konnte.

    Natürlich kann ich das mit Messungen belegen. Guck z.B. da wenn du mir nicht glaubst: http://www.dlugosz.com/Repertoire/refman/Classics/atomic_counter_whitepaper.html

    Ich habe selbst etwas andere Werte gemessen, nämlich ca. 50ns für ein interlocked increment und etwa 120ns für ein CS lock + unlock (auch mit nem 2.8 GHz P4 mit HT eingeschaltet). Bei 2.8 GHz sind das 140 Zyklen -- in der Zeit kann ein P4 bis zu ~~400 Befehle durchackern. Also, ist das nu teuer oder nicht?

    Und vielleicht solltest du nochmal lesen was ich geschrieben habe. Ich habe geschrieben es ist verglichen mit dem Rumreichen eines normalen Pointers sehr teuer. Ob das einen messbaren Unterschied (auf die Laufzeit des gesamten Programmes) macht oder nicht kommt logischerweise drauf an wie oft das im Vergleich zu anderen Sachen die Zeit kosten passiert.

    EDIT: @Artchi: ich bin mir zu 99% sicher dass das nicht so ist, also dass es sehr wohl Sinn macht, aber ich werde es nachprüfen. Warum ich mir so sicher bin liegt u.A. daran dass ich an einigen Stellen mehr Geschwindigkeit durch genau solche Optimierungen rausgeholt habe. Vielleicht sind sich die Damen & Herren der Boost Mailing Liste auch nicht darüber im Klaren dass ein InterlockedIncrement verflixt teuer ist.



  • hustbaer schrieb:

    Jester schrieb:

    hustbaer schrieb:

    Äh. Nen shared_ptr zu by value übergeben ist sogar sehr teuer, daher immer per const-ref.

    Kannst Du das durch Messungen belegen? Sollte ja kein Problem sein, wenn es nicht nur teuer sondern sogar sehr teuer ist. Dann wundert mich nur, dass lolz keinen Unterschied messen konnte.

    Natürlich kann ich das mit Messungen belegen. Guck z.B. da wenn du mir nicht glaubst: http://www.dlugosz.com/Repertoire/refman/Classics/atomic_counter_whitepaper.html

    Ich habe selbst etwas andere Werte gemessen, nämlich ca. 50ns für ein interlocked increment und etwa 120ns für ein CS lock + unlock (auch mit nem 2.8 GHz P4 mit HT eingeschaltet). Bei 2.8 GHz sind das 140 Zyklen -- in der Zeit kann ein P4 bis zu ~~400 Befehle durchackern. Also, ist das nu teuer oder nicht?

    Und vielleicht solltest du nochmal lesen was ich geschrieben habe. Ich habe geschrieben es ist verglichen mit dem Rumreichen eines normalen Pointers sehr teuer. Ob das einen messbaren Unterschied (auf die Laufzeit des gesamten Programmes) macht oder nicht kommt logischerweise drauf an wie oft das im Vergleich zu anderen Sachen die Zeit kosten passiert.

    EDIT: @Artchi: ich bin mir zu 99% sicher dass das nicht so ist, also dass es sehr wohl Sinn macht, aber ich werde es nachprüfen. Warum ich mir so sicher bin liegt u.A. daran dass ich an einigen Stellen mehr Geschwindigkeit durch genau solche Optimierungen rausgeholt habe. Vielleicht sind sich die Damen & Herren der Boost Mailing Liste auch nicht darüber im Klaren dass ein InterlockedIncrement verflixt teuer ist.

    Sorry, aber wo steht in deinem Link was zu boost::smart_pointer?
    Ich seh da nur eine atomic_counter Klasse.



  • @phlox81:
    Ok, ich bin davon ausgegangen dass man threadsafe' shared_ptr verwendet. Das vorausgesetzt ist nunmal die schnellste Version von shared_ptr jene welche interlocked Befehle verwenden (interlocked increment, interlocked decrement etc.). Und dazu gibts Messungen in dem von mir gelinkten Dokument. Dass das jetzt nur auf die Intel Plattform bezogen ist ist mir auch klar, bloss soweit ich weiss sind interlocked Befehle derzeit auf so ziemlich keiner Hardware billig.

    Ich habe jetzt selbst folgendes Programm ausprobiert:

    #define _WIN32_WINNT 0x0500
    
    #include <windows.h>
    #include <boost/shared_ptr.hpp>
    #include <stdio.h>
    #include <conio.h>
    
    void f1(boost::shared_ptr<int volatile> const& p)
    {
    	(*p)++;
    }
    
    void f2(boost::shared_ptr<int volatile> p)
    {
    	(*p)++;
    }
    
    int main()
    {
    	boost::shared_ptr<int volatile> p(new int volatile(0));
    
    	LARGE_INTEGER li;
    	unsigned loops = 1000*1000;
    	::QueryPerformanceFrequency(&li);
    	double f = li.QuadPart;
    
    	// test f1
    	::QueryPerformanceCounter(&li);
    	double t0 = double(li.QuadPart) / f;
    	for(unsigned i = 0; i < loops; i++)
    		f1(p);
    	::QueryPerformanceCounter(&li);
    	double t1 = double(li.QuadPart) / f;
    
    	printf("*p = %d\n", *p);
    	printf("f1: %f ns/call\n", (t1 - t0) * 1000.0 * 1000.0 * 1000.0 / double(loops));
    
    	// test f2
    	*p = 0;
    	::QueryPerformanceCounter(&li);
    	t0 = double(li.QuadPart) / f;
    	for(unsigned i = 0; i < loops; i++)
    		f2(p);
    	::QueryPerformanceCounter(&li);
    	t1 = double(li.QuadPart) / f;
    
    	printf("*p = %d\n", *p);
    	printf("f2: %f ns/call\n", (t1 - t0) * 1000.0 * 1000.0 * 1000.0 / double(loops));
    
    	_getch();
    }
    

    Output:

    *p = 1000000
    f1: 1.559901 ns/call
    *p = 1000000
    f2: 91.879105 ns/call
    

    Wer natürlich BOOST_SP_DISABLE_THREADS definiert kann getrost shared_ptr rumreichen sooft er mag. Oder wer für (imaginäre) Plattformen entwickelt auf denen interlocked Befehle nicht (wesentlich) mehr als normale Befehle kosten.


Anmelden zum Antworten