Alles rund um Compiler und Softwareentwicklung

So, habe mir MPC-BE mal angeschaut. Mit dem MinGW wird das nix. In der Anleitung steht zwar, wie man eine MinGW Umgebung einrichtet. Es gibt seltsamerweise aber keinen Buildpfad für MinGW. Vielleicht irgendwelche Einrichtungsleichen vom originalen MPC, wo die Kompilierung mit MinGW mal angedacht war, aber nicht umgesetzt wurde. Habe daher die VS2013 Solution über Codeblocks importiert. Bekomme da allerdings Fehler. Also ob es einwandfrei importiert wurde, weiss ich nicht. Im Endeffekt auch egal. MPC-BE nutzt die MFC. Keine Ahnung, ob es dafür MinGW kompatible Lösungen gibt. Das Standard MinGW Package unterstützt die MFC jedenfalls nicht.
 
Aha, so ist das.
 
1. In C/C++ gibt es "for", wo man jegliches Schindluder mit treiben kann, wie z.B. einzelne Elemente eines arrays in der Schleife löschen, bis das Programm abschmiert, etc.
Das liegt aber nicht am for per se, sondern am eigentlichen Elementzugriff. C und C++ prüfen keine Indizierungen. Und das ist auch gut so, da jedes Prüfen Zeit kostet. Dafür verlangt es vom Entwickler entsprechende Wachsamkeit. Passt er nicht auf, kann folgendes passieren:
Code:
int buf[10]; // Elemente 0..9
...
buf[20] = 42; // <- oops, Element 20 existiert ja gar nicht im Array

Das ist syntaktisch und semantisch korrekter Code, den jeder C und C++ Compiler akzeptieren wird. Allerdings ist es auch kaputter Code, weil ein ungültiger Speicherzugriff erzeugt wird. Dh, hier wird auf Speicher zugegriffen, der womöglich einem anderen Objekt zugewiesen ist oder gar nicht existiert. Was die Anwendung dann zur Laufzeit macht, ist einfach Zufall und hängt von den Begleitfaktoren ab. Daher wird es vom Sprachstandard auch als undefiniertes Verhalten deklariert. Viele Sicherheitslücken in der Vergangenheit basierten auf genau solchen Sachen.

Zumindest bei compiletime Konstanten wie im Beispiel können smarte Compiler auf Fehler hinweisen. Diese Möglichkeit gibt es aber natürlich bei weitem nicht immer, zB:
Code:
void foo(int* buf)
{
    buf[20] = 42; // und nun?
    ...
}

Deshalb sollte man bei jeder Schleife schon vorher prüfen, ob die möglichen Indizes auch sicheren Zugriff auf das Array gewährleisten. Ob man das nun manuell macht oder auf builtin Konstrukte wie foreach zurückgreift, läuft codetechnisch aufs gleiche hinaus. Letzteres erhöht aber natürlich die Sicherheit, weil die Fehleranfälligkeit für den Programmierer geringer ist.

Übrigens, auch foreach ist schon länger in C++ möglich. War bisher allerdings etwas umständlich und nur mit Containern nutzbar, die entsprechende Iteratoren zur Verfügung stellten:
Code:
std::vector<int> v;
...
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
    ...

Seit C++11 ist dies einfacher umsetzbar und wurde direkt in die Sprachspezifikation integriert.
Code:
std::vector<int> v;
...
for (int i : v) // for each i in v
    ...
 
Das ist zwar korrekt gruffi, aber ich stimme nicht mit dir darüber ein dass das "gut so" ist. So kleine checks die sich mit wenigen assemblerbefehlen machen lassen oder die der Compiler sogar bei einer flussanalyse wegoptimieren könnte, sollten performancetechnisch kein großes Dilemma sein, wenn dafür die Anzahl an Sicherheitslücken abnimmt.
Natürlich wird sorgfalt von den Programmieren verlangt, aber wir sind auch nur Menschen - und den selben wirtschaftlichen Zwängen unterworfen wie alle anderen. Ergo kann einfach im Eifer des Gefechts schonmal eine Prüfung vergessen werden wenn man an mehreren Projekten und Baustellen gleichzeitig arbeitet.
Foreach allgemein erleichtert die Iteration über container unheimlich. C++ mag dafür bis 2011 gebraucht haben, andere Sprachen bieten das selbe schon seit vielen Jahren. Und gerade dass die indizes dabei wurscht sind und ich mich nicht darum kümmern muss wie viele Elemente eigentlich in dem Array oder der Liste sind.
Allgemein beitet die C-sche pointer-Artihmetik und die weniger strikte Unterscheidung zwischen Pointer und Array einige stolperfallen.
Wenn man dann noch die ollen Library-Routinen alla scanf betrachtet mit ihren Stolperfallen, dass man ein char ohne weiteres einem unsigned byte zuweisen kann usw. naja.
Also ich sage mal es gibt Sprachen die zu Fehlern einladen und welche bei denen man die meisten Fehler besser erkennen kann, bevor das PRogramm ausgeliefert wird.
Wobei auf irgend eine Weise jede Plattform so ihre Stolperfallen hat. Wenn man Runtimes wie die JVM oder .net nicht mag, bietet D meiner Meinung nach auch einige schöne Ansätze. Aber wieso alle Welt an diesem Fossil C festklebt ist mir nicht wirklich eingängig sorry. *noahnung*
 
Aber wieso alle Welt an diesem Fossil C festklebt ist mir nicht wirklich eingängig sorry. *noahnung*

Portabilität, nativ, kleiner Code, für embedded oft die einzigste Möglichkeit, leicht zu implementieren auf neuen Architekturen, jede Menge Beispielcode und Bibliotheken, keine Runtime erfoderlich, kein Interpreter nötig...
mehr fällt mir auf die schnelle nicht ein.
Leider ist viel Disziplin nötig um sauberen Code zu schreiben und auch Fehlerquellen sauber abzufangen.
 
Das ist zwar korrekt gruffi, aber ich stimme nicht mit dir darüber ein dass das "gut so" ist. So kleine checks die sich mit wenigen assemblerbefehlen machen lassen oder die der Compiler sogar bei einer flussanalyse wegoptimieren könnte, sollten performancetechnisch kein großes Dilemma sein
Mit "Flussanalyse" lässt sich da aber nix wegoptimieren, da die Daten meistens erst zur Laufzeit bestimmbar sind. Du hast also immer irgendwelche Vergleiche, Sprünge, geworfene Exceptions, usw. Und das kann ordentlich auf die Performance drücken in entsprechend kritischen Schleifen. Es ist also schon gut so, dass C und C++ nativ nix weiter bei Indizierungen machen. Alles darüber hinaus erledigt man sowieso besser mit Containern, die dann auch genügend Sicherheit bieten.

Allgemein beitet die C-sche pointer-Artihmetik und die weniger strikte Unterscheidung zwischen Pointer und Array einige stolperfallen.
Die Unterscheidung in der Sprachdefinition ist schon strikt. Leider erlaubt C und C++ die implizite Umwandlung eines Arrays in einen Zeiger auf das erste Element. Weshalb die Unterscheidung bei wenig versierten Programmierern dann oft leidet. Halt ein Relikt aus alten Tagen, was heutzutage sicherlich fragwürdig ist. Allerdings gilt das auch für andere implizite Umwandlungen.

Wenn man dann noch die ollen Library-Routinen alla scanf betrachtet mit ihren Stolperfallen, dass man ein char ohne weiteres einem unsigned byte zuweisen kann usw. naja.
Auch da gibt's dank C++11 und smarter Compiler schon deutlich sichere Lösungen. Wobei man hier sowieso entsprechende String Streams nutzen sollte.

Aber wieso alle Welt an diesem Fossil C festklebt ist mir nicht wirklich eingängig sorry.
Neben dem was amdfanuwe schrieb, es ist eine wenn nicht sogar die hardwarenahste Hochsprache und hat nach wie vor eine enorme Basis. Wenn es geht nutze ich mittlerweile aber lieber C++.
 
Wenn es geht nutze ich mittlerweile aber lieber C++.

Ich auch. Und wenn man weiß, wie C++ arbeitet, kann man genauso effektiven Code wie in C schreiben, jedoch durch die Typprüfung wesentlich sicherer, durch die Klassen strukturierter, durch Polymorphismus lesbareren Code.
Allerdings kann man immer noch genügend Blödsinn anstellen.
Wenn man das System nicht verstanden hat, kommt bei jeder Programmiersprache nur Murks raus.
Jede Programmiersprache hat ihren Einsatzzweck, nur denke ich manchesmal, das manche Programmiersprache nur erfunden wurde, weil der Erfinder mit den Existierenden nicht richtig klar kam bzw. wurden manche Sprachen künstlich aufgebläht weil sich die Nutzer nicht an richtige Werzeuge ranwagten (Basic und Pascal, ursprünglich als Lehrsprachen gedacht).

Richtig traurig finde ich eigentlich, dass es so lange gedauert hat, bis multithreading und parallele Abarbeitung in den Standard aufgenommen wurde. Bisher war Multithreading in C++ ja nur über OS Funktionen bzw. Libraries verfügbar, die immer erst mehr oder weniger umständlich eingebunden werden mußten.
 
Sorry aber ein großer Teil davon sind selbsterfüllende Tatsachen. Wie viel Beispielcode und Bibliotheken für eine Sprache existieren hängt davon ab wie häufig sie benutzt wird.
Wenn das also letztentendes ein Grund für ihre Benutzung selbst darstellt, negieren wir jeglichen höheren Sinn der Weiterentwicklung, weil es ja sowieso weniger Beispielcode gibt als in C un daher niemand die neue Sprache nutzen würde.
Da beißt sich doch die katze in den Schwanz.
Portabilität ist relativ und betrifft bestenfalls den Quellcode. Wobei schon dort einige Abhängigkeiten zu Compiler und Plattform bestehen können. Ich muss allerdings zugeben dass mein C-Wissen etwas eingerostet ist (mehr als 10 jahre alt). Wir wurden damals mit einem Borland Turbo-C Compiler (16Bit) gequält.
Das mit der Codegröße muss ich erstmal stehen lassen. Allerdings im Zeitalter der Gigabytes Arbeisspeicher auch eher ein sekundäres Argument. Wobei ich zugegebenermaßen wenig mit Embedded-Programmierung zu tun habe. Kommen da immernoch 80535 Mikrocontroller zum einsatz mit ein paar Kbyte Speicher?
Interpreter brauchen Sprachen wie D, Go etc. ebenfalls nicht. Und die "Runtime" - naja, das trifft auch nur zu wenn du alles statisch kompilierst. In dem Augenblick wo du DLLs oder ähnlichen Code erst zur Laufzeit bindest, ist genau dieser Code deien "runtime" - Also er muss vorhanden sein auf dem Zielsystem.
Dennoch nehme ich alle diese "Vorteile" zur Kenntnis, stelle aber die Frage ob es das wert ist wenn man bedenkt wie viel mangelhaften Code es gibt, wie viele Sicherheitslücken etc. und ob nicht etwas langsamerer / größerer code verschmerzbar wäre wenn damit ein großteil der Angriffsfläche eliminiert werden würde.
IMHO ist C eifnach deswegen noch da weil jeder es mal irgendwo gesehen und "gelernt" hat, und daher wirds halt weiter so verwendet. Das ist bequemlichkeit, nicht in Stein gemeißelt. Zu einem gewissen Grad womöglich auch wirtschaftliche Entscheidung nach dem Motto "wir haben zigtausend Mannstunden in C und C++ Bibliotheksfunktionen, routinen und andere 'intellectual property' investiert, und das muss nun genutzt und gemolken werden mindestens bis zum Jahre 3458..."
Verzeiht mir den Sarkasmus.
Meine Erfahrungen sind vielleicht auch nicht unbedingt Repräsentativ, denn ich muss jeden Tag damit rechnen sauber geschriebenen Code wieder zerfleddern zu müssen und "hinzubasteln" wenn dem Kunden einfällt dass er das gerne anders hätte.
Das ist eben der Unterschied zwischen Statischer, Release- und Milestone- Orientierter Software und direkt für einen Mandanten dynamisch angepasster, auf den Leib geschriebener Software, die sich jederzeit ändern kann. *noahnung*
Trotzdem stehen einem die Haare zu Berge wenn man von manchen Lücken so hört und der Verbreitung die sie erreicht haben... und oftmals nur wegen mangelnder Speicehrüberprüfung überhaupt möglich sind.
 
Kommen da immernoch 80535 Mikrocontroller zum einsatz mit ein paar Kbyte Speicher?

Mittlerweile verwendet man PIC.
bis zu 536k Byte Rom
bis zu 96k Byte Ram
bis zu 4k Byte EEPROM
alles on Chip und kein Adressbus oder Datenbus vorhanden, nur ein paar I/O.
und das sind schon die größeren 16 Bit Typen.
Schau mal in deine Motorsteuerung im Auto, könnte so ein Teil drin sein.

OS haben die Dinger natürlich nicht. Wozu? Nach dem Reset gibt es eventuell noch etwas Hardware Initialisierung in Assembler, dann gehts direkt zu Main().
Ich denke mal, dass einige Embedded Entwickler hart zu knabbern haben, wenn sie dann plötzlich mit einem OS wie Autosar oder Linux konfrontiert werden. Deshalb haben die Fahrzeuge von 2005 - 2009 manchmal ganz schöne Probleme mit der Motorsteuerung :)

Und in Industrien wie Automobilentwicklung, Maschinenbau, Luft und Raumfahrt dauert es Jahre, bis ein neues Tool, Compiler etc. eingeführt wird. Lieber die bekannten alten Macken umschiffen als sich neue, unbekannte einhandeln.
 
Das auf jeden Fall.
Hab mich grade durch den doch etwas ausschweifenden excavator-Thread gelesen und stelle mir dabei die Frage, wie viel kostet eigentlich ein Context-Switch in einem CPU-Kern?
Das gehört nur zum Teil in den Software-Thread, weil es ein Hardware bezogenes Feature ist - aber es hat doch unmittelbar mit der Software zu tun.
Manche "Experten" scheinen die Meinung zu vertreten mit einem 10Ghz Singlecore wäre jeder optimal bedient. Ich bin da anderer Meinung, aber es wäre doch mal schön sich das auszurechnen.
In Anbetracht der Tatsache dass bei einem Context-Switch alle Register und die Pipeline geflushed werden müssen, vielleicht noch ein Teil der caches (loop-cache z.B.) dürfte das eine vergleichsweise teure Operation sein, in Takten gerechnet. Ich meine, eigentlich ist es ja schon Wahnsinn wie viele Assemblerbefehle für einen Funktionsprolog nötig sein können um den Stackframe entsprechend einzurichten und alle Parameter dort abzulegen etc.
Ein Context-Switch, bei dem ja potenziell komplett anderer Code laufen kann muss ja in dem Fall noch deutlich aufwändiger sein.
d.h. Es muss einen bestimmten "sweet spot" geben, ab wie vielen Threads pro Core die Ausführung noch halbwegs effizient vonstatten geht, und ab wann der Overhead der Context-Switches alles auffrisst.
In Anbetracht der Tatsache dass z.b. auf meinem Notebook just in diesem Moment schon 22 Prozesse in der Prozessliste stehen, wovon jeder unter umständen mehrere Threads haben kann... nunja.

@amdfanuwe
Die paar Bytes wirken im Jahre 2014 wie eine Rückkehr ins DOS-Zeitalter, wo Kilobytes noch teure Ressourcen waren. Ich meine klar, Curiosity hat weniger Rechenleistung als ein Smartphone, aber irgendwie kommt es mir komisch vor, dass da noch solche Zustände herrschen. Erklärt aber einiges über die Trägheit des Marktes und der Entwicklung an sich.
 
Die paar Bytes wirken im Jahre 2014 wie eine Rückkehr ins DOS-Zeitalter, wo Kilobytes noch teure Ressourcen waren.

In der Industrie zählt jeder Cent. Da wird einem Ingenieur schon mal ein paar Monate Zeit gegeben, um eine Schaltung dahingehend zu optimieren, dass sie ein Cent billiger produziert werden kann. Bei millionen Stückzahlen rechnet sich das.
Zudem muss man den Anwendungszweck betrachten. Ein paar Sensoren abfragen, einige Berechnungen, ein kleiner Fehlerspeicher, Entsprechende Ausgabepins für Steuerungszwecke und blinkende LEDs und Pipsen im Fehlerfall. Da braucht es nicht viel.
Denk an Rauchmelder, Digitaluhren, Stromzähler, kleine Steuerungen überall wo sich ein Motor dreht, Spühlmaschinen, Waschmaschine, Aufzüge, Rolltreppen etc. Mittlerweile steigen jedoch die Anforderungen, da die Geräte im Fehlerfall den Fehler auch über das Internet melden sollen, per E-Mail oder SMS bzw. gleich einen Webserver zur Konfiguration und Statusabfrage enthalten sollen.

Zum Kontexswitching: Hab da letztens auch einen Artikel gelesen bei dem es darum ging, dass bei aktueller Software, jeder Socket ein Thread, die Server fast nur noch mit Threadswitching beschäftigt sind. Deshalb auch die Nachfrage nach Microservern.
 
Eben das meinte ich bei den Contextswitches. Bei Virtualisierung ja auch so ein Thema. Und interessanterweise sind die meisten virtuellen Kisten die ich gesehen haben dennoch eher I/O-Limitiert als auch CPU-Ebene.

Bezüglich der Industrie magst du Recht haben, aber genau hier haben wir nun den Punkt. Das "Internet der Dinge" - oder auf deutsch, jeder USB-Stick muss online gehen können.
Spätestens hier rächt sich der Sparwahn. Denn in dem Moment wo so ein Gerät sich irgendwie online betätigt, ist es angreifbar für Hacker, Trojaner etc. Und ich Aussicht dass mein gesamter Haushalt teil eines Botnetzes ist das ein Chinesischer Hacker fernsteuert ist nun wirklich nicht gerade ein Schmankerl.
Genau hier wirds nämlich gefährlich. Mag C auch noch so effizient sein und jedes Byte irgendwie nutzen können, so hat doch jüngst der Heartbleed-Bug gezeigt wie einfach man langezeit unentdeckte Sicherheitsprobleme schaffen kann, wenn man einfach nur eine Speicherüberprüfung vergisst. Genau hier gehören IMHO höhere Sprachen verwendet, die mehr integrierte Sicherheit bieten. Menschen sind nunmal fehlbar. - Aber so wirds nicht kommen. Die chips werden so designt dass sie mit hängen und würgen gerade eben so ein bisschen onlinekram verkraften können und alles wird wieder mit der Hand am Arm hinprogrammiert... und in 10 Jahren oder so fällt irgendwem erst auf dass alles offen ist wie ein Scheunentor :]
Ich will garnicht wissen wie oft das Rad in der IT schon neu erfunden wurde, wie viele Funktionen die es eigentlich längst hab mal eben händisch nachgebastelt wurden nur weil man von der Existenz der anderen Funktion entweder nichts wusste, oder sie aus lizenzrechtlichen, Speichertechnischen usw. Gründen nicht nutzen konnte.

Zurück zur Software:
Hier ist ein etwas älterer, aber dennoch interessanter Vergleich: http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html
Intel 5150: ~1900ns/process context switch, ~1700ns/thread context switch
Intel E5440: ~1300ns/process context switch, ~1100ns/thread context switch
Intel E5520: ~1400ns/process context switch, ~1300ns/thread context switch
Intel X5550: ~1300ns/process context switch, ~1100ns/thread context switch
Intel L5630: ~1600ns/process context switch, ~1400ns/thread context switch
Intel E5-2620: ~1600ns/process context switch, ~1300ns/thread context siwtch
Das Ganze unter Linux, das Genaz noch ohne die Auswirkungen der "Cache pollution" - wie in dem Blogartikel weiter unten konstantiert wird.
Fakt ist, 1300 nanosekunden sind bei einer 1Ghz-CPU schon 1300 Takte. Im konkreten Fall war der E5-2620 bereits eine 2Ghz-CPU, also sind das 2600 Takte die Flöten gehen um nur einen Thread zu switchen, ein ganzer Prozess kostet gar um die 3200 Takte. Wenn man nun zugrunde legt wie wenige Takte eine durchschnittliche Rechenoperation kostet (Gruffi, du kannst doch das Assemblerhandbuch auswendig, gib mal ein paar Zahlen ;) ) dann merkt man dass wahrscheinlich im mittel mehrere hundert wenn nicht tausend operationen in der Zeit ausgeführt werden könnten wo die CPU nur damit beschäftigt ist, die Aufgaben zu wechseln. Wenn wir nun viele Threads/Prozesse auf einem Kern liegen haben, verbringt er einige Mikrosekunden nur mit Contextwechseln. Und eine Mikrosekunde ist für eine moderne CPU eine Ewigkeit.
 
Zuletzt bearbeitet:
Ich meine, eigentlich ist es ja schon Wahnsinn wie viele Assemblerbefehle für einen Funktionsprolog nötig sein können um den Stackframe entsprechend einzurichten und alle Parameter dort abzulegen etc.
Naja, das Einrichten geht doch relativ fix: ;D
Code:
	push rbp
	mov rbp, rsp
	sub rsp, xxx
	...
	mov rsp, rbp
	pop rbp

Maximal 5 Instruktionen um den Stackframe zu reservieren und freizugeben. Uu auch weniger mit zB Instruktionen wie enter/leave. Das Aufbereiten von Funktionsparametern vor dem Aufruf und die Initialisierung lokaler Variablen im Stackframe kann dann natürlich deutlich aufwändiger werden.


Wenn man nun zugrunde legt wie wenige Takte eine durchschnittliche Rechenoperation kostet (Gruffi, du kannst doch das Assemblerhandbuch auswendig, gib mal ein paar Zahlen ;) ) ...
Wer's nachschauen will, steht zB bei AMD alles im Software Optimization Guide.


Zum Thema Context Switching, ich bin mir nicht sicher, ob man da CPUs mit GPUs vergleichen sollte. GPUs sind doch deutlich anders aufgebaut. Ich bin eh etwas skeptisch, ob das bei GPUs sonderlich viel bringt. Solche hoch parallelen Prozessoren werden heutzutage doch oft verwendet, um eine Aufgabe partitioniert zu verarbeiten, und nicht um zeitgleich verschiedene Aufgaben zu verarbeiten. Trotzdem nett, wenn Context Switching hardwareseitig auch bei GPUs unterstützt wird. Auch im Hinblick auf die Vollständigkeit der HSA Spezifikation. Ob es in der Praxis was bringt, wird sich aber erst zeigen müssen.
 
Kann man dann 2 Spiele gleichzeitig spielen? ;)
Dürfte die Sache doch für den Programmierer erheblich vereinfachen, wenn die GPU Context Switching beherrscht. Dürfte GPGPU Berechnungen doch erst Salon fähig machen, da man sich nicht drum kümmern muß, ob ein anderer Process aktuell die GPU blockiert.
 
Genau da dürfte es am interessantesten sein, wenn sich unter die Grafikberechnungen noch der eine oder andere computeshader drunter mischt für physik o.ä. Und für heterogeneours queueing (schlimm zu schreiben... ) dürftes auch einfacher sein.
Im Vollausbau, und sollte der Tag jemals kommen dass wir Voll-HSA-Anwendungen sehen, könnte es ja theoretisch auch passieren dass grafikberechnungen, sofern nicht allzu komplex, im "topf" der CPU landen und dann durch die SSE oder AVX-Einheiten gelutscht werden, wenn da grade mehr "Luft" sein sollte als auf der GPU.
Effektiv beschreibt HSA doch nichts anderes als eine weitere Abstraktion vom jeweiligen Computing-Device, also salopp gesagt, mir als high-level-Programmierer kanns schnuppe sein auf welcher HW das Ganze am Ende rennt, das beeinflusst meinen Code höchstens sekundär.
Aus Sicht der Runtime ist es dahingeehnd Praktisch wenn die einzelnen computing devices ähnliche "Fähigkeiten" haben - nicht im Sinne von wie schnell geht was sondern "ist das überhaupt möglich" - und in der CPU-Welt ist Context-Switching und Preemtion inzwischen ein alter Hut. Je mehr die GPUs auch zu solchen Dingen fähig werden, desto mehr verschwimmt die Grenze zwischen CPU und GPU - bzw. desto einfacher lassen sie sich integrieren. Sowohl in HW als auch im Softwarestack.

@gruffi 5 instruktionen für parameterlose Funktionen - und das für so etwas "einfaches" wie eine subroutine. Zumal das glaubich auch nur bei x86 geht, weil man den stack mit offset adressieren kann, also so sachen wie mov eax, ebx+12
Strenggenommen ist die Datenstruktur damit kein LIFO mehr, also kein "echter" Stack, aber im Endeffekt ist es ja ohnehin nur (linearer) Speicher.
Bei "breiten" Funktionssignaturen wirds da bestimmt lustig mit den ganzen Parametern. IMHO ein Grund warum in .Net beispielsweise Eventfunktionen meistens nur 2 parameter bekommen, sender und eventargs, wobei letztere zahlreiche unterklassen hat für spezielle events und als "container" für die ganzen event-spezifischen werte dient die man sonst als Funktionsparameter bräuchte.
 
@gruffi 5 instruktionen für parameterlose Funktionen - und das für so etwas "einfaches" wie eine subroutine
Das Einrichten des Stackframes schaut bei Funktionen mit Parametern genauso aus. Der Zugriff auf den Stackframe (lokale Variablen) erfolgt dann über [rbp-...] und auf Parameter, welche im Stackframe der übergeordneten Funktion liegen, über [rbp+...].

Zumal das glaubich auch nur bei x86 geht, weil man den stack mit offset adressieren kann, also so sachen wie mov eax, ebx+12
Das eigentliche Stackregister in x86 ist sp bzw esp/rsp (stack pointer), nicht bp bzw ebp/rbp. Sofern man nicht weiss, was man tut, sollte man daher an sp auch nicht rumpfuschen, weil das von jedem push/pop/call etc geändert wird. Und das funktioniert bei anderen ISAs auch nicht anders. Vielleicht mag die Adressierung je nach ISA etwas unterschiedlich ausschauen. Das Prinzip bleibt aber gleich.

Strenggenommen ist die Datenstruktur damit kein LIFO mehr, also kein "echter" Stack
Nicht ganz. Man bedient sich hier nur eines "Tricks", indem ein zweites Register genutzt wird. Der eigentliche Stackzeiger kann von Instruktion zu Instruktion unterschiedlich sein. Je nachdem, ob gerade was auf den Stack gepusht oder vom Stack gepopt wurde. Daher ist es auch ein echter Stack. Um lokale Variablen oder Parameter aber immer mit der gleichen Adressierung ansprechen zu können, kommt ein zweites freies Register ins Spiel, bp (base pointer). Theoretisch könnte man jedes andere Register auch nutzen. Man nutzt aber bp, weil das extra dafür gedacht ist. Dieses Register wird dann einmal bei der Initialisierung des Stackframes gesetzt und ist für die restliche Lebensdauer des jeweiligen Stackframes konstant.

Bei "breiten" Funktionssignaturen wirds da bestimmt lustig mit den ganzen Parametern.
Das geschieht aber wie gesagt ausserhalb der Funktion bzw vor dem Funktionsaufruf im Stackframe der übergeordneten Funktion. Und normalerweise werden dann eh meist nur primitive Daten by-value und komplexe Daten by-reference (Zeiger) übergeben. Das pushen selbst ist also relativ wenig Aufwand. Aufwändig wird es erst, wenn die Daten vorher noch berechnet oder in anderer Form aufbereitet werden müssen. Man denke zB an Strings:
Code:
foo("Hallo, " + name + ". Was geht ab?")
Der gesamte String muss vorher erst mal in temporärem Speicher (normalerweise Stack oder Heap) aufbereitet werden, um ihn dann der aufgerufenen Funktionen übergeben zu können. Dh jedes einzelne Zeichen muss in entsprechender Reihenfolge in diesen temporären Speicher kopiert werden. Je mehr Zeichen, umso länger dauert es natürlich. Wobei man hier mit Metaprogrammierung (Templates/Generics) einige interessante Sachen anstellen kann, um das zu verhindern. Aber das ist eine andere Geschichte.

IMHO ein Grund warum in .Net beispielsweise Eventfunktionen meistens nur 2 parameter bekommen, sender und eventargs
Naja, mehr brauchen sie ja eigentlich auch nicht. Schaut zB in der WinAPI mit all ihren Fensternachrichten auch nicht viel anderes aus. Dort ist es halt nur etwas allgemeiner gehalten. Neben dem Fenster-Handle hast du noch einen Parameter, welches die Kennung der Nachricht enthält, und zwei Parameter für spezifische Daten, je nach Nachricht.
 
Zuletzt bearbeitet:
Mit "Kein echter Stack" meinte ich auch nur, gerade weil du eben nicht nur oben drauflegen (push) und von oben wegnehmen (pop) kannst, sondern elemente zwischendrin auslesen, über jene Notation mit
oder
ist es kein stack-zugriff mehr im eigentlichen Sinne.
Das gehht eben deswegen weil selbst der Stack eigentlich nur ein linearer Bereich aufeinanderfolgener Speicheradressen ist und du per Se mit der CPU jede bliebige Speicheradresse in dem Bereich deines Prozesses anspringen bzw. auslesen kannst.
Wenn man einen Stack als LIFO-Datenstruktur sieht und in seiner Grundform betrachtet, müsstest du eigetnlich erst die ersten 3 Elemente pder POP oben wegnehmen um zu sehen was an 4. Stelle von oben liegt. Dass du stattdessen einfach ein offset zu einem Pointer addieren kannst und damit Zwischenelemente im Stack manipulieren ist streng genommen kein Stack-artiges Verhalten mehr.

Angesprochen habe ich das Ganze eigentlich nur weil ich erst aufgrund aktueller Lektüre im Detail mit Stackframe etc. und dem ganzen Kram in Berührung gekommen bin.
Für jemanden der Funktionsuafrufe als einfaches name + kommagetrennte parameterliste hinschreiben aus einer Hochsprache kennt ist es erstmal ein bisschen schockierend dass schon bevor der eigentliche Sprung überhaupt stattfindet 5 Assembler-Befehle nötig sind nur um "aufzuräumen", vom zusätzlichen auf den Stack pushen der ganzen Funktionsargumente ganz zu schweigen. Gottseidank inlinen das die Compiler wenn sie können, ansonsten wäre das auslagern eines Codestücks in eine Funktion nur im Hochsprachen-Code ein Gewinn und würde in Assembler nur Kosten verursachen im Vergleich zu einem inline-Anweisungsblock. Will heißen, wenn ich den Code meiner Funktion stattdessen direkt dahin schreibe wo der funktionsaufruf gestanden hat, fallen die 5 Instruktionen für den Stackframe sowie alle weiter folgenden für Funktionsargumente etc weg und ich hab die Werte immernoch dort wo sie das Hauptprogramm sowieso stehen hat.
Dass effektiv am Ende doch alles nur primitive Daten oder Speicheradressen (Pointer) sind, ist schon klar. Wobei man theoretisch z.B. ein Array mit wenigen Elementen direkt auf den Stack schieben könnte anstatt den Pointer dorthin zu speichern. Inwieweit das allerdings sinnvoll ist, mag wieder eine andere Geschichte sein.
 
Du musst es so betrachten, die Elemente, die gepusht und gepopt werden, sind die Stackframes selber. Und die Spitze des Stacks ist dann der aktuelle Stackframe, repräsentiert durch bp. Auf welche Member des aktuellen Stackframes dann zugegriffen wird, hat mit der Natur des Stacks nichts mehr zu tun. Das obliegt dann einzig und allein der Natur des jeweiligen Elements. Also auch der x86 Stack arbeitet wie ein "echter" Stack.

Und selbst wenn einige Instruktionen zum Einrichten des Stackframes notwendig sind, mov/add/sub/push/pop sind alles low cycler. Die brauchen bei modernen Prozessoren idR nicht mehr als einen Takt. Das ist also wirklich nicht der Rede wert. Und kurze Routinen, wie du schon sagst, sollten smarte Compiler sowieso inlinen.

Dass effektiv am Ende doch alles nur primitive Daten oder Speicheradressen (Pointer) sind, ist schon klar. Wobei man theoretisch z.B. ein Array mit wenigen Elementen direkt auf den Stack schieben könnte anstatt den Pointer dorthin zu speichern. Inwieweit das allerdings sinnvoll ist, mag wieder eine andere Geschichte sein.
Nicht nur theoretisch. Ich empfehle eigentlich, jeden Container, der nur eine bekannte und begrenzte Anzahl an Elementen aufnehmen kann oder soll, direkt auf dem Stack speichern zu lassen. Damit entfällt das Reservieren und Freigeben von dynamischem Speicher (Heap), was deutlich aufwändiger sein kann. Allerdings erlaubt nicht jede Programmiersprache so viele Freiheiten.
 
und sollte der Tag jemals kommen dass wir Voll-HSA-Anwendungen sehen, könnte es ja theoretisch auch passieren dass grafikberechnungen, sofern nicht allzu komplex, im "topf" der CPU landen und dann durch die SSE oder AVX-Einheiten gelutscht werden, wenn da grade mehr "Luft" sein sollte als auf der GPU.

Es könnte auch dank HSA kommen, dass FP, SSE und AVX von der GPU ausgeführt werden.
 
Es könnte auch dank HSA kommen, dass FP, SSE und AVX von der GPU ausgeführt werden.

Möglich wäre das sicher, aber nicht unbedingt effizient. Ich meine, damit etwas per HSA ausgeführt werden kann, muss es durch den finalizer und damit für den jeweiligen chip kompiliert werden. Und den overhead hast du immer. Der wird sich bei einer Handvoll SSE Instruktionen kaum lohnen.
HSA ist doch in der Hinsicht nicht unähnlich zu VMs wie CLR oder JVM, nur dass das Target, für welches binärcode erzeugt wird eben unterschiedlich sein kann. Und ob jemals einer ein Betriebssystem in HSA schreibt, sodass vom ROM ein Minimal-OS startet und dann den HSA Compiler anschmeißt bezweifel ich irgendwie. HSA wird ein relativ einfacher Weg mehrere Rechenknechte unter einen Hut zu kriegen. Aber eine CPU die den Finalizer ausführt wird es immer brauchen. Gerade wegen der dynamischen Natur, dass eben unter Umständen Code für einen x86 prozessor, einen Grafikchip Marke X und noch meinetwegen eine Beschleunigerkarte mit CELL Prozessor auf dem selben Systrm benötigt wird.

---------- Beitrag hinzugefügt um 22:54 ---------- Vorheriger Beitrag um 22:48 ----------

Nicht nur theoretisch. Ich empfehle eigentlich, jeden Container, der nur eine bekannte und begrenzte Anzahl an Elementen aufnehmen kann oder soll, direkt auf dem Stack speichern zu lassen. Damit entfällt das Reservieren und Freigeben von dynamischem Speicher (Heap), was deutlich aufwändiger sein kann. Allerdings erlaubt nicht jede Programmiersprache so viele Freiheiten.

Bei kleinen Sachen ok, aber bei einem Array, womöglich mehrdimensional mit tausenden Feldern... Nunja... Mach das noch in einem rekursiven Funktionsaufruf und du bist auf dem Besten Weg zum Stack overflow...
Und so wie ich das sehe müssten die Arrayelemente doch einzeln auf den stack gepusht oder gemoved werden oder?
 
Bei kleinen Sachen ok, aber bei einem Array, womöglich mehrdimensional mit tausenden Feldern...
Ich sagte doch, "jeden Container, der nur eine bekannte und begrenzte Anzahl an Elementen aufnehmen kann oder soll". Für alles darüber hinaus ist dynamischen Speicher natürlich vorzuziehen.

Mach das noch in einem rekursiven Funktionsaufruf und du bist auf dem Besten Weg zum Stack overflow...
Rekursive Funktionsaufrufe sollte man sich eh abgewöhnen, wenn man guten Code schreiben will. Es gibt eigentlich für alles eine iterative Alternative. Rekursionen sind eher was für Metaprogrammierung. ;)
 
Intel 5150: ~1900ns/process context switch, ~1700ns/thread context switch
Intel E5440: ~1300ns/process context switch, ~1100ns/thread context switch
Intel E5520: ~1400ns/process context switch, ~1300ns/thread context switch
Intel X5550: ~1300ns/process context switch, ~1100ns/thread context switch
Intel L5630: ~1600ns/process context switch, ~1400ns/thread context switch
Intel E5-2620: ~1600ns/process context switch, ~1300ns/thread context siwtch
So ganz grob also 2 µs pro Prozessswitch. Wenn ein Kern dann z.B. jede Milisekunden für eine ganz kleine Berechnung eines anderen Prozess aus seiner Arbeit heraus gerissen wird, wären das 4 µs pro ms bzw. 0,4 % der theoretischen Rechenleistung. Würde sagen, im Desktop- und Workstationbereich ist das dann doch eher vernachlässigbar. Bei Servern mit vielen Kommunikationsanfragen kann das anders aussehen.
 
So ganz grob also 2 µs pro Prozessswitch. Wenn ein Kern dann z.B. jede Milisekunden für eine ganz kleine Berechnung eines anderen Prozess aus seiner Arbeit heraus gerissen wird, wären das 4 µs pro ms bzw. 0,4 % der theoretischen Rechenleistung. Würde sagen, im Desktop- und Workstationbereich ist das dann doch eher vernachlässigbar. Bei Servern mit vielen Kommunikationsanfragen kann das anders aussehen.

Bei nur zwei Prozessen hast du recht. - mach 20 draus und du hast schon 4% eingebüßt. - und das war in dem Benchmark noch ziemlich simpel geprüft ohne die Effekte von cache pollution etc. einzubeziehen. Wenn du jetzt 100 Prozesse bzw. Threads, die sind zwar etwas billiger aber immernoch recht kostspielig hast, wird das auf einem einzelnen Kern schon interessant. Soviel zu denjenigen die glauben ein einzelner Singlecore mit brutaler IPC wäre ein Allheilmittel. :]
 
Im Vollausbau, und sollte der Tag jemals kommen dass wir Voll-HSA-Anwendungen sehen, könnte es ja theoretisch auch passieren dass grafikberechnungen, sofern nicht allzu komplex, im "topf" der CPU landen und dann durch die SSE oder AVX-Einheiten gelutscht werden, wenn da grade mehr "Luft" sein sollte als auf der GPU.

Es könnte auch dank HSA kommen, dass FP, SSE und AVX von der GPU ausgeführt werden.

Hier ist eine sehr detaillierte Präsentation zu den HSA Möglichkeiten:
http://www.slideshare.net/hsafounda...chitecture-and-algorithms-tutorial#&skip=true

isca-2014-heterogeneous-system-architecture-hsa-architecture-and-algorithms-tutorial-287-1024.jpgisca-2014-heterogeneous-system-architecture-hsa-architecture-and-algorithms-tutorial-288-1024.jpgisca-2014-heterogeneous-system-architecture-hsa-architecture-and-algorithms-tutorial-289-1024.jpgisca-2014-heterogeneous-system-architecture-hsa-architecture-and-algorithms-tutorial-290-1024.jpgisca-2014-heterogeneous-system-architecture-hsa-architecture-and-algorithms-tutorial-291-1024.jpg
 
Zurück
Oben Unten