Wie programmiere ich performant? Literaturtipps?



  • Ok, stimmt, wenn ich nicht debugge, schmeißt er anderen Code raus, der auch nicht wesentlichs schneller ist, aber die o.g. Aufrufe nicht hat. Vielleicht versteh ich den Profiler auch nicht so ganz. Jetzt zeigt er mir an, dass ein fstp ca.40% der Rechenzeit einer Funktion verbaucht. Kann ja auch nicht wirklich sein, oder?
    Ich probier mal den Glowcode aus..[edit: NOOOOOOOT, wer gibt mir die 499$]



  • dapadu schrieb:

    Du kompilierst aber nicht im debug modus bei der performance messung?

    Kannst die die Codestellen mal zeigen (funktion+aufruf)?

    Ja, sorry, ich wusste nicht, dass der Debug die performance so einschränkt. Ausserdem bekomm ich es nicht hin, nicht zu debuggen und im Profiler noch c++ code zu bekommen 😕
    Code folgt:

    //Um nicht ständig return werte zu bekommen und in die nächste Funktion zu
    //stecken habe ich die Module durch Pointer verbunden, wo sie ihren Eingang her
    //beziehen und ihren Ausgang reinschreiben
    float CSVoice::computeNextSample()
    {
    
    	if(!m_active)return 0;
    
    	m_initWave[0].computeNextSample();//displayed on the lefthand side
    	m_initWave[1].computeNextSample();
    	(*m_mixOut)+=m_inGainModulated*(m_mixModulated*m_mixIn1+m_oneMinusMix*m_mixIn2)+m_sympathyInput;
    	m_mixIn1=0;
    	m_mixIn2=0;
    	m_filter.computeNextSample();
    	m_antiFixpointHP.computeNextSample();
    	m_delay.computeNextSample();
    	m_nonlinearity.computeNextSample();
    	if(m_loopInput<0.0000000001&&m_loopInput>-0.0000000001&&m_loopInput!=0)m_denormalCounter++;
    	else m_denormalCounter=0;
    	if(m_denormalCounter>500)
    	{
    		m_delay.clear();
    		m_active=false;
    	}
    	return m_loopInput;
    }
    //Ein variables nicht-integer delay
    void CSVFDelay::computeNextSample()
    {
    	//linearInterpolation
    	(*m_output)=m_delayLine[m_readPointer2]*m_integerRemainder+m_delayLine[m_readPointer]*(1-m_integerRemainder);
    	//if(m_loopOutput)(*m_loopOutput)=(*m_output);
    	m_delayLine[m_writePointer]=m_input;
    	m_input=0;
    	m_writePointer++;
    	m_readPointer++;
    	m_readPointer2++;
    	if(m_writePointer==kMaxDelay)m_writePointer=0;
    	if(m_readPointer==kMaxDelay)m_readPointer=0;
    	if(m_readPointer2==kMaxDelay)m_readPointer2=0;
    }
    //Ein brute-force fir-filter mit 5 bzw. 13 koeffizienten
    //Wollte noch ausprobieren, ob es mit Ringpuffer schneller geht, siehe
    //diskussion im Forum
    void CSParamFilter::computeNextSample()
    {
    	memmove(&m_inBuffer[m_activeInBuffer][1],&m_inBuffer[m_activeInBuffer][0],m_lengthMinusOne[m_activeInBuffer]*sizeof(float));
    	float sum=0;
    	m_inBuffer[m_activeInBuffer][0]=m_input;
    	for(int i=0;i<m_filterLength[m_activeCoeff];i++)
    		sum+=m_inBuffer[m_activeInBuffer][i]*m_theCoeffs[m_activeCoeff][i];
    	(*m_output)+=sum;
    	if(m_loopOutput)(*m_loopOutput)=sum;
    	m_input=0;
    }
    //wendet eine nichtlineare funktion auf den Input an. Diese ist definiert durch
    //eine Tabelle (functionLut), die im x-Bereich von -1 bis 1 geht. 
    void CSNonlinearity::computeNextSample()
    {
    	m_input*=m_preGain;
    	if(m_input>1)
    	{
    		m_input=1;
    	}
    	if(m_input<-1)
    	{
    		m_input=-1;
    	}
    
    	int in=(int)(m_input*m_hLutSize);
    	float rest=m_input*m_hLutSize-in;
    	in+=m_hLutSize;
    	//linearInterpolation:1. between two LookUpTable values, 2. linear/nonlinear
    	float temp=(m_functionLut[in]*(1-rest)+m_functionLut[in+1]*rest)*m_nonlinearity+(1-m_nonlinearity)*m_input;
    	(*m_output)=temp*m_postGain;
    	m_input=0;
    }
    

    Mit Sicherheit lässt sich da einiges tun, aber sooo schlecht wie mein Ergebnis hätte ich es nicht erwartet...
    Grüße
    Sören



  • soerenP schrieb:

    Ok, stimmt, wenn ich nicht debugge, schmeißt er anderen Code raus, der auch nicht wesentlichs schneller ist, aber die o.g. Aufrufe nicht hat.

    die stelle die 50% der zeit zieht ist weg und es laeuft immer noch gleich schnell? ja...

    Vielleicht versteh ich den Profiler auch nicht so ganz. Jetzt zeigt er mir an, dass ein fstp ca.40% der Rechenzeit einer Funktion verbaucht. Kann ja auch nicht wirklich sein, oder?

    doch, die allermeisten programme sind speicherlimitiert, da die compiler wenn alle optimierungen eingeschaltet sind akzeptablen code generieren und fuer den misst der dann noch uebrig bleibt, dafuer sind die x86 cpus angepasst worden.
    was unoptimiert bleibt ist meist der speicher-teil, durch allignment und padding kann der compiler nur maginal aushelfen, da auch dafuer die x86-cpus ausgelegt sind.



  • Naja, es ist schon schneller, aber nicht so, wie ichs brauche.
    Zum vergeich:
    Für etwa den selben Algorithmus (vom Syntheseprinzip her) schafft es ein anderes Plugin anstatt mit 30% CPU-Auslastung mit sowenig dass ich die Anzeige nicht lesen kann. (2-3%)
    Ich kann mir beim besten Willen nicht vorstellen, wie man aus dem Code noch 90% rausholen kann. Aber scheint ja zu gehen...
    Gibt es denn Techniken oder allgemeine Regeln, wie ich die Speicherzugriffe minimieren kann? Ich kann mir grad nicht vorstellen, wie ich das Optimieren/vermeiden kann...



  • Meistens sind eindimensionale array schneller als mehrdimensionale. Kannst du ja mal umbauen.

    Zu deinen if

    if(m_input>1)
        {
            m_input=1;
        }
        if(m_input<-1)
        {
            m_input=-1;
        }
    
        //sollte etwas schneller sein, aber macht vielleicht sogar schon der compiler
        if(m_input>1)
        {
            m_input=1;
        }
        else if(m_input<-1)
        {
            m_input=-1;
        }
    


  • Pointer Zugriff auf Array sollte schneller sein, als mit Index.

    Und wenns Hardware abhängig sein darf
    http://de.wikipedia.org/wiki/SIMD (SSE Zeugs)



  • Wenn m_delayLine ein std::vector ist könntest du mal versuchen nen einfachen Pointer draus zu machen. Je nach STL Implementierung macht das durchaus nen Unterschied.

    Und natürlich immer nur den Release Build profilen.
    Source-Code beim Debuggen/Profilen bekommst du im Release-Build indem du einfach im Release-Build auch die Erzeugung von Debug-Infos einschaltest. Hat bei mir zumindest immer funktioniert.



  • soerenP schrieb:

    Für etwa den selben Algorithmus (vom Syntheseprinzip her) schafft es ein anderes Plugin anstatt mit 30% CPU-Auslastung mit sowenig dass ich die Anzeige nicht lesen kann. (2-3%)

    das könnte in assembler geschrieben worden sein. möglicherweise solltest du das mit deinem code auch tun (zumindest die flaschenhälse).
    🙂



  • Ich bin jetzt nicht ganz durch den Code durchgestiegen, aber das memmove() hört sich nicht gerade optimal an. Wieviel bytes werden da immer verschoben? Vielleicht eine Ringpuffer-Struktur anwenden?

    Edit: bzgl. dem anderen Plugin: "Optimiert" es vielleicht gar am Konzept? Also z.B. IIR statt FIR?



  • @soerenP: wie gross ist denn der verwendete LUT? Wenn der recht gross ist (> 1/2 1st Level Cache oder sogar > 2nd Level Cache) kann das schon gröber bremsen.

    @Tim: er schreibt 5 bzw. 13 Samples, wobei ein Sample ein float ist. Wären also 20 bzw. 52 Bytes. Bei 52 bytes könnte IMO ein Ringpuffer etwas schneller sein, bei 20 eher nicht.

    Allerdings würde ich den Puffer umdrehen und memcpy statt memmove verwenden.

    p.S.: oder gleich ne for() Schleife - memcpy ist entweder intrinsic (=langsam weil "rep movsb") oder aber ein unnötiger function call + test auf quadword alignment etc. (=unnötiger overhead für so kleine blöcke).



  • Tim schrieb:

    Edit: bzgl. dem anderen Plugin: "Optimiert" es vielleicht gar am Konzept? Also z.B. IIR statt FIR?

    Ich denke nicht. Ich habe es mal mit IIRs ausprobiert. Die verschieben die Phase in einer Weise, dass man starke verstimmungen erhält. (sowohl zwischen zwei Tönen, als auch im Ton selber (Inharmonizität)) Bei Firs kann man die Verzögerung des Kernels bis zu einem bestimmten Punkt im Delay ausgleichen und alles ist gestimmt und harmonisch.

    hustbaer schrieb:

    @soerenP: wie gross ist denn der verwendete LUT? Wenn der recht gross ist (> 1/2 1st Level Cache oder sogar > 2nd Level Cache) kann das schon gröber bremsen.

    @Tim: er schreibt 5 bzw. 13 Samples, wobei ein Sample ein float ist. Wären also 20 bzw. 52 Bytes. Bei 52 bytes könnte IMO ein Ringpuffer etwas schneller sein, bei 20 eher nicht.

    Allerdings würde ich den Puffer umdrehen und memcpy statt memmove verwenden.

    p.S.: oder gleich ne for() Schleife - memcpy ist entweder intrinsic (=langsam weil "rep movsb") oder aber ein unnötiger function call + test auf quadword alignment etc. (=unnötiger overhead für so kleine blöcke).

    Der LUT ist recht groß (20 "metrische" KiloByte), damit ich linear interpolieren kann. Lieber intelligenter interpolieren als großen LUT?
    Den memmove/Ringpuffer teste ich nochmal...
    Lädt der denn den ganzen Lut in den Cache? Ich sollte mich mal mit Computerarchitektur beschäftigen, glaub ich.
    Vielen Dank für die zahlreichen Vorschläge!
    Grüße
    Sören



  • hustbaer schrieb:

    @Tim: er schreibt 5 bzw. 13 Samples, wobei ein Sample ein float ist. Wären also 20 bzw. 52 Bytes. Bei 52 bytes könnte IMO ein Ringpuffer etwas schneller sein, bei 20 eher nicht.

    Oh, den Kommentar in dem die 5/13 steht habe ich irgendwie... ignoriert 🙂



  • ZU den LUTs:
    Ich hab die mal auf 500 mal 4 Byte geschrumpft. Hat nicht wirklich was gebracht...
    Grüße
    Sören



  • soerenP schrieb:

    ZU den LUTs:
    Ich hab die mal auf 500 mal 4 Byte geschrumpft. Hat nicht wirklich was gebracht...
    Grüße
    Sören

    Hast du nicht mal eine vollständige Testimplementierung, die man mal laufen lassen kann? Alles ist mit Dummywerten gefüllt, nur der langsame Kernalgorithmus nicht?



  • Ponto schrieb:

    Hast du nicht mal eine vollständige Testimplementierung, die man mal laufen lassen kann? Alles ist mit Dummywerten gefüllt, nur der langsame Kernalgorithmus nicht?

    ???
    Versteh ich nicht? Das Ding läuft komplett als VST-Instrument. Nur zu langsam um das einzubauen, was ich noch vor habe, oder um es auch für andere Leute interessant zu machen. Was lässt dich denn denken, dass da noch nichts läuft? Und was meinst du mit "Alles ist mit Dummywerten gefüllt, nur der langsame Kernalgorithmus nicht?"
    Gruß
    Sören



  • soerenP schrieb:

    Ponto schrieb:

    Hast du nicht mal eine vollständige Testimplementierung, die man mal laufen lassen kann? Alles ist mit Dummywerten gefüllt, nur der langsame Kernalgorithmus nicht?

    ???
    Versteh ich nicht? Das Ding läuft komplett als VST-Instrument. Nur zu langsam um das einzubauen, was ich noch vor habe, oder um es auch für andere Leute interessant zu machen. Was lässt dich denn denken, dass da noch nichts läuft? Und was meinst du mit "Alles ist mit Dummywerten gefüllt, nur der langsame Kernalgorithmus nicht?"
    Gruß
    Sören

    Ich meine, dass ich nur vom Lesen deines Quellcodes keine Laufzeitprobleme sehen kann. Etwas, was man lokal laufen lassen kann, wäre besser. Und da will man nicht das ganze Plugin haben, sondern die langsame Routine auf irgendwelchen Dummydaten.



  • Ahhhh, entschuldige. "Hast nicht mal?" kann man auf zwei Arten interpretieren, ich hab die falsche gewählt.
    Ich mach morgen mal was fertig, vielen Dank für deine Hilfe!
    Sören



  • Performant programmiert wird mit -O3.



  • Hab aber nur 02, was mach ich jetzt? 😞 😉
    Man könnte mal probieren, was der gcc dazu sagt, war aber immer ein bisschen zu blöd die Geschichte ans laufen zu bekommen. Ich denke aber nicht, dass da Wunder zu erwarten sind, oder?


Anmelden zum Antworten