# Ggf. Performanceproblem durch Senden von vielen kleinen Buffern



## Kr0e (16. Jan 2010)

Nabend Leute,

vorab erstmal sollte man erwähnen, dass ich mit Mina arbeite.

Meine Problem:

Meine Programmlogig baut darauf auf, dass ich ByteBuffer[] bzw IoBuffer[] übertragen kann. Ich könnte natürlich nun beim
eigentlich senden ByteBuffer[] zu einem IoBuffer zusammenfassen und dann später wieder in ein ByteBuffer[] Array.
Aber irgendwie fand ich das ziemlich unschön und ansich auch unnötig. 

Zur Zeit mache ich es so:

Wenn ein ByteBuffer[] Array reinkommt, synchronisiere ich auf die IoSession und pack dann die ByteBuffers aus dem Array der Reihe nach einzeln in die Warteschlange von Mina. 

Das Synchronisieren mach ich, damit ein zweiter Thread nicht gleichzeitig anfängt, ein ByteBuffer[] Array (Also die einzelnen Buffers) der Reihe nach zu schreiben.

Ich könnte mir nämlich vorstellen das könnte problematisch werden, wenn auf der anderen Seite dann die ByteBuffers in der falschen Reihenfolge eintreffen.

Funktionieren tut das alles ganz gut, vorallem weil nun das Programm mehr Flexibilität hat. Angenommen ich will ein ByteBuffer schicken wo bereits Datan drinstehen aus einer Datei, aber das Protokoll muss jetzt noch einen Header erstellen für die Gegenseite, dann würde es ja wenig Sinn machen, einen weiteren IoBuffer zu erstellen wo dann der HEader und die Daten aus dem anderen Buffer reinkommen. 

Meine Frage:

Ist das so in Ordnung ? Macht es Sinn lieber kleine Packete der Reihe nach zu schreiben, anstatt ein großes ?
Oder verursacht das verschicken von vielen kleinen Packeten mehr Overhead als das ständige umkopieren von Buffern ?

Gruß, Chris


----------



## Empire Phoenix (17. Jan 2010)

Generell jede Packet verursacht etwas overhead, also sollte man wenn die Packet sehr klein sind und viele gleichzeitig anfallen versuchen diese combinieren zu können. 
Generell kann jedoch jedes System nur eine bestimmte Größe von Packeten maximal verarbeiten (zb. um die 8kb bei udp und windows), sogesehen kann man nur bis zu einem bestimmten level combinieren.
Praktisch solange keine wirklichen engpässe beobachtet werden (Profiler,Netzwerktests) und alles funktioniert kann man es so lassen, weil wenn du nur verhältnismäßig(zum Netzwerkanschluss) wenige Daten schickst ist der unterschied absolut minimal.


----------



## Kr0e (17. Jan 2010)

Alles klar. Dann lass ich das erstmal so. Sollten Engpässe eintreten, werde ich diese Lösung nehmen, dass bei vielen kleinen lieber ein großes erstellt wird.


----------



## tuxedo (18. Jan 2010)

Was man dabei auch beachten sollte: Implementiert man ein solches "kombinieren" von Paketen, hat das unweigerlich einfluss auf die Paketlaufzeit. 

Nebenbei: Ein solches verhalten hast du bereits von Haus aus mit dem Nagle-Algorithmus. Das nochmal auf oberster Schicht zu implementieren ist wohl nicht in allen Fällen sinnvoll und sollte gut überlegt sein (oder "ausprobiert sein").

Gruß
Alex


----------



## Empire Phoenix (18. Jan 2010)

Wen er UDP benutzt hatters nicht ^^ wurde ja niergentwo gesagt ob es tCP oder UDP betrifft. (Abgesehen davon das zumindest wenn es auf reaktionszeiten ankommt der NagleAlgo übers I-Net besser aus bleiben sollte)


----------



## Kr0e (18. Jan 2010)

Ok, also kein manuelles Zusammenfassen der Pakete BEVOR es überhaupt in diese oberste Schicht gelangt.
Ich benutze übrigens TCP, hatte ich vergessen zu schreiben. Ich denke ich werde dann den Naglealgorithmus für diese Aufgabe
aktivieren. Reaktionszeiten sind nicht wichtig, da es um Dateitransfer geht. Für zeitempfindlichere Daten kann ich diesen ja dann wieder ausschalten. Der Grund, warum ich diese Sturktur (Dass ein Datensatz aus mehreren kleinen Buffern besteht) benötige, ist der dass ich z.b. beim Lesen aus einer Datei noch keinen Header in dem selben Buffer habe. Sprich ich müsste dann beim verschicken des Datensatzes den HEader und den Buffer mit den Daten aus der Datei erneut umkopieren. Dieser Gedanke gefällt mir einfach nicht.

Danke für eure Tipps soweit.

Gruß Chris


----------



## tuxedo (18. Jan 2010)

Kr0e hat gesagt.:


> Ich denke ich werde dann den Naglealgorithmus für diese Aufgabe
> aktivieren.



Der ist per default an. Wenn, dann kannst du ihn ausschalten.

- Alex


----------



## FArt (18. Jan 2010)

Nur eine Zwischenfrage:
dieser Thread entgleitet gerade ein wenig in (in meinen Augen) seltsame Protokollimplementierungen.

Gibt es denn wirklich ein Performanceproblem oder reden wir hier über ungelegte Eier? Wenn es eines gibt, wie äußert sich das Problem? Geht es um die allgemeine Netzlast, Antwortzeiten, ...???


----------



## Kr0e (18. Jan 2010)

Was genau meisnt du mit "seltsame Protokolimplementierung" ?


----------



## FArt (18. Jan 2010)

Kr0e hat gesagt.:


> Was genau meisnt du mit "seltsame Protokolimplementierung" ?



In der Regel muss man sich mit dem Nagle-Alg. nicht befassen... das ist wie mit der Umwandlung von Kohlenhydrate in Fett... 

Interessanter ist: was ist das Problem?


----------



## Kr0e (18. Jan 2010)

Die ursprüngliche Frage war eigentlich, was schlimmer ist:

Kurz vor dem Senden die ganzen Buffer zusammenzufassen zu einem großen und den zu senden oder einfach die Packete einzeln auf den Socket schreiben.

Ich habe mein Programm aufgeteilt in zwei Schichten:

- Die Transportschicht, die durch Interfaces austauschbar ist. (Mina ist jetzt nur eine spezielle Impl. für den Transport)

- Die Anwendungsschicht.

Meine Programmlogig innerhalb der Anwendungsschicht verlangt die Möglichkeit, mehrere Buffer zu schicken. Genauer gesagt: eine Liste von Buffern.

Meiner Ansicht nach, ist der dadurch ggf. entstehende Overhead Sache des Protokols und den versuche ich grad auf Transportschicht zu minimieren.


----------



## FArt (18. Jan 2010)

> - Die Transportschicht, die durch Interfaces austauschbar ist. (Mina ist jetzt nur eine spezielle Impl. für den Transport)


Interessanter Ansatz, zumal das u.A. eine Aufgabe von MINA ist...



> Meiner Ansicht nach, ist der dadurch ggf. entstehende Overhead Sache des Protokols und den versuche ich grad auf Transportschicht zu minimieren.


Jetzt kommt der eigentliche Knackpunkt: es gibt gar kein Problem (?)... es ist eher hypotetisch... 
Und da kommen die drei Grundsätze es Optimierens zum tragen: optimiere spät, optimiere spät, optimiere spät... und nur dort wo es wirklich (nachweisbar) Probleme gibt, wo mit vertretbarem Aufwand ein merklicher Vorteil erreicht werden kann und man (wenn man das Problem kennt) genau dem Problem passend begegnen kann...   just my 2 cents

EDIT
www.hans-eric.com  Optimize Late, Benchmark Early


----------



## Kr0e (18. Jan 2010)

Nagut, Mina ist zwar dafür gemacht, eine austauschbare Transportschicht zu haben, allerdings ist es eben immernoch ein Netzwerkframework, soweit ich weiß. (Belehre mich )

Mein Ansatz ist noch etwas grobmaschiger.

Bei mir kann die Transportschicht z.B. auch lokal sein. Sprich komplett ohne Sockets.
Ihr fragt euch bestimmt "Warum zum Teufel brauch man sowas ?!".

Die Idee ist folgende:

Habe zuerst RMI genutzt. Danach hab ich ne eigene Impl. von RMI mit Hilfe non Mina gemacht.
Dann kamen die Tests. Ich habe dann schließlich gesehen, dass es Unsinn ist, für einen einfach "Startbefehl" z.B. einen entfernten Methodenaufruf durchzuführen. Im Endeffekt brauchte ich dann für einen simplen Startbefehl mit vlt. 1-2 Parameters um die 100 byte die durch Serialisieren entstanden sind. Nun habe ich einen komplett neuen Ansatz angefangen:

Ich benutze nun keine Java Serialisierung mehr, sondern eine eigene stark auf Optimierung der Datenmenge basierende eigene Serialisierungsmethode.

Damit dieses Vorhaben klappt, muss jede Klasse, die dieses System nutzen will, direkt davon abgeleitet werden.
Die Gründe dafür sind, dass ich übers Netzwerk nicht die Klassennahmen austauschen will. Der andere Computer weiß, wenn er z.b. eine 1 bekommt, welches Object ich meine... 

Und damit ich quasi für Objekte nun nicht 2 Implentierungen machen muss, leite ich jede Klasse von meinem Projekt nun davon ab.
Sollten da Klassen dabei sein, die sowohl Netzwerktechnisch als auch lokal funktionieren sollen, so brauch ich nur die Transportschicht von Lan auf Lokal stellen. Dann gibt garkeine Sockets mehr...

Das klappt nun alles auch wunderbar.

Das war die einzige Frage die jetzt noch offen war.

Gruß,

Chris

EDIT:
Ich habe die Datenmenge dadurch in den meisten Fällen um die Hälfte reduziert. Natürlich ist dafür die Auswahl der Parameter bei einer Methode stark begrenzt. Aber durch diese Arbeitsweise brauche ich nun uach kein weiteres Protokol mehr und spare zusätzlich Overhead. Ich dachte auch erst "Ochja 100 byte oder 50"... Hab dann ein paar Tests gemacht. Angenommen ich schicke über eine Internetleitung mit 50kByte/s Daten zu iwem. Wenn ich nun oft auf einem anderen Socket wenig Daten hin und her schicken muss und das simultan mit mehreren Clienten klappen soll, so ist es schon spürbar ob man 150 oder 70 bytes jedes Mal schickt.


----------



## FArt (18. Jan 2010)

> allerdings ist es eben immernoch ein Netzwerkframework, soweit ich weiß. (Belehre mich )


Vorwiegend, jein ;-)
Es gibt auch einen "in memory piping", aber auch das vollführt (sinnvollerweise!) das marshalling/unmarshalling mit dem entsprechenden Overhead.

In Mina kannst du aber auch eigene "Protokolle" anbinden... das kann auch eines sein, das nur so tut als ob und die Refernzen durch die Gegend schiebt, mit minimalem Overhead ... vielleicht hat das sogar schon mal jemand gemacht, das habe ich jetzt nicht mehr gesucht.


----------



## tuxedo (21. Jan 2010)

Kr0e hat gesagt.:


> Habe zuerst RMI genutzt. Danach hab ich ne eigene Impl. von RMI mit Hilfe non Mina gemacht.
> Dann kamen die Tests. Ich habe dann schließlich gesehen, dass es Unsinn ist, für einen einfach "Startbefehl" z.B. einen entfernten Methodenaufruf durchzuführen. Im Endeffekt brauchte ich dann für einen simplen Startbefehl mit vlt. 1-2 Parameters um die 100 byte die durch Serialisieren entstanden sind.



Oh, dann hast du aber schlecht serialisiert. Für einen entfernten Methodenaufruf brauch ich:

1 byte für die Message-Art
4 byte für eine ID
4 byte für die länge des bodys der Nachricht
2+x bytes für den Remote-Obbjekt-Namen
8 bytes für die Methoden-ID
x bytes für die Argumente

Der Overhead ist also 19 bytes + die Länge des Namens der Methode + Die Argumente der Methode
Wählt man jetzt keinen großen Objekthierarchien sondern benutzt einfachere Typen wie primitiove oder Strings, oder Arrays aus dessen, hat man fast keinen weiteren Overhead sondern fast nur die Rohdaten. 15byte pro Paket kann ich sogar einen Modem noch abverlangen.



> Ich habe die Datenmenge dadurch in den meisten Fällen um die Hälfte reduziert. Natürlich ist dafür die Auswahl der Parameter bei einer Methode stark begrenzt. Aber durch diese Arbeitsweise brauche ich nun uach kein weiteres Protokol mehr und spare zusätzlich Overhead. Ich dachte auch erst "Ochja 100 byte oder 50"... Hab dann ein paar Tests gemacht. Angenommen ich schicke über eine Internetleitung mit 50kByte/s Daten zu iwem. Wenn ich nun oft auf einem anderen Socket wenig Daten hin und her schicken muss und das simultan mit mehreren Clienten klappen soll, so ist es schon spürbar ob man 150 oder 70 bytes jedes Mal schickt.



Das kann ich nicht nachvollziehen. Selbst wenn man nur DSL1000 oder sogar DSL384 (wie in in ländlichen Gegenden teilw. der Fall ist), hat kommt bei 100byte Protokoll-Overhead nix uns schwitzen. 
Nehmen wir DSL100 als Beispiel. Wenn ich mich recht erinnere sind hier etwa 16kybte/sek das Upload-Limit. Da passt dein Overhead allein (rund) 160mal rein. Gehen wir mal davon aus das Verhältnis aus Rohdaten und Overhead liegt bei 2:1... Dann kannst du Sekundenweise ein Paket mit etwa 10.000byte schicken. 

Ich hab nen SIMON Server (auf nem 100mbit Root-Server) und rund 50 CLients (an unterschiedlichen DSL-Anschlüssen) laufen. Pro Client gehen im Schnitt etwa 10 Pakete pro Sekunde raus und kommen wieder zurück. Weder Client noch Server lassen sich dadurch beeindrucken. 

Ein anderen Projekt (meine Diplomarbeit) hat auf Sockets (ohne NIO) etwa 5kbyte/sek Daten produziert. Quelle war ein Audiostream. Normalerweise macht man sowas über UDP. Aber selbst mit TCP (und abgeschaltetem Nagle) ließ sich das realisieren ohne dass die Latenz unerträglich groß wurde. Mit Teamspeak konnte das Ding mithalten. Nich tnur im lokalen Netzwerk, auch einmal quer durch halb Deutschland gings noch gut was Bandbreite und Latenz betraf. 

Naja, aber worauf ich eigentlich hinaus will:

a) ich kann nciht glauben dass man auf so niedrigem Level um jedes Byte feilschen muss. Schon gar nicht mit heutigen Internetanschlüssen
b) irgendwo hast du nen anderen Fehler drin der dir die Supper versalzt. An der Bandbreite kanns offenbar nicht liegen

- Alex


----------



## Kr0e (21. Jan 2010)

Hm, mag sein, dass ich das noch nicht perfekt hatte... Das was du da gerade angesprochen hast... "Die gesamte Objekthierachie" 
ICh glaub das war ein Fehler...  Hab mir deinen Code mal angeschaut.. Du machst das etwas geschickter. Ich serialisiere jetzt garnicht mehr. Hab jetzt 2 abstrakte Methoden, einmal "Object readFromBuffer(ByteBuffer buffer, byte dataFormat)" und "writeToBuffer(ByteBuffer buffer, byte format)". Hab das ganze so gemacht, dass auch n-dimensionale Arrays geschrieben werden können. Der Algorithmus muss nur wissen, wie man eine einzige Variable eines Types schreibt, der Rest geschieht im Hintergrund.

So spar ich mir die Java Serialisierung komplett. Bzw, die könnte ich sogar trotzdem noch einbauen "als eigener Datentyp".
Dieses System ist jetzt auf jeden Fall performanter als das alte. Darauf kams mir an. Auch so Sachen wie, dass eine Methode die void zurück gibt, auf ne Antwort vom Server wartet, war für mein Vorhaben nicht ideal.

Ich will damit auch Dateien übertragen und da wäre das sehr störend.. Keine Ahnugn wie das RMI macht, ob RMI wartet oder nicht.. 

Btw: Ich brauch nur 2Byte für die Methode.
Aber auf 8 oder 2 kommts nicht an... Ich erstelle ein Array von Methoden am Anfang und sortiere sie. Ich übermittele nur noch die nummer der Methode im Array.

Mein System ist channel-basierend. (RMI war vorerst garnicht eingeplant)
DAs Channelsystem bietet mir erstmal die Möglichkeit für die ÜBermittelung von Daten von Channel A nach Channel B.
Der Austausch geht über einen Provider der je nach Belieben implementiert werden kann. Moment halt Mina.

Auf dem System hab ich jetzt die SAche mit RMI implementiert... Zwar muss ein Objekt nun von meinem Channel abgeleitet werden, aber das ist garnicht schlimm, da ich sowieso vorhatte mein Projekt komplett "transport-orientiert" zu gestalten. 

Gibt noch bei meinem System jetzt ein paar Extras... Zum Beispiel ist mir bei SIMON aufgefallen, du hast ja auch diese "RawDataChannel"-Sache gemacht, dass du die Daten davon beim Encoder nochmal extra in einen IoBuffer kopierst.. Und es dann verschickst...  Hab jetzt praktisch einen ChannelBuffer mit einer bestimmten Größe (Deshalb natürlich etwas eingeschränkt), aber brauche nun keine ständigen Neuerstellungen von IoBuffern. Hab aber ehrlich gesagt auch keine Ahnung ob sich der Aufwand wirklich lohnt, du wirst wahrscheinlich sagen "Nein", aber ich finds irgendwie nicht schön ständig umzukopieren...

Naja ansonsten geb ich dir Recht, lag an der unschönen Serialisierung. Werde vermutlich noch die Möglichkeit bereitstellen, dass
Parameter ohne definitierten Encoder/Decoder einfach per Serialisierung gelesen und geschrieben werden...

Aber so wie ich es hatte war es wirklich nicht mehr schön... LAg an der ungeschickten Serialisierung des gesamten Objektbaums...

Gruß,

Chris


----------



## tuxedo (21. Jan 2010)

Zur Sache mit dem umkopieren von Buffern:

Das lag mir anfangs auch sehr im Magen. Dachte das wäre absolut unnötig und würde auch noch performance fressen. Aber:

1) Erst optimieren wenn Performance Probleme auftreten
2) IoBuffer sind ByteBuffer. 
3) Das Umkopieren von ByteBuffern geht deutlich schneller als das umkopieren von byte[]s. 
4) An manchen Stellen (und da gehört mein RawChannel dazu) wäre die implementierung sehr unschön geworden wenn ich mir das umkopieren hätte ersparen wollen. Und die lesbarkeit des Codes trägt zur wartbarkeit ja unweigerlich bei.
5) Geschwindigkeitseinbußen konnte ich dadurch nicht feststellen. Im 100Mbit Netzwerk hab ich die gleiche Geschwindigkeit erreicht wie andere Anwendungen. Gigabit-Netzwerk steht noch aus. Aber selbst da erwarte ich das gleiche Ergebnis wie mit anderen Anwendungen. Und "localhost only"... Naja, da waren meine vergangenen Tests jenseits von "mit normalem Netzwerk erreichbar". Sehe da keinen bedarf das noch weiter zu optimieren. 

Wozu also der Aufwand?


----------



## Kr0e (21. Jan 2010)

Hm, hatte befürchtet dass das erstellen von neuen ByteBuffern unteranderem auch höhere CPU Lasten fordert...
Ich denke auch, dass die dafür notwendige Zeit minimal ist, aber es gefiel mir einfach nicht^^...

Zu deinem Problem mit der Lesbarkeit... Da weiß ich genau was du meinst... MAn muss ja praktisch schon von Anfang an wissen,
wie groß der ByteBuffer sein soll .. (Inklusive Headergröße, die ja eigentlich auf Anwendungsebene garnicht interessieren sollte...)
Alles in allem ein heikles Thema... Ich denke sowieso, dass Java im Hintergrund stark optimiert, sonst würden manche Dinge garnicht so gut laufen wie sie es tun 

Naja so wie ich es jetzt hab, kann ich wenigstens "ruhig schlafen"... Wenns eins gibt, was ich hasse, dann ist es Unperfektheit 

Gruß,
Chris


----------

