Warum bremst isalnum mein Programm hier so?



  • Hallo,

    ich hab mal eine Performance bezogene Frage. Und zwar habe ich ein Edit-Control, über dessen Text ich bei jedem TextChanged Event char-weise iteriere. Die Loop sieht ungefähr so aus (nur die relevanten Zeilen):

    void TextChanged(void)
    {
        for(unsigned int i = 0; i < textLength; i++)
        {
            if(isalnum(text[i]))
                // Mach irgendwas
        }
    }
    

    Wenn ich jetzt einen sehr langen Text habe (ca. 600000 Zeichen) und ich halte im Edit-Control eine Tast einfach gedrückt so dass laufend neuer Text eingegeben wird, dann ruckelt das Ganze sehr stark (25% CPU Auslastung bei einem Quad-Core).

    Das komische ist, wenn ich das isalnum rausschmeiße, läuft es auch mit noch wesentlich mehr Zeichen perfekt flüssig (0% CPU Auslastung). Wenn ich den check direkt in das If schreibe, etwa so bleibt die Performance super (0% Auslastung):

    void TextChanged(void)
    {
        for(unsigned int i = 0; i < textLength; i++)
        {
            if((text[i] > 96 && text[i]< 123) || (text[i]> 64 && text[i]< 91) || (text[i]> 47 && text[i]< 58))
                // Mach irgendwas
        }
    }
    

    Das macht genau das Gleiche, läuft aber perfekt flüssig. Woran liegt das? Nur an dem Funktionsaufruf alleine kanns ja nicht liegen, da ich auch sonst noch einige Funktionsaufrufe in der Schleife mache (etwa stack.push_back etc.) und sich die eigentlich gar nicht auf die Performance auswirken.

    Auch wenn ich die Überprüfung der chars in eine eigene Funktion auslagere, etwa so:

    bool my_isalnum(char c)
    {
        if(c > 96 && c < 123) || (c > 64 && c < 91) || (c > 47 && c < 58)
            return true;
        return false;
    }
    

    Ist die Performance eine Katastrophe. Es scheint also tatsächlich am Funktionsaufruf zu liegen, aber wieso dann nur bei dieser Funktion?



  • So nutze er den Debugger und schaue er welch skandalöse Dinge geschehen!



  • Release-Build gemacht? Optimierungen eingeschaltet?


  • Mod

    theta schrieb:

    Release-Build gemacht? Optimierungen eingeschaltet?

    Das hier wird's sein. Ein Funktionsaufruf für jedes einzelne Zeichen ist schon teuer, das will inline sein. Beim my_isalnum bekommt dein Compiler bei deinen Einstellungen das anscheinend hin, beim Standard isalnum anscheinend nicht.



  • Vielleicht mal die Template Variante aus std::locale ausprobieren. Die sollte in jedem Fall geinlined werden können. Bei eigenen Funktionen kommt es oft darauf an, wo die Funktion definiert ist. Ansonsten am besten einen kleinen Funktor schreiben, der kann bei entsprechender Implementierung immer ginlined werden.



  • Aufgeschmissener schrieb:

    So nutze er den Debugger und schaue er welch skandalöse Dinge geschehen!

    Was soll dass denn bringen? Da passiert nichts "skandalöses", es ist halt einfach langsam.

    theta schrieb:

    Release-Build gemacht? Optimierungen eingeschaltet?

    SeppJ schrieb:

    Das hier wird's sein. Ein Funktionsaufruf für jedes einzelne Zeichen ist schon teuer, das will inline sein. Beim my_isalnum bekommt dein Compiler bei deinen Einstellungen das anscheinend hin, beim Standard isalnum anscheinend nicht.

    Ok, hätt ich jetzt nicht gedacht dass das bei nem char als Argument schon so teuer ist... Also ich hab das vielleicht schlecht geschrieben, aber my_isalnum ist auch (genauso) schlecht wie das native isalnum. Nur wenn ich die Abfrage direkt in das if schreibe läuft es sehr gut.

    TNA schrieb:

    Ansonsten am besten einen kleinen Funktor schreiben, der kann bei entsprechender Implementierung immer ginlined werden.

    Also ein Funktionspointer kann leichter geinlined werden als eine normale Funktion? Wieso ist das so?



  • Functor != Funktionspointer.



  • std::isalnum
    int isalnum( int ch );
    Checks if the given character is an alphanumeric character as classified by the current C locale

    das Performanceproblem lautet: "by the current C locale"



  • Darum sagte ich Debugger, um zu schauen, was isalnum über happystudents Logik hinaus bewerkstelligt. Aber happystudent hat das als fähiger Troll ja einfach weggewischt.



  • knivil schrieb:

    Functor != Funktionspointer.

    Ups, hatte das verwechselt 🙂

    Aufgeschmissener schrieb:

    Darum sagte ich Debugger, um zu schauen, was isalnum über happystudents Logik hinaus bewerkstelligt. Aber happystudent hat das als fähiger Troll ja einfach weggewischt.

    Warum gleich so beleidigend?

    Wie bereits gesagt habe ich bereits den Debugger benutzt und es wird nichts weiter gemacht als meine Funktion aufgerufen. Es kann ja sein dass isalnum irgendwelche komischen Sachen im Hintergrund macht. Aber das Performance Problem existiert ja, wie du wüsstest wenn du genau gelesen hättest, auch mit meiner selbst definierten Funktion my_isalnum die gar nichts im Hintergrund macht. Auf solche Comments kann ich verzichten.

    dd++ schrieb:

    das Performanceproblem lautet: "by the current C locale"

    Was bedeutet das genau?



  • Das Hauptproblem ist, dass nach einem Tastendruck der komplette Text verarbeitet wird.


  • Mod

    happystudent schrieb:

    dd++ schrieb:

    das Performanceproblem lautet: "by the current C locale"

    Was bedeutet das genau?

    Das echte isalnum macht eventuell noch wesentlich kompliziertere Prüfungen als deines, da das eingebaute isalnum mit allen möglichen Codierungen und Sprachen zurecht kommt. Wenn du die richtige locale wählst, dann wird beispielsweise auch "ä" korrekt als Buchstabe erkannt. Dazu ist deine Funktion vom Design her gar nicht fähig, daher ist deine Funktion möglicherweise schneller als isalnum.

    Da aber bei dir beide langsam sind, liegt es wohl wirklich an fehlender Optimierung. Welchen Compiler nutzt du und wie rufst du ihn auf?



  • otze schrieb:

    Das Hauptproblem ist, dass nach einem Tastendruck der komplette Text verarbeitet wird.

    Ja, leider denke ich gehts nicht anders 😞

    Also ich mache einen Syntax-Highlighter und da muss ich leider immer über den Ganzen Text laufen...

    SeppJ schrieb:

    Das echte isalnum macht eventuell noch wesentlich kompliziertere Prüfungen als deines, da das eingebaute isalnum mit allen möglichen Codierungen und Sprachen zurecht kommt. Wenn du die richtige locale wählst, dann wird beispielsweise auch "ä" korrekt als Buchstabe erkannt. Dazu ist deine Funktion vom Design her gar nicht fähig, daher ist deine Funktion möglicherweise schneller als isalnum.

    Da aber bei dir beide langsam sind, liegt es wohl wirklich an fehlender Optimierung. Welchen Compiler nutzt du und wie rufst du ihn auf?

    Also ich benutz den Compiler von VS 2008. Aufrufen tu ich den einfach per F6 😃
    Kenn mich da leider noch nicht so wirklich aus, wie man wo noch was optimieren kann.



  • happystudent schrieb:

    Also ich benutz den Compiler von VS 2008. Aufrufen tu ich den einfach per F6 😃
    Kenn mich da leider noch nicht so wirklich aus, wie man wo noch was optimieren kann.

    Wahrscheinlich baust du eine Debug-Version. Da wird kein Funktionsaufruf geinlined. Ich kenne mich mit VC leider auch nicht aus.



  • Oben ist im Visual Studio ein Pfeil zu sehen (mit dem kann man das Programm starten), daneben steht sowas wie "Debug" in einem Dropdown-Menü, da kannst du Release auswählen. Dann werden gewisse Optimierungen schon eingeschaltet. Ansonsten gehst du über "Projekt > Eigenschaften (Alt + F7) > Konfigurationseigenschaften > C/C++ > Optimierung" hin und stellst alles mögliche ein.



  • Für einen Syntax-Highlighter solltest du dir mal Scintilla anschauen.

    Und wenn du wirklich(!?!) selber den Syntax-Highlighter programmieren willst, dann solltest du aber die Überprüfungen in Abhängigkeit der vorgenommenen Tastendrücke vornehmen (und des angezeigten Textauschnitts).



  • Th69 schrieb:

    Oben ist im Visual Studio ein Pfeil zu sehen (mit dem kann man das Programm starten), daneben steht sowas wie "Debug" in einem Dropdown-Menü, da kannst du Release auswählen. Dann werden gewisse Optimierungen schon eingeschaltet. Ansonsten gehst du über "Projekt > Eigenschaften (Alt + F7) > Konfigurationseigenschaften > C/C++ > Optimierung" hin und stellst alles mögliche ein.

    Ok Danke. Werd mal überprüfen ob damit my_isalnum richtig geinlined wird.

    Th69 schrieb:

    Für einen Syntax-Highlighter solltest du dir mal Scintilla anschauen.

    Und wenn du wirklich(!?!) selber den Syntax-Highlighter programmieren willst, dann solltest du aber die Überprüfungen in Abhängigkeit der vorgenommenen Tastendrücke vornehmen (und des angezeigten Textauschnitts).

    Also ich benutze schon Scintilla für das Ganze Highlighting. Allerdings unterstützt Scintilla kein Kontextsensitives Highlighting wie etwa Intellisense, daher benutze ich den TextChanged Event um dynamisch Schlüsselwörter hinzuzufügen oder zu entfernen (abhängig vom jeweiligen Scope in dem das Caret sich befindet).



  • happystudent schrieb:

    dd++ schrieb:

    das Performanceproblem lautet: "by the current C locale"

    Was bedeutet das genau?

    Die "current C locale" ist global. Und die meisten C++ Implementierungen sind (auch schon vor C++11) thread safe.

    Daraus folgt: sie müssen den Zugriff auf diese globale Einstellung "current C locale" synchronisieren. In der Praxis (*) heisst das mindestens 2 "Interlocked" Befehle (z.B. Lock + Unlock einer Mutex).

    Und "Interlocked" Befehle sind laaaaangsam. Rechne mal mit so 50~300 Zyklen (**) pro Stück, also 100~600 Zyklen Overhead für die nötigen zwei.
    Dazu kommt noch der Impact des Cache-Line-Stealing wenn so ne Schleife in mehreren Threads gleichzeitig läuft, und sich die Threads die ganze Zeit die Cache-Line mit der Mutex gegenseitig klauen.

    In Summe WEIT mehr als ein Funktionsaufruf der nicht inlined wird.

    *: Theoretisch könnte man vermutlich mit einem "Interlocked" Befehl auskommen, oder vielleicht sogar ganz ohne. Nur müsste man dazu vermutlich mit Hazard-Pointern oder ähnlich heiklen Teilen arbeiten, und ich hätte noch keine C++ Implementierung gesehen die das tut.

    EDIT: Hmmm... es könnte sogar noch einfacher gehen. Die Anzahl der unterstützten Locales ist ja begrenzt, und die Implementierung könnte einfach immer alle instanzieren. Dann würde ein relaxed atomic load reichen. Und die sind normalerweise recht billig. Trotzdem: ich kenne keine Implementierung die sowas in der Art macht. Wenn jmd. eine kennt bitte mich wissen lassen. /EDIT

    **: Je nach CPU Typ. Pentium 4 war ganz übel. Core 2 ist viel besser. Sandy und Ivy werden vermutlich mindestens auf Niveau von Core 2 sein. Haswell soll angeblich nochmal schneller sein.



  • Wenn das Problem das locking ist, kann man dann nicht einfach ein ctype facet per use_facet aus einer locale nutzen und per pointer overload von ctype::is die Informationen für mehrere Zeichen gleichzeitig erhalten? Sofern die implementierung intern nur ein mal lockt.



  • Ich meine das müsste gehen.
    Ein std::ctype Objekt ist ja immutable, right?


Anmelden zum Antworten