Socket-Buffer C vs C++



  • Ok. Jetzt versteh ich wie du das meintest. 😃

    char *p = Buffer.data();
    for ( unsigned i = 0; i < Size; ++i )
       *p++ = (char)( i % 26 + 65 );
    

    Aber mal ehrlich: Ist das noch C++ ? Eigentlich lügt man sich doch hier selber in die Tasche. Der std::vector wird hier nur noch zum "Array-Besorger" degradiert und man verwendet weder Iteratoren noch Indizierung um die Performance von C zu erreichen...

    Da könnte man doch den vector einfach weglassen und gleich beim char-Array bleiben. Der Unterschied ist eigentlich marginal.



  • It0101 schrieb:

    C++ ist immer noch geringfügig langsamer

    Logisch. Du vergleichst ein Array fester Größe mit einem dynamisch allozierten (den eben dies ist der vector). Du könntest ja mal std::array ausprobieren, das entspricht dem C-Array.



  • Mit der neuesten geposteten Variante sind jetzt beide gleich schnell. Passt schon.

    Die "stilistischen" Bedenken bleiben aber...



  • asc schrieb:

    Logisch. Du vergleichst ein Array fester Größe mit einem dynamisch allozierten (den eben dies ist der vector). Du könntest ja mal std::array ausprobieren, das entspricht dem C-Array.

    +1, der Vergleich macht so hinten und vorne keinen Sinn. Abgesehen davon fragt man sich was das mit Sockets zu tun haben soll, wer 1 byte nach dem anderen recv()ed hat ganz, ganz andere Probleme. Ich sehe auch kein Problem damit in C++ normale Arrays zu benutzen. Das macht den Test noch mal schwachsinniger. Das einzige was man vergleichen könnte wäre malloc gegen vector, aber da wird eben einfach das gleiche raus kommen. (Und auch hier wäre es schwachsinnig Zugriffszeiten auf den Speicher zu messen, die sind natürlich exakt gleich. Man will höchstens die Erstellungszeiten messen.)



  • Die Frage ist immer: willst du Stil oder Performance.

    Üblicherweise muss man, wenn man auch das letzte an Performance herauskitzeln möchte, Einbußen beim Stil in Kauf nehmen.
    Letztlich hättest du aber auch bei deinem letzten Code

    char *p = Buffer.data();
    for ( unsigned i = 0; i < Size; ++i )
       *p++ = (char)( i % 26 + 65 );
    

    einfach p[i] schreiben können, anstatt p zu inkrementieren.
    Auch das dürfte (bedingt) schneller sein, als direkt Buffer[i] zu nutzen.
    p[i] ist ein roher Array-Index-Zugriff, während Buffer[i] den überladenen operator[] aufruft. Letzterer wird zwar inline sein und gegebenenfalls vom Compiler wegoptimiert werden, aber da würde ich mich nicht 100%ig drauf verlassen.



  • Nachtrag: Ui, da kommt ja sogar etwas Interessantes bei raus.
    Edit: Code mal nach ISO-C++ portiert.

    #include <iostream> 
    #include <vector>
    
    #ifndef STOPWATCH_HPP
    #define STOPWATCH_HPP
    
    #include <chrono>
    
    template <typename Clock = std::chrono::steady_clock>
    class stopwatch
    {
    public:
    	typedef Clock clock;
    	typedef typename clock::time_point time_point;
    	typedef typename clock::duration duration;
    
    private:
    	time_point last_;
    
    public:
    	stopwatch()
    		: last_(clock::now())
    	{}
    
    	void reset()
    	{
    		*this = stopwatch();
    	}
    
    	time_point now() const
    	{
    		return clock::now();
    	}
    
    	duration elapsed() const
    	{
    		return now() - last_;
    	}
    
    	duration tick()
    	{
    		time_point dummy;
    		return tick(dummy);
    	}
    
    	duration tick(time_point& now_)
    	{
    		now_ = now();
    		auto elapsed = now_ - last_;
    		last_ = now_;
    		return elapsed;
    	}
    };
    
    typedef stopwatch<> default_stopwatch;
    
    template <typename T, typename Rep, typename Period>
    T duration_cast(const std::chrono::duration<Rep, Period>& duration)
    {
    	return duration.count() * static_cast<T>(Period::num) / static_cast<T>(Period::den);
    }
    
    #endif
    
    template <typename T>
    class myvec
    {
    	T* v_;
    	std::size_t size_;
    
    public:
    	myvec(std::size_t size)
    		: v_(new T[size])
    		, size_(size)
    	{}
    
    	// Ja ja, operator = etc. pp. fehlen hier
    
    	~myvec()
    	{
    		if (v_ != nullptr) // Ja Sone/*, das ist schon richtig so.
    			delete[] v_;
    	}
    
    	T& operator [] (std::size_t i)
    	{
    		return v_[i];
    	}
    
    	const T& operator [] (std::size_t i) const
    	{
    		return v_[i];
    	}
    };
    
    void test_vector(std::size_t size, unsigned count)
    {
    	default_stopwatch sw;
    
    	while (count--)
    	{
    		std::vector<char> buffer(size);
    		buffer[(count * 1234567) % size] = 77;
    	}
    
    	std::cout << "vector: " << duration_cast<double>(sw.elapsed()) << '\n';
    }
    
    void test_malloc(std::size_t size, unsigned count)
    {
    	default_stopwatch sw;
    
    	while (count--)
    	{
    		char* buffer = (char*)malloc(size);
    		buffer[(count * 1234567) % size] = 77;
    		free(buffer);
    	}
    
    	std::cout << "malloc: " << duration_cast<double>(sw.elapsed()) << '\n';
    }
    
    void test_raw_new(std::size_t size, unsigned count)
    {
    	default_stopwatch sw;
    
    	while (count--)
    	{
    		char* buffer = new char[size];
    		buffer[(count * 1234567) % size] = 77;
    		delete[] buffer;
    	}
    
    	std::cout << "raw new: " << duration_cast<double>(sw.elapsed()) << '\n';
    }
    
    void test_raii_new(std::size_t size, unsigned count)
    {
    	default_stopwatch sw;
    
    	while (count--)
    	{
    		myvec<char> buffer(size);
    		buffer[(count * 1234567) % size] = 77;
    	}
    
    	std::cout << "raii new: " << duration_cast<double>(sw.elapsed()) << '\n';
    }
    
    int main()
    {
    	const std::size_t size = 1048576;
    	const unsigned count = 5000;
    
    	test_vector(size, count);
    	test_vector(size, count);
    	test_vector(size, count);
    
    	test_malloc(size, count);
    	test_malloc(size, count);
    	test_malloc(size, count);
    
    	test_raw_new(size, count);
    	test_raw_new(size, count);
    	test_raw_new(size, count);
    
    	test_raii_new(size, count);
    	test_raii_new(size, count);
    	test_raii_new(size, count);
    }
    

    Ausgabe:

    vector: 1.06401
    vector: 1.06201
    vector: 1.06201
    malloc: 0.0220203
    malloc: 0.0210204
    malloc: 0.0210201
    raw new: 0.022021
    raw new: 0.0220209
    raw new: 0.0210198
    raii new: 0.0210201
    raii new: 0.0210202
    raii new: 0.0210198
    

    Wenn man sich den generierten Code anguckt fällt auf, dass in der vector Variante der Konstruktor eben nicht geinlined wurde*, vermutlich ist die Implementierung hier einfach zu bulky. Das Erstellen von sehr vielen std::vector könnte also tatsächlich zu einem Bottleneck werden - wobei man sich fragt ob die Lösung dann nicht eher wäre den gleichen Speicher mehrmals zu verwenden, das ist dann in jedem Fall schneller als jede andere Variante.

    * Das gilt sowohl für 32 als auch für 64 bit Code generiert von VS 2013 Preview, mit /MT, function-expansion=any suitable, favor=fast code, etc.

    Edit: Das scheint allerdings nur für VS der Fall zu sein, bei g++ 4.8 und clang 3.3 wird nur new aufgerufen, die dürften damit genau so schnell sein. Sollte Microsoft vielleicht mal nachbessern.



  • @cooky451
    Hast du Iterator-Debugging deaktiviert? Das ist bei MSVC nämlich per Default an.

    Und wieso soll der Test gegen nullptr "schon richtig so" sein?
    Klar ist er richtig. Er ist halt nur unnötig.



  • hustbaer schrieb:

    Hast du Iterator-Debugging deaktiviert? Das ist bei MSVC nämlich per Default an.

    Ich habe alle security-checks ausgestellt.

    #if _ITERATOR_DEBUG_LEVEL != 0
    #error
    #endif
    

    Wenn ich das einfüge kompiliert es problemlos. (Ich benutze doch aber auch gar keine Iteratoren?)

    hustbaer schrieb:

    Klar ist er richtig. Er ist halt nur unnötig.

    Nicht ganz. 1. ermöglicht es in vielen move-Fällen erst die Optimierung den Destruktor auszulassen (das habe ich in einem anderen C vs C++ Thread hier festgestellt), und 2. ist es generell performanter so oft wie möglich ein call auszulassen.



  • C++ ist immer noch geringfügig langsamer

    Ist auch nicht der gleiche Code

    Buffer[ i ] = (char)( i % 26 + 65 );
    

    vs.

    *p++ = (char)( i % 26 + 65 );
    

    Meine Anmerkungen hoerten nicht bei resize auf.



  • Hat er doch schon selbst korrigiert...



  • Ich habe keine Messwerte gesehen. Meine Erwartungshaltung war, dass sie fettgedruckt irgendwo hervorleuchten. Genauso wie man Behauptungen ins Internet hinausschreit, sollte auch der eigene Irrtum bekanntgegeben werden.

    einfach p[i] schreiben können, anstatt p zu inkrementieren.

    Meiner Erfahrung nach ist *p++ schneller als p[i] . Ich verwende dieses Pattern haufig, wenn Speed im Vordergrund steht. Zaehler und Inkrement wuerde ich auch noch sparen (in dem Beispiel wird er nur fuer Testzwecke gebraucht):

    char *p = Buffer.data();
    char const*  pend = p + Buffer.size();
    while( p != pend ) *p++ = ...;
    

    Omfg: Der Code ist auch wesentlich kuerzer. Zur Leserlichkeit: Ich wuerde ihn auf gleicher Stufe wie vorhergehende Varianten stellen. Daraus folgt: Codefragment ist besser geworden. Hey. 🙂 Und nein, ranged-based-for ist langsamer in VS 2013.

    Aber mal ehrlich: Ist das noch C++ ? Eigentlich lügt man sich doch hier selber in die Tasche. Der std::vector wird hier nur noch zum "Array-Besorger" degradiert und man verwendet weder Iteratoren noch Indizierung um die Performance von C zu erreichen...

    Logo ist das C++, std::vector ist ein Arraybesorger, Zeiger sind Iteratoren.

    Da könnte man doch den vector einfach weglassen und gleich beim char-Array bleiben. Der Unterschied ist eigentlich marginal.

    Nee, der Unterschied ist gewaltig: RAII.



  • knivil schrieb:

    Und nein, ranged-based-for ist langsamer in VS 2013.

    Benchmark?

    knivil schrieb:

    Nee, der Unterschied ist gewaltig: RAII.

    RAII bei Arrays auf dem Stack, klingt ja interessant.



  • @knivil:

    Wenn ich also mein Array in einem Template vergrabe, wie es vector macht, ist es C++ und wenn ich meinen Speicher direkt selbst mit new allokiere und zerstöre ( oder als konstantes Array anlege ), ist es kein C++ ?



  • knivil schrieb:

    Ich habe keine Messwerte gesehen. Meine Erwartungshaltung war, dass sie fettgedruckt irgendwo hervorleuchten. Genauso wie man Behauptungen ins Internet hinausschreit, sollte auch der eigene Irrtum bekanntgegeben werden.

    Ich habe geschrieben, dass jetzt beide Varianten eine identische Laufzeit aufweisen. Musst nur mal alle Beiträge lesen und nicht nur ausgewählte. 😉

    Zudem wollte ich nicht behaupten, sondern ich wollte explizit Hilfe, um meine derzeitige C-Lastigkeit im Bereich Sockets noch etwas einzudämmen und ich wollte dabei sichergehen, dass ich mir da kein Performance-Ei ins Nest lege.
    Außerdem bin ich halt bestrebt, die Kritik anzunehmen und auch, sofern performancetechnisch einwandfrei, C++ intensiver einzusetzen. Ich merke ja selbst bei meinem Quellcode, dass der C++-Anteil nicht immer so hoch ist, wie er sein könnte. Ich arbeite halt viel mit Buffern und co. und da ist C meist näher als C++. Und genau diesen Umstand will ich beseitigen, indem ich in mir selbst die Überzeugung nähre, dass C++ in dem Fall keinen Performancenachteil mit sich bringt.



  • Wenn ich also mein Array in einem Template vergrabe, wie es vector macht, ist es C++ und wenn ich meinen Speicher direkt selbst mit new allokiere und zerstöre ( oder als konstantes Array anlege ), ist es kein C++ ?

    Wie kommst du darauf, beides ist C++, nur nutze ich bei einem RAII. Du must RAII in C++ nicht benutzen, aber das ist der Unterschied.

    Musst nur mal alle Beiträge lesen und nicht nur ausgewählte.

    Das habe ich nicht gemeint. Du must meine Posts verstehen. Mir aber voellig egal.



  • Was ich immernoch nicht verstehe und Cookie wohl auch nicht, ist die Möglichkeit, ein konstantes Char-Array als Member in einer Klasse ( oder halt auf dem Stack ) anzulegen und darauf zu arbeiten? Wieso ist die Variante aus deiner Sicht schlechter als die vector<char>-Variante?



  • @It0101 Mach dir mal keine Sorgen um Arrays auf dem Stack. (Außer wenn sie wie hier 1 MB groß sind :D) Aber wenn dein Code C-Lastig ist dann solltest du einfach mal durch gehen, und an jeder Stelle an der ein Handle (z.B. FILE*, oder auch char* auf reservierten Speicher, oder auch SOCKET) wieder frei gegeben wird diese Freigabe in einen Destruktor schieben.



  • cooky451 schrieb:

    @It0101 Mach dir mal keine Sorgen um Arrays auf dem Stack. (Außer wenn sie wie hier 1 MB groß sind :D) Aber wenn dein Code C-Lastig ist dann solltest du einfach mal durch gehen, und an jeder Stelle an der ein Handle (z.B. FILE*, oder auch char* auf reservierten Speicher, oder auch SOCKET) wieder frei gegeben wird diese Freigabe in einen Destruktor schieben.

    Das mach ich sowieso schon. In der Hinsicht gibts doch auch gar keine Probleme.
    Ich habe mich nur deswegen mal damit auseinandergesetzt, weil hier im Forum immer gleich rumgeheult wird, von wegen: "du darfst das so nicht machen, dass ist kein C++", "new benutzt man nicht, weil man immer Container benutzt", usw... Genau deswegen hab ich mich hingesetzt und überlegt, wie ichs besser machen kann.



  • Wieso ist die Variante aus deiner Sicht schlechter als die vector<char>-Variante?

    Abhaengig vom Anwendungszweck. Allgemein beim Array konstanter Laenge als Member kann ich nicht "moven". Eine Kopie ist noetig, ob mit memcpy oder std::copy ist egal (ich nutze letzteres). Obwohl meist ausreichend flink, ist es kein Vergleich zum Verschieben eines Pointers.

    Ich arbeite halt viel mit Buffern und co. und da ist C meist näher als C++

    Ich auch.

    Benchmark?

    Hier bei mir, aus meiner Erfahrung, bei meinem Problem, ... wenn du Sourcecode sehen moechtest, hier:

    void label::buffer::extract(span::buffer const& buf, label l, span::buffer& out_buf) const
    {
        out_buf.lines_.resize(buf.lines().size());
        out_buf.spans_.clear();
    
        auto pline = buf.lines().data();
        auto pline_end = pline + buf.lines().size();
        auto pline_out = out_buf.lines_.data();
        auto pspan = buf.spans().data();
        auto plabel = labels().data();
    
        for (; pline < pline_end; ++pline)
        {
            auto old_size = out_buf.spans().size();
            auto const pspan_end = pspan + *pline;
            for (; pspan < pspan_end; ++pspan, ++plabel)
            {
                if (*plabel == l) out_buf.spans_.push_back(*pspan);
            }
            *pline_out++ = out_buf.spans().size() - old_size;
        }
    }
    

    Aber ich werde ihn nicht in ein nachvollziehbaren Benchmark aufbereiten.

    RAII bei Arrays auf dem Stack, klingt ja interessant.

    Da steht char-Array, ein Vergleich mit std::vector macht nur Sinn, wenn es dynamisch ist.



  • It0101 schrieb:

    ...

    Vermutlich mache ich ja etwas falsch... Bei mir läuft der C++ Code genauso schnell wie der C-Code. Und das auch wenn ich über einen Index zugreife (Getestet unter Visual Studio 2012).

    #include <iostream>
    #include <array>
    #include <windows.h>
    
    const unsigned Size = 1048576;
    const unsigned Cnt = 1000;
    
    void TestCPP()
    {
        std::array<char, Size> Buffer;
    
        long t1 = GetTickCount();
    
        for(unsigned k = 0; k < Cnt; ++k)
            for(unsigned i = 0; i < Size; ++i)
                Buffer[i] = (char)( i % 26 + 65 );
    
        long t2 = GetTickCount();
        std::cout << "C++: " << t2 - t1 << std::endl;
    }
    ...
    

Anmelden zum Antworten