Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise



  • @axam Du kannst einfach kaum Ahnnahmen darüber treffen, welche Berechnungen auf einem beliebigen System wie lange brauchen.
    Möglicherweise kann dein nächstes System superschnell zählen, weil es da eine neue Hardware für gibt, und zack schon sind deine Annahmen kaputt.

    Und auch genereller und weniger theoretisch. Es ist kaum vorherzusehen, was dein Processor an Branch Prediction, Threading, Paging und was sonst noch alles eine Rolle spielt, alles macht. Oder welche Aufrufe dein OS noch dazwischen schiebt.
    Wenn du zeitliche Abläufe simmulieren willst, muss du das selber tun. Wenn du Abläufe synchroniesieren musst, weil Aufgabe A vor Aufgabe B fertig sein muss, dann musst du das selber tun. Du kannst dich nicht darauf verlassen, dass Berechnung A schneller ist als B und das dass immer so bleibt.

    Was Assembler betrifft, da wärst du wahrscheinlich mit x86 bzw x86-64 ganz gut bedient und die Wahrscheinlichkeit ist hoch, dass auch dein nächster PC das noch versteht.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    @john-0 Hab ich auch schon mal überlegt. Nachteil:

    1. Assembler/Maschinencode muß meines wissens nach individuell für jede Hardware geschrieben werden.
    2. Größere Projekte können mit Assembler/Maschinencode nicht realisiert werden.

    Es geht darum zu verstehen, wie der Computer funktioniert. Es geht rein um Lerneffekte und nicht darum ein Programm wie MS Word mit Assembler nachzuprogrammieren. So etwas hat man schon zur Heimcomputerzeit auf einem Atari ST oder Amiga nur noch selten gemacht, in der Regel wurden die Programme damals schon in C oder einen ähnlichen Sprache geschrieben. Auf den 8Bit Heimcomputer sah das vielfach noch anders aus, da wurde in der Hauptsache Z80, 6502 oder 6800 Assembler genutzt.

    Es geht einfach darum, dass Du einige Verständnisprobleme hast, wie ein Computer funktioniert. Das kannst Du mit dem Erlernen von Assembler beheben. Als Plattform würde ich persönlich ARM x86-64 vorziehen, weil letztere ein ziemliches Stückwerk an Assembler nutzt. Man merkt es x86-64 einfach an, dass das einmal aus einem 8Bit Prozessor entwickelt wurde.


  • Mod

    Sogar irgendeiner der vielen Assembler-Simulatoren würde sehr beim Verständnis helfen. Vielleicht sogar mehr noch als das echte Ding, weil man Schritt für Schritt zugucken kann, was passiert. Ist aber weniger spannend als das Experiment am echten System.



  • @SeppJ sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Sogar irgendeiner der vielen Assembler-Simulatoren würde sehr beim Verständnis helfen. Vielleicht sogar mehr noch als das echte Ding, weil man Schritt für Schritt zugucken kann, was passiert. Ist aber weniger spannend als das Experiment am echten System.

    Dann böte sich sogar MMIX an, wenn man gewillt ist sich durch Knuths Werk zu arbeiten.



  • @Schlangenmensch
    Im Grunde habe ich genau dies, dank euch allen inzwischen gelernt.
    Ich kann mich also mehr oder weniger darauf Verlasen das bei von mir gewünschten Berechnungen der "return-Wert" das richtige Ergebnis liefert, ich werde jedoch niemals wissen, was mein Programm genau macht.

    Beispielsweise kann ich niemals einfach eine Schleife programmieren und mich dann darauf verlassen das diese so und so oft aufgerufen wird. Ergo sind Funktionen in ihrer Effizient für mich auch nicht direkt vergleichbar.
    Ich kann daher niemals mit Gewissheit sagen, wenn ich Funktion A so oder so gestalte und dann Funktion B aufrufe, dann ist das ganze effizienter/schneller als wenn ich beide Funktion durch Funktion C ersetze, die dafür doppelt so viele Schritte benötigt, da es ja sein könnte, das der Compiler Funktion C mit einer Multiplikation löst, während er bei den Funktionen A-B gezwungen ist zwei Additionen auszuführen. (Vereinfacht gesagt)
    Und wenn eine Funktion auch von ihrer Funktionsweise abhängig ist, sollte ich besser mal meinen gewünschten return-Wert hinterfragen und die Funktion umschreiben/aufsplitten.

    Ich Danke euch allen für diese Erkenntnis.
    Und auch dafür, daß ich, dank euch jetzt erkannt habe, daß ich mehr Augenmerk darauf legen muß, was genau ich als return-Wert haben will, anstatt meine Zeit damit zu verschwenden, mir Gedanken darüber zu machen wie ich das Programm am besten optimiere, indem ich den gewünschte return-Wert auf diese oder jene Weise berechne, damit dies dann auch am besten/effizientesten Weg geschieht. Denn dies wäre ja nur der beste/effizienteste Weg, wenn das Programm in allen von mir bedachten Fällen so übersetzt würde, wie es von mir ursprünglich gedacht war.
    DANKE

    PS.: MMIX klingt interesant. Muß zwar erstmal 'ne Nacht drüber schlafen, denke aber, ich werd mir das Buch kaufen.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Ich kann mich also mehr oder weniger darauf Verlasen das bei von mir gewünschten Berechnungen der "return-Wert" das richtige Ergebnis liefert

    Ich kann mich also mehr oder weniger immer darauf verlassen, dass bei von mir gewünschten Berechnungen der "return-Wert" das richtige Ergebnis liefert.



  • @wob sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Ich kann mich also mehr oder weniger immer darauf verlassen, dass bei von mir gewünschten Berechnungen der "return-Wert" das richtige Ergebnis liefert.

    OFF-Topic:
    Dazu ein kleiner Witz am Rande:

    Ein Astrologe, ein Physiker und ein Mathematiker fahren mit dem Zug von England nach Schottland.
    Das erste Schaf das der Astrologe in Schottland sieht ist schwarz und er ruft ganz erstaunt aus:
    "Seht mal, in Schottland sind alle Schafe schwarz."
    Darauf der Phsiker: "Eigentlich wissen wir jetzt nur, das es in Schottland schwarze Schafe gibt."
    Meint der Mathematiker:
    "Also, das ist so nicht ganz korrekt.
    Wir wissen jetzt lediglich,
    daß es in Schottland eine Weide gibt,
    auf der ein Schaf steht,
    das zumindest auf einer Seite schwarz ist."

    😁



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Und wenn eine Funktion auch von ihrer Funktionsweise abhängig ist, sollte ich besser mal meinen gewünschten return-Wert hinterfragen und die Funktion umschreiben/aufsplitten.

    Ich verstehe nicht ganz was du meinst. Kannst du ein Beispiel liefern für eine Funktion die "abhängig von ihrer Funktionsweise" (meinst du damit abhängig davon wie der Compiler sie übersetzt?) unterschiedliche Ergebnisse liefern/unterschiedliche Effekte haben könnte?



  • @hustbaer sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Ich verstehe nicht ganz was du meinst. Kannst du ein Beispiel liefern für eine Funktion die "abhängig von ihrer Funktionsweise" (meinst du damit abhängig davon wie der Compiler sie übersetzt?) unterschiedliche Ergebnisse liefern/unterschiedliche Effekte haben könnte?

    Dann hätte er UB ins Programm eingebaut, was man normalerweise explizit zu verhindern sucht.

    Er hat ein ziemlich großes Verständnisproblem, wenn er solche Dinge in Betracht zieht.



  • Was schnell oder langsam ist, hängt noch von soviel mehr ab als den konkreten Anweisungen. Selbst mit zig Jahren Erfahrung fällt es schwer C++ Code auch nur grob nach Performance zu ordnen. Man sollte das daher überhaupt nur angehen, wenn man mit einer Messung bewiesen hat, das die Performance nicht ausreicht.



  • @TGGC sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Was schnell oder langsam ist, hängt noch von soviel mehr ab als den konkreten Anweisungen. Selbst mit zig Jahren Erfahrung fällt es schwer C++ Code auch nur grob nach Performance zu ordnen. Man sollte das daher überhaupt nur angehen, wenn man mit einer Messung bewiesen hat, das die Performance nicht ausreicht.

    Was man meines Erachtens relativ einfach sagen kann, dass man Algorithmen auf modernen CPUs auf Cache Hits optimieren muss. Nichts ist schlimmer, als wenn die CPU auf den Arbeitsspeicher warten muss. Alle Mikrooptimierungen hingegen sollte man nur angehen, wenn man mit dem Profiler konkrete Werte hat und auch vorher schon weiß, dass sich das lohnt.



  • @hustbaer
    Ich kann dir leider kein brauchbares Beispiel geben, denn es ist so wie @john-0 andeutet, ich habe "ein ziemlich großes Verständnisproblem".

    Also wenn ich in meinem Code sage + - schleife, usw. kann ich aus meiner beschränkten sequenziellen Sicht heraus geistig nachvollziehen was genau mein Programm macht, welche Ergebnisse ich dort und da erwarte, und für mich sind es n-Schritte, die für eine Lösung nötig sind.
    Da der Compiler jedoch den von mir geschriebenen Code verändert bin ich gezwungen, daß ich darauf vertraue, daß der Compiler tatsächlich alles richtig macht.
    Sprich es besteht zumindest theoretisch die geringe Möglichkeit, daß ich im Code einen Fehler nicht finden kann, da das Programm fehlerfrei funktionieren würde wenn der Code 1:1 vom Compiler übersetzt wird.

    Ein dummes Beispiel aus der Realität:
    "4 mal 6" und "6 mal 4" sind in der Mathematik äquivalent, also austauschbar.
    Wenn ich nur wissen will, wie viele Äpfel ich habe, sind es immer 24, es macht semantisch jedoch einen unterschied ob ich 4 Packungen habe mit je 6 Äpfeln, oder 6 Packungen mit je 4 Äpfeln.
    Und selbst wenn ich mich darauf festlege, das in einer Packung 6 Äpfel sind, ist da immer noch ein kleiner Unterschied ob ich nun 4 Packungen nehme und mir ausrechne das ich jetzt 24 Äpfel habe (4x6), oder ob ich sehe das in einer Packung 6 Äpfel sind und mir ausrechne das ich bei 4 Packung 24 Äpfel habe (6x4), was der Compiler dann womöglich sogar (übertrieben gesagt) mit einer Division löst (24/6) wo es dann sehr wohl einen unterschied machen würde welcher Wert zuerst steht.
    Gut der letzte Teil mit dem Compiler ist ein wenig an den Haaren herbeigezogen, und es stellt sich auch die Frage wie weitreichend ist die Unterscheidung zwischen "4 mal 6" und "6 mal 4" tatsächlich, allerdings sagte ich bereits am Anfang, es sei ein dummes Beispiel.

    Aber es bleibt jedoch, ab dem Punkt des Compilieren verliere ich die Kontrolle über den Code. Ein Fehler resultiert somit nicht nur ausschliesslich aus einer fehlerhaften Logik meines Codes, sonder es besteht immer auch die Möglichkeit, das der Code für den Compiler einfach nur falsch "formuliert" ist.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Da der Compiler jedoch den von mir geschriebenen Code verändert bin ich gezwungen, daß ich darauf vertraue, daß der Compiler tatsächlich alles richtig macht.

    Äh ja, natürlich. Du wärst aber auch gezwungen, darauf zu vertrauen, dass der Compiler alles richtig macht, wenn er nicht optimiert. Auch da könnte er ja aus Versehen 6*7 rechen, wenn du 6*6 geschrieben hast. Außerdem musst du noch darauf vertrauen, dass die Hardware auch richtig funktioniert und keinen Bug hat (wie z.B. der alte Pentium mit dem fdiv-Bug). Aber jetzt für dich die Überraschung: die Compiler sind sogar so schlau, dass sie den Bug des Prozessors kennen und diesen gekonnt umschiffen! Vertraue also deinem Compiler!

    Abgesehen davon: natürlich könnte dein Compiler irgendwo einen Bug haben, das ist bei der Compilergröße einfach nicht auszuschließen. Aber mehrere Größenordnungen wahrscheinlicher ist, dass du selbst einen Bug in deinem Code hast, bevor durch einen Compilerfehler dein Code falsch übersetzt wird. Praktisch solltest du erstmal von einem fehlerfreien Compiler ausgehen. Und wie gesagt: der Fehler muss gar nicht unbedingt im Optimierer stecken.

    Ein dummes Beispiel aus der Realität:
    "4 mal 6" und "6 mal 4" sind in der Mathematik äquivalent also austauschbar.

    Genau, es gilt das Kommutativgesetz. Deine weiteren Ausführungen machen eigentlich nur auf semantischer Ebene Sinn, aber es kommt ja eh dasselbe heraus.

    Übrigens: wenn du float-Zahlen nimmst, kann der Compiler nicht so frei umformen, da es dort häufig auf die Reihenfolge ankommt. Das ist aber eher ein Problem mit dem Assoziativgesetz.

    Aber es bleibt jedoch, ab dem Punkt des Compilieren verliere ich die Kontrolle über den Code. Ein Fehler resultiert somit nicht nur ausschliesslich aus einer fehlerhaften Logik meines Codes, sonder es besteht immer auch die Möglichkeit, das der Code für den Compiler einfach nur falsch "formuliert" ist.

    Verstehe ich nicht. Der Compiler formt nur so um, dass dasselbe herauskommt wie vorher. Es sei denn, du hast ungültigen Code geschrieben (undefined behaviour).



  • @wob
    Was dein Beispiel mit "6mal6" statt "6mal7" betrift, da hast du mich ganz leicht misverstanden. Wovon du sprichst ist ein Bug, also etwas was immer und bei jedem falsch wäre. Wovon ich spreche ist, wenn der Compiler genau das macht was er immer macht, und in der Regel dieses Verhalten auch erwünscht ist, jedoch in einem ganz speziellem Fall die Codeänderung zum Ursprung eines Fehlers wird.
    Bei letzterem ist eine Änderung des QuellCodes notwendig um denn Fehler zu beheben, bei ersterem muß der Compielerentwikler den Fehler beheben. Ein bloßes Workaround wäre natürlich in beide Fällen QuellCode-Mäsig denkbar.

    Was dein Verständisproblem mit Logik und Formulierung betrifft. In beiden Fällen ist natürlich der Code falsch, aber ich unterscheide dabei zwischen meinen Überlegungen und meiner Formulierung.

    Als Beispiel:
    Als ich mich über die Verwendung von "volatile" befasst habe wurde auf Wikipedia ein Beispiel angeführt, wo der Compiler ein "cout" und alles dazugehörige aus dem Programm entfernt, weil er es für unnötig hält.

    In diesem Beispiel wäre die Logik meines Codes nur dann falsch, wenn dieses "cout" auch im QuellCode fehlt, da ich nicht daran gedacht habe das ich so ein "cout" irgend wann brauchen könnte.

    Die Formulierung des Codes ist hingegen falsch, wenn im Code zwar dieses "cout" drin steht, aber Aufgrund von mangelndem Compilerwissen kein "volatile", und der Compiler somit das "cout" entfernt. Erfolgt dann von aussen ein entsprechender Zugriff kann "cout" nicht ausgeführt werden und Aufgrund von mangelndem Compilerwissen habe ich keine Ahnung wieso. Die Logik wäre somit in diesem Fall zwar richtig, die Formulierung ist jedoch falsch, und Aufgrund von mangelndem Compilerwissen gestaltet sich die Fehlersuche schwierig.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Also wenn ich in meinem Code sage + - schleife, usw. kann ich aus meiner beschränkten sequenziellen Sicht heraus geistig nachvollziehen was genau mein Programm macht, welche Ergebnisse ich dort und da erwarte, und für mich sind es n-Schritte, die für eine Lösung nötig sind.
    Da der Compiler jedoch den von mir geschriebenen Code verändert bin ich gezwungen, daß ich darauf vertraue, daß der Compiler tatsächlich alles richtig macht.
    Sprich es besteht zumindest theoretisch die geringe Möglichkeit, daß ich im Code einen Fehler nicht finden kann, da das Programm fehlerfrei funktionieren würde wenn der Code 1:1 vom Compiler übersetzt wird.

    Das ist korrekt und auch durchaus realistisch. Compiler können Fehler haben, und manchmal haben sie auch wirklich welche die wirklich falschen Code erzeugen. Wir hatten z.B. einen Fall wo der Compiler falscherweise angenommen hat dass es an einer bestimmten Stelle keinen Overflow geben kann, und daher keine Notwendigkeit besteht ein 32 Bit Zwischenergebnis im 64 Bit Register auf 32 Bit zu maskieren. Das Ergebnis war dann eine viel zu grosse Zahl die hinten rausgekommen ist. (Und nur für alle dich sich jetzt denken "die haben sicher signed Integer Overflow gemacht, und das ist ja wohl UB": nein, das war alles unsigned.)

    Also ja, kann passieren. Ist aber sehr sehr selten. Das selbe kann BTW in der CPU selbst passieren, und auch da hat es Fälle gegeben wo CPUs falsch gerechnet haben (z.B. der berühmte Pentium fdiv Bug oder auch der Bug in Intels TSX Erweiterung). Dort ist es allerdings noch viel seltener als bei Compilern.

    Eine Möglichkeit die du allerdings bei Compilern hast ist ohne Optimierungen zu kompilieren. Dadurch macht der Compiler kaum bis gar keine schlauen Optimierungen mehr, und erzeugt Code der quasi 1:1 so funktioniert wie das was du hingeschrieben hast. Dadurch bleiben auch alle Schleifen und sinnlosen Operationen 1:1 erhalten. Wenn du einen Fehler beobachtest, und der auch mit nicht optimiertem Code auftritt, dann ist es sehr wahrscheinlich dass der Fehler in deinem Programm ist und nicht im Compiler. Wenn der Fehler nur im optimierten Programm auftritt ist es immer noch viel wahrscheinlicher dass er in deinem Code ist, aber dann ist es zumindest möglich dass es sich um einen Compiler-Fehler handelt.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Wovon ich spreche ist, wenn der Compiler genau das macht was er immer macht, und in der Regel dieses Verhalten auch erwünscht ist, jedoch in einem ganz speziellem Fall die Codeänderung zum Ursprung eines Fehlers wird.

    Wenn eine Optimierung in einem speziellen Fall zu falschem Verhalten führt, ist das auch ein Bug im Compiler.
    Mir ist bisher in meiner Laufbahn noch kein Compilerfehler untergekommen, ich kenne nur Fälle aus der Literatur.
    Tatsächlich bist du, wenn du Software entwickeln willst, darauf angewiesen, dass die CPU funktioniert wie spezifiziert, der Compiler funktioniert wie erwartet, das das OS funktioniert wie erwartet und das irgendwelche 3 Party Libs funktionieren. Bei letzteren stolpert man schon mal über Fehler, alles andere funktioniert recht gut.

    Wenn du alles selbst machen möchtest, viel Spaß beim Löten von Schaltkreisen 😉 Aber, selbst machen heißt noch lange nicht, dass es hinterher Fehlerfrei ist, dann ist man nur wirklich auch selbst Schuld.


  • Mod

    @Schlangenmensch sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Wenn eine Optimierung in einem speziellen Fall zu falschem Verhalten führt, ist das auch ein Bug im Compiler.
    Mir ist bisher in meiner Laufbahn noch kein Compilerfehler untergekommen, ich kenne nur Fälle aus der Literatur.

    Das erinnert mich an die fast-floating-point-math-Optimierung vieler Compiler, die viele Leute aktivieren, ohne die Doku genau zu lesen, unter welchen Sonderumständen diese Optimierung zu (dokumentiert!) anderem Verhalten führt. Und sich dann wundern. Hat mich schon viel Zeit gekostet, Kollegen damit zu helfen, die selber dachten, einen Compilerbug gefunden zu haben.



  • @Schlangenmensch sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Tatsächlich bist du, wenn du Software entwickeln willst, darauf angewiesen, dass die CPU funktioniert wie spezifiziert, der Compiler funktioniert wie erwartet, das das OS funktioniert wie erwartet und das irgendwelche 3 Party Libs funktionieren. Bei letzteren stolpert man schon mal über Fehler, alles andere funktioniert recht gut.

    Du schreibst "recht gut". Das kann ich so unterschreiben.

    Speziell beim OS sind Fehler aber gar nicht so selten. Da hab ich schon mehr als einen in Windows gefunden.

    Compilerfehler die zu Fehlermeldungen bei korrektem Code führen hab ich auch selbst bereits einige gefunden. Einen sogar der zu falschem Code führt (falscher Overload ausgewählt). Compilerfehler wo so richtig "falsch gerechnet" wird hat wie schon geschrieben ein Kollege von mir einen gefunden, davon abgesehen kenne ich es auch nur aus der Literatur.

    Und schliesslich CPU Fehler kenne ich auch nur aus der Literatur/Internet. Weder selbst einen gefunden noch kenne ich persönlich jmd. der einen gefunden hat.

    Also Zuverlässigkeit ist mMn. recht eindeutig CPU > Compiler > OS > Third-Party Libs & alter (bewährter) eigener Code > neuer eigener Code.

    ps: @axam Ich mach den Job jetzt aber seit > 20 Jahren, also lass dich nicht schrecken/entmutigen. Was @Schlangenmensch geschrieben hat stimmt schon - das Zeug funktioniert schon recht gut.


  • Mod

    @hustbaer sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    ps: @axam Ich mach den Job jetzt aber seit > 20 Jahren, also lass dich nicht schrecken/entmutigen. Was @Schlangenmensch geschrieben hat stimmt schon - das Zeug funktioniert schon recht gut.

    Es gibt da natürlich noch eine ganz andere Kiste, nämlich inwiefern du den Machern all dieser Dinge vertrauen kannst, und ob du überhaupt prinzipiell(!) prüfen könntest, ob das Vertrauen gerechtfertigt ist: https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_ReflectionsonTrustingTrust.pdf



  • @hustbaer sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Compilerfehler die zu Fehlermeldungen bei korrektem Code führen hab ich auch selbst bereits einige gefunden. Einen sogar der zu falschem Code führt (falscher Overload ausgewählt). Compilerfehler wo so richtig "falsch gerechnet" wird hat wie schon geschrieben ein Kollege von mir einen gefunden, davon abgesehen kenne ich es auch nur aus der Literatur.

    Das ist bei MSVC in letzter Zeit recht schlimm geworden, zumindest wenn man sich gleich die vorgeschlagenen VS Updates installiert. "Lustig" fand ich paar dead code elimination Bugs hintereinander. Hat viel Zeit gekostet, den Bug zu finden (in einer third party Bibliothek), dann haben wir gefunden, dass den schon jemand gemeldet hatte, dann wurde in dem Ticket rumdiskutiert und Begründung war irgendwie, die dead code elimination hat offensichtlich nötigen Code wegoptimiert.
    Nächstes SP mit dem Bugfix installiert, anderer Bug in einer anderen third party aufgefallen... Und dann wieder dead code elimination als Begründung 🙂


Log in to reply