Motorola zu Intel konvertieren ....
-
RHBaum schrieb:
im schnitt max 3800 Nachrichten = 2+1+8 byte pro sekunde ... der zeitstempel wird beim empfang generiert.
3800 Botschaften pro Sekunde wären aber schon recht viel für 500k. Ihr fahrt da hoffentlich nur mit einem Sender, oder?
-
jenz schrieb:
@net, fleißig. sieht man mal, das unsere ersten varianten nicht so besonders waren. die ausgerollte ist halt am besten.
ja, und die moral von der geschicht? schleifen haben in zeitkritischen codes nix zu suchen :p
-
net schrieb:
jenz schrieb:
@net, fleißig. sieht man mal, das unsere ersten varianten nicht so besonders waren. die ausgerollte ist halt am besten.
ja, und die moral von der geschicht? schleifen haben in zeitkritischen codes nix zu suchen :p
Ich würde eher sagen: die Moral von der Geschicht: Wer was misst misst Mist.
Ich habe den Code auch mal ein wenig getestet und bin zu dem Ergebnis gekommen, daß es verschiedene Faktoren gibt, die das Ergebnis wesentlich beeinflussen können.
Zunächst habe ich den Code auf Linux portiert, da ich hier kein Windows habe. Aber das sollte auf der Qualität der Aussagen keinen Einfluß haben. Auch habe ich die REPETITIONS ein wenig erhöht, um durch eine längere Laufzeit eine geringere Varianz zu erreichen.
Wer einen großen Einfluß auf die Performance hat, ist natürlich der Optimierer. Und wenn der die read_bit-Funktion sieht, da lacht er doch nur darüber und optimiert sie weg. Und schon ist der ganze Test futsch :p .
Also erst mal die read_bit-Funktion in eine separate Compilationseinheit und der Datensalat sieht schon anders aus. Und dann mit unterschiedlichen Optimierungsstufen testen. Unter Linux habe ich -O2 und -O3 getestet.
Mit -O2 kam heraus, daß die "table"- und "loopless"-Varianten in etwa gleichauf sind. "mask" ist weit abgeschlagen. Mit -O3 dreht ändert sich das Bild. Da geben sich die Varianten nicht viel. Da ist die "loopless"-Variante eher ein wenig langsamer als die anderen. Also hier meine Ergebnisse:
Mit -O2:
mask: 22445 table: 10314 loopless: 9053 ------------------------------ mask: 21564 table: 9924 loopless: 10185 ------------------------------ mask: 21574 table: 10838 loopless: 9654 ------------------------------ mask: 21998 table: 11176 loopless: 8998 ------------------------------
Und mit -O3:
mask: 8213 table: 8611 loopless: 8869 ------------------------------ mask: 8734 table: 7332 loopless: 9590 ------------------------------ mask: 7781 table: 7943 loopless: 8882 ------------------------------ mask: 7802 table: 8169 loopless: 8890 ------------------------------
Das gilt natürlich nur für den einen bestimmten Prozessor mit einem bestimmten Compiler unter einem bestimmten Betriebssystem. Bei solchen manuellen loop-unrolling spielt auch die Größe des Caches im Prozessor eine Rolle, wenn man das weiter treibt.
Tntnet
-
cool, noch ein messer.
da interessiert mich aber mal der generierte assemblercode. wundert mich, dass es sich nur durch die optimierung noch ändert.
leider habe ich zur zeit keinen zugriff auf meine werkzeuge, bin ein bisschen in urlaub und habe nicht den eigenen rechner dabei...
read_bit() sozusagen auszulagern finde ich ich gut. clever.
aber was man auch noch berücksichtigen müsste irgendwie, wäre dass threadwechsel stattfinden, im normalfall. und dann auch gleich die luts aus den caches gefegt werden. höchstwahrscheinlich, oder nicht?
still interested,
jenz
-
Die Zeitscheiben im Betriebssystem sind schon groß genug, daß das mit der Cache-entleerung sicherlich keine allzu grosse Rolle spielt. Bis zum nächsten Prozeßwechsel schafft das Programm sicherlich einige Millionen Iterationen. Da macht es nicht viel aus, wenn der Cache bei der 1. direkt aus dem RAM lesen muß. Und selbst das gilt nur, wenn es etwas anderes zu tun gibt und das andere den Cache vollständig austauscht. Auf einem ansonsten unbelasteten Rechner sollte das also gar nichts ausmachen.
Tntnet
-
ich würde aber davon ausgehen, das zwischen wenigen emfpangenen bits ein prozesswechsel stattfindet.
einfach weil die einleseroutine dem bs mitteilt, "ich habe nichts mehr zu tun, der nächste bitte" wäre ja nur sinnvoll.
und dann haben wir den wechsel öfter, als die zeitscheiben sagen, die daten liegen ja auch nicht immer so an, dass es passend zu den scheiben ist.aber da wird es dann wahrscheinlich doch ein wenig zu unsicher, oder?
-
tntnet schrieb:
Wer einen großen Einfluß auf die Performance hat, ist natürlich der Optimierer. Und wenn der die read_bit-Funktion sieht, da lacht er doch nur darüber und optimiert sie weg. Und schon ist der ganze Test futsch :p .
hey, solche tests darfste natürlich nicht mit optimierungen machen sonst haste so gut wie keine aussage über die geschwindigkeit. wenn der optimierer die 'read_bit' als eine feste '1' erkennt braucht er auch die abfragen nicht mehr zu machen. wenn er dann noch die schleife beseitigt, hat man plötzlich einen konstanten wert 'result = 0xffffffff' und wundert sich, warum die lut-version 1000-fach schneller ist als alles andere
einflüsse wie thread-switches und interrupts kann man ja etwas umgehen, indem man dem tester möglichst hohe prio gibt, die tests mehrfach ausführt und dann den mittelwert nimmt...
das mit der schleife: es ist natürlich gut wenn ein compiler bei 'optimize for speed' schleifen selbständig beseitigt aber dummerweise hat man keinen einfluss darauf, nach welchen kriterien er's macht. manch einer löst nur kleine schleifen auf, um nicht zu viel code zu erzeugen. da kann man also ruhig mal nachhelfen wenn man ganz sicher gehen will. je weniger sich im schleifeninnern tut, desto mehr fällt die ausführung der schleife selber in's gewicht. also wenn's echt um mikrosekunden geht: schleifen, funktionsaufrufe, maschinenfremde datentypen (wie longs auf 16 bittern) und globale/statische variablen vermeiden etc. ...und immer schön das disassembly betrachten...;)
-
ich find' das ja irgendwie ganz niedlich wie steil ihr alle geht,
aber welches problem diskutiert ihr hier ueberhaupt?
keine api der welt uebertraegt einzelne bits...solche tests darfste natürlich nicht mit optimierungen machen
sonst haste so gut wie keine aussage über die geschwindigkeitund ohne optimierung wohl auch nicht...
-
hellihjb schrieb:
keine api der welt uebertraegt einzelne bits...
finde ich ja auch merkwürdig. irgend so'n can-adapter für windoofs soll das sein. aber die ganzen treiber unter win sind paketorientiert. da ist mit einzelbits nix zu machen....
hellihjb schrieb:
solche tests darfste natürlich nicht mit optimierungen machen
sonst haste so gut wie keine aussage über die geschwindigkeitund ohne optimierung wohl auch nicht...
doch, klar, zum messen immer ohne. optimierungen kann man zum schluss einschalten und wenn man's gut gemacht hat, dann hat der compiler nix mehr zu optimieren
-
Ja ich weiß, daß wir schon off-topic sind, aber es ist doch interessant, über die Verfahren im Benchmark zu debattieren.
Einen Benchmark ohne Optimierung durchzuführen ist, als ob man die Höchstgeschwindigkeit eines Autos mit angezogener Handbremse ermitteln möchte. Ich wollte doch genau ausdrücken, daß die Optimierung unsere Tests ad absurdum führt. Wenn wir einen Benchmark durchführen, dann doch um zu erfahren, wie sich unser Programm in der Realität macht. Und in der Realität compiliere ich mit eingeschalteter Optimierung. Unsere handgemachten Optimierungen können da sogar kontraproduktiv sein.
Gute Optimierer können sogar Cache-Größen berücksichtigen und wissen, wann sich ein loop-unrolling lohnt und wann nicht. Der macht das besser, als wir.
net schrieb:
hey, solche tests darfste natürlich nicht mit optimierungen machen sonst haste so gut wie keine aussage über die geschwindigkeit. wenn der optimierer die 'read_bit' als eine feste '1' erkennt braucht er auch die abfragen nicht mehr zu machen. wenn er dann noch die schleife beseitigt, hat man plötzlich einen konstanten wert 'result = 0xffffffff' und wundert sich, warum die lut-version 1000-fach schneller ist als alles andere
Wenn Du meine Erläuterung aufmerksam gelesen hast, hast Du gelesen, daß ich die read_bit-Funktion in eine separate Compilationseinheit verschoben habe, um sie aus dem Blickfeld des Optimierers zu entfernen. Der Optimierer sieht nur den aktuellen Source-code. Nicht das, was später der Linker noch dazu linkt. Es gibt auch Optimierer, die solche Intermoduloptimierungen durchführen können, aber nicht, wenn ich dem nicht alles zur Verfügung stelle, was er dazu braucht. Zu dem Zeitpunkt, wo ich das Testmodul compiliere, weiß der Optimierer gar nicht, was da für read_bit gelinkt wird.
jenz schrieb:
ich würde aber davon ausgehen, das zwischen wenigen emfpangenen bits ein prozesswechsel stattfindet.
einfach weil die einleseroutine dem bs mitteilt, "ich habe nichts mehr zu tun, der nächste bitte" wäre ja nur sinnvoll.
und dann haben wir den wechsel öfter, als die zeitscheiben sagen, die daten liegen ja auch nicht immer so an, dass es passend zu den scheiben ist.aber da wird es dann wahrscheinlich doch ein wenig zu unsicher, oder?
Wenn wir jetzt davon ausgehen, daß die Empfangsroutine eine gewisse signifikante Zeit braucht, dann können wir die ganze Optimierung vergessen. Wenn die Empfangsroutine so lange braucht, daß nach wenigen Bits die Zeitscheibe aufgebraucht ist, dann ist das egal, wie lange diese Bitpfrimlerei braucht. Die ist dann immer signifikant schneller und verbraucht höchtens wenige Prozent der Gesamtzeit.
Tntnet
-
tntnet schrieb:
Einen Benchmark ohne Optimierung durchzuführen ist, als ob man die Höchstgeschwindigkeit eines Autos mit angezogener Handbremse ermitteln möchte. Ich wollte doch genau ausdrücken, daß die Optimierung unsere Tests ad absurdum führt. Wenn wir einen Benchmark durchführen, dann doch um zu erfahren, wie sich unser Programm in der Realität macht. Und in der Realität compiliere ich mit eingeschalteter Optimierung. Unsere handgemachten Optimierungen können da sogar kontraproduktiv sein.
Gute Optimierer können sogar Cache-Größen berücksichtigen und wissen, wann sich ein loop-unrolling lohnt und wann nicht. Der macht das besser, als wir.
net schrieb:
hey, solche tests darfste natürlich nicht mit optimierungen machen sonst haste so gut wie keine aussage über die geschwindigkeit. wenn der optimierer die 'read_bit' als eine feste '1' erkennt braucht er auch die abfragen nicht mehr zu machen. wenn er dann noch die schleife beseitigt, hat man plötzlich einen konstanten wert 'result = 0xffffffff' und wundert sich, warum die lut-version 1000-fach schneller ist als alles andere
Wenn Du meine Erläuterung aufmerksam gelesen hast, hast Du gelesen, daß ich die read_bit-Funktion in eine separate Compilationseinheit verschoben habe, um sie aus dem Blickfeld des Optimierers zu entfernen. Der Optimierer sieht nur den aktuellen Source-code. Nicht das, was später der Linker noch dazu linkt. Es gibt auch Optimierer, die solche Intermoduloptimierungen durchführen können, aber nicht, wenn ich dem nicht alles zur Verfügung stelle, was er dazu braucht. Zu dem Zeitpunkt, wo ich das Testmodul compiliere, weiß der Optimierer gar nicht, was da für read_bit gelinkt wird.
es ist ja auch richtig so, dass du die 'read_bit()' in eine extra datei verschoben hast. aber wenn man optimieren zulässt, dann kann man die algos lut/mask/loopless nicht geschwindigkeitsmässig vergleichen, weil der optimierer sie massiv verändern kann. dazu hätteste den erzeugte maschinencode vergleichen müssen und solche sachen wie etwa wieviele instructions der prozessor sich parallel in den cache zieht, eventuell beherrscht der out-of-order execution usw. stell dir nur mal einen prozessor vor, der gewisse sachen parallel abarbeiten kann. der hätte dann z.b. die schleife mit der mask in einem takt abgehandelt, hehe, und ein schön optimierender compiler hätt's im mungerecht dargereicht...
-
@tntnet okay, dass ein messen nicht viel sinn macht, wenn dauernd threadwechsel passieren und das einlesen nur sehr wenig zeit in anspruch nimmt ist klar. aber ich wollte hat darauf hinaus, dass die lut immer extra wieder in den cache gepackt wird...egal
@net warum darf denn der optimierer nicht eingreifen. verstehe ich irgendwie nicht. es geht ja schon um die endgeschwindigkeit. optmieren finde ich da sogar notwendig. gerade bei nichtoptimierten debugfähigem code wird ja die maskensache stark benachteiligt.
jenz
-
jenz schrieb:
@net warum darf denn der optimierer nicht eingreifen. verstehe ich irgendwie nicht. es geht ja schon um die endgeschwindigkeit.
weil wir dann unsere 3 verfahren (lut/mask/loopless) einfach nicht vergleichen können. mann sollte z.b. von einer einfachen von-neumann architektur ausgehen, bei der jeder befehl einzeln geholt und abgearbeitet wird. und alles nacheinander. jede optimierung o.ä. verfäscht das ergebnis. manche compiler z.b. erkennen gewisse codeteile (pattern mathing schimpft sich das, glaub' ich) und ersetzen die komplett gegen was schnelleres. das geht also so nicht...
-
net schrieb:
jenz schrieb:
@net warum darf denn der optimierer nicht eingreifen. verstehe ich irgendwie nicht. es geht ja schon um die endgeschwindigkeit.
weil wir dann unsere 3 verfahren (lut/mask/loopless) einfach nicht vergleichen können. mann sollte z.b. von einer einfachen von-neumann architektur ausgehen, bei der jeder befehl einzeln geholt und abgearbeitet wird. und alles nacheinander. jede optimierung o.ä. verfäscht das ergebnis. manche compiler z.b. erkennen gewisse codeteile (pattern mathing schimpft sich das, glaub' ich) und ersetzen die komplett gegen was schnelleres. das geht also so nicht...
um von einem verfälschten ergebnis sprechen zu können, muss erst einaml feststehen, was gemessen werden soll. die algorithmische komplexität aller vorgestellten verfahren ist gleich, es ist also keiner dieser algorithmen prinzipiell schneller als ein anderer, es kommt auf die konkrete anwendung an. und wenn es letzlich auf die performance einer konkreten anwendung in einer konkreten umgebung geht, hat es keinen sinn, den algorithmus unter bedingungen (sprich ohne optimierungen) zu testen, unter denen dieser am ende nicht eingesetzt werden wird. zudem ist die transformation von sourcecode in ausführbaren code ja kein eindeutiger prozess, jeder ausführbare code, der zum selben ergebnis führt, ist gleichermaßen gültig. dem code eines nicht optimierten builds größere signifikanz einzuräumen als dem eines optimieren builds erscheint mir recht sinnfrei.
-
camper schrieb:
dem code eines nicht optimierten builds größere signifikanz einzuräumen als dem eines optimieren builds erscheint mir recht sinnfrei.
du bist auch so ein spielverderber
man braucht doch gewisse rahmenbedingungen um solche vergleiche anstellen zu können (eine 'referenzplattform', die darf nix am ursprünglichen code ändern), sonst vergleicht man äpfel mit birnen. auch wenn alle algos von der komplexität gleich sind, das 'wie' macht den geschwindigkeitsunterschied aus...
-
apropos "recht sinnfrei":
for (s=0; s<32; s++) { if (read_bit() == 1) result |= table[s]; }
mit
table[s]= (1<<s);