Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

41

19.06.2014, 16:46

Zitat

Wann genau kann es jetzt zu Endian Problemen kommen?

Wenn du Datenwörter die aus mehreren Bytes bestehen aufbauend auf ihreren inneren Aufbau speicherst.
Immer bei einer Konvertierung im Still von "reinterpret_cast<char const*>" von einem anderen Typen, dessen Aufbau undefiniert ist(wie alle integrale Typen aus mehreren Bytes), sollten die Alarmglocken schrillen, wenn die Daten einen konsistenten Aufbau haben sollen.
Zum Beispiel bei deinem Code. Ganz neben bei, sollten auch die Shifts ziemlich in die Hose gehen, schließlich sind es 8 Bit Chars die du da um mehr als 8 Bits shiftest. Das Ergebnis davon ist schon undefiniert und auch falsch. Und dann hast du eben die kritische Schreiboperation, die Endianabhänig ist. Wenn du den String einfach direkt so schreiben würdest wie er ist, würde das Problem nicht auftreten, weil String Char-Arrays sind die einen eindeutigen Aufbau besitzen. file.write("snip", 4);

Zitat

wobei bei ihm nur die mittleren beiden Bytes vertauscht waren. Ist das immer so?

Ich verstehe nicht ganz, was du meinst.

Er verwendet aber scheinbar auch Endian variierende Dateien. Einfacher wäre es meiner Meinung nach ein eindeutiges Dateilayout zu definieren, bei dem der Endian immer gleich ist.

42

19.06.2014, 17:54

Ich hab jetzt mittlerweile mithilfe von Bitoperationen eine byteswap Methode gebastelt.
Das ist das Ergebnis (Integer 4 Byte, Dezimal: 300):

Ist korrekt, oder?

Ach und wollte nochmal kurz was zu der Methode von Helmut sagen, also dieser hier:

C-/C++-Quelltext

1
2
3
4
5
6
7
void WriteInt(std::ostream& out, uint32_t Int)
{
    WriteByte(out, Int >> 0);
    WriteByte(out, Int >> 8);
    WriteByte(out, Int >> 16);
    WriteByte(out, Int >> 24);
}

Ich hab vorhin mal einen Hexeditor angeschmissen und einen Integer in einer Binär Datei analysiert.
Dieser war völlig identisch mit dem über reinterpret_cast in einem Rutsch abspeicherten Integer,
es passiert nämlich anscheinend wirklich nichts anderes als das die Bytes Byte für Byte angereiht werden.

Schlussfolgernd, kommt man also doch nicht drum rum, von Little Endian auf Big Endian zu konvertieren und andersrum, wenn es nötig ist,
oder ist was an meiner Logik verkehrt?

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

43

19.06.2014, 18:01

Einfacher, benachteiligt allerdings eine spezielle Maschine bezüglich der Ladezeit. Da wähle ich lieber den "fairen" Weg. Und nein, entweder habe ich mich vertippt, oder er sich verlesen. 'oxbc' (Das ist ein char32_t, kein String) für die native Endianness, 'cbxo' für Byteswaps. Aller andere Quark wäre Middle Endian und Konsorten, die ich nicht unterstütze.

Zitat

Das Ergebnis davon ist schon undefiniert und auch falsch.
Ich mag mich irren, aber würde der Compiler nicht an der Stelle aus Narrowing-Gefahr mal kurz rauf zu int casten? Ist ja kein Rollen, wo das 'nen Unterschied bewirken würde. Zumindest musste ich bei meiner VM an einer Stelle den Compiler dazu zwingen, val+1 mit val als uint16_t nicht hoch zu casten.
(Gewollter over- und underflow, um an Branches zu sparen. Und nein, mehr als 16bit sind nicht drin.)


Edit:
Y U NO Hex-Print? D:
Aber ja, ist richtig.

Helmut's Methode speichert auch - wie erwähnt - im Little Endian -Format ab, was die Endianness deines Prozessors ist.
(Du hast einen x86, da bin ich mir sicher.)

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Evrey« (19.06.2014, 18:06)


Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

44

19.06.2014, 18:05

Zitat von »Jack«

Ist korrekt, oder?

Könnte sein, mit Sicherheit kann man das natürlich nur mit den Code sagen. Es kann theoretisch natürlich immer sein, dass eine Methode nur in einigen Fällen richtig ist.

Zitat von »Jack«

Schlussfolgernd, kommt man also doch nicht drum rum, von Little Endian auf Big Endian zu konvertieren und andersrum, wenn es nötig ist,
oder ist was an meiner Logik verkehrt?

Du machst einen ähnlichen Fehler wie zuletzt. Weil bei dir im Moment unter den Bedienungen der Plattform das selbe Ergebnis herauskommt, heißt noch lange nicht, dass dies generell der Fall ist. Die Methode von Helmut ist also trotzdem einfach generell "richtig" und "reinterpret_cast" kann unterschiedliche Ergebnisse liefern. Aus dem Beobachtungen bei dir kannst du einfach nicht auf die allgemeine Korrektheit schließen.

EDIT:

Zitat von »Evrey«

spezielle Maschine bezüglich der Ladezeit

Ein Byteswap sollte auf einem modernen Prozessor wesentlich schneller laufen als eine zusätzliche Verzweigung. Außerdem wird aus Performancesicht etwas ganz anderes auf Konto schlagen.

Zitat von »Evrey«

Ich mag mich irren, aber würde der Compiler nicht an der Stelle aus Narrowing-Gefahr mal kurz rauf zu int casten?

Ok, ja er tut es. "operator >>" konvertiert auf "int". Das finde ich zwar höchst unlogisch aber du hast recht. Es geht, ich würde es trotzdem eher nicht so schreiben, sieht für mich jedenfalls wirklich sehr falsch aus.

Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von »Spiele Programmierer« (19.06.2014, 18:18)


Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

45

20.06.2014, 00:50

Zitat

Ein Byteswap sollte auf einem modernen Prozessor wesentlich schneller laufen als eine zusätzliche Verzweigung.
Ich verstehe nicht ganz, worauf du hinaus willst. Nehmen wir an, dass eine OnyxVM auf einem PowerPC Bytecode speichert. Dann wäre diese Datei im Big Endian -Format. Liest die PPC-VM jetzt diesen Bytecode ein, sind keine Byteswaps von Nöten. Jetzt lade ich die selbe Datei auf einer x86-VM, die nunmal Little Endian ist. Jetzt muss jedes Häppchen der Datei mit Byteswap geladen werden. Erstellt die x86-VM nun eine Kopie, ist diese Kopie im Little Endian -Format und kann dann wieder ohne Byteswap geladen werden.
(Tatsächlich könnte man der VM erlauben, Bytecode-Dateien in der nativen Endianness zu überschreiben.)

Es findet ergo nur eine einzige Verzweigung statt, und zwar nach dem Lesen des Kontrollworts 'oxbc'. Das ist ein einziger bedingter Sprung gegen verflixt viele 32bit-Instruktionen und Meta-Daten an denen man sich Byteswaps sparen könnte.

Irgendwo müssen die Byteswaps eh' landen. Wenn ich sage, dass Bytecode immer im Big Endian -Format gespeichert werden soll, spart ein PowerPC oder SPARC ganz schön was an Arbeit. Der x86 hingegen muss dann nicht nur beim Laden byteswappen, sondern auch noch beim Speichern. Damit ist er gleich doppelt gelackmeiert! Und gegen diesen Mangel an Fairness gehe ich mit variabler Endianness in den Bytecode-Dateien vor.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

46

20.06.2014, 01:09

Zitat

sind keine Byteswaps von Nöten.

Das ist richtig. Allerdings muss ja bei jedem gelesenen Wert entschiedenen Werden "Byteswap oder nicht". Und das kostet, außer wenn man das irgendwie ganz abgefahren macht, mehr oder weniger bis zu einen Verzweigung ob der Byteswap ausgeführt werden soll oder nicht. Byteswaps sind aber billig. Ein paar Bitshifts oder sogar auf dem x86/x64 direkt als Maschinenbefehl kostet höchstens ein paar Zyklen. Die Verzweigung hingegen, die verursacht ab und zu einen Pipelinezusammenbruch, belastet die Sprungvorhersage und verursacht eine Diskontinuität im Programmcode. Das wiegt in der Regel wesentlich schwerer als ein paar Bytes rumschieben. Außerdem wiegen andere Dinge wesentlich schwerer. Gerade die Streams von C++ sollen teilweise lahm-wie-sau implementiert sein. Und IO ist in der Regel eh nicht das Schnellste. Ein paar Byteswaps und wahrscheinlich sogar Verweigungen würden da nicht ins Gewicht fallen. Jetzt bestünde natürlich der spezielle Fall, dass du das alles bereits über die Maße optimiert hast, die vielen Sprünge durch duplizieren des gesamten Maschinencode nicht mehr notwendig sind und die IO-Funktionen auf präzise optimiert sind und die Daten gebuffert geladen werden etc. Dann hypothetisch hättest du besonders auf den genannten exotischen Platformen damit einen kleinen Vorteil. Aber das kann ich im Moment nicht wirklich glauben.

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

47

20.06.2014, 01:37

Zitat

Allerdings muss ja bei jedem gelesenen Wert entschiedenen Werden "Byteswap oder nicht".
Nein. Wie gesagt: Ich lese die ersten vier Bytes der Datei. Ist das Prüfwort 'oxbc', wird die gesamte Datei in einem Rutsch ohne Byteswaps gelesen. Andernfalls mit. Es gibt einfach zwei verschiedene Funktionen, die den Bytecode laden. Da wird nicht Wort für Wort geprüft. Das würde doch die arme BPU töten. Und das bisschen mehr an Bytes im Code-Segment juckt mich nicht.

Zitat

Gerade die Streams von C++ sollen teilweise lahm-wie-sau implementiert sein.
Ich habe noch nie Performance-Messungen mit den C++-Streams durchgeführt. Es kann sein, dass das "lahm-wie-sau" ausschließlich bei operator<< und operator>> besteht, die ziemlich knorke aber eben auch ziemlich teure Formatierungs-Arbeit leisten und read und write hingegen recht flott sind. Ich weiß es nicht. Habe eh' noch gewisse IO-Experimente vor, die dann entscheiden, welche Strategie/Lib ich wählen werde. Wahrscheinlich Marke Eigenbau, weil ich das eh' länger mal ausprobieren wollte.
(Meine VM ist so 'ne kleine Spiel-Wiese, auf der ich Zeugs austeste. Zuletzt z.B. die 2MiB/4MiB Arenen.)

Dass diese kleine Verzögerung nicht sooo ins Gewicht fallen wird ist klar. Es ist einfach eine kleine Macke von mir. Ich habe nichtmals selbst eine Big Endian -Maschine hier, außer der Playsi3, mit der ich von der Design-Entscheidung profitieren würde. Und wenn ich nur auf x86 Bytecode speichere und lade, besteht die einzige Leistungseinbuße darin, das Kontrollwort zu prüfen, was recht flott dazu führen wird, dass die BPU die richtigen Instruktionen in die Pipeline schmeißt.
(Mal davon abgesehen, dass die Verzweigung des Byteswaps als "unwahrscheinlich" markiert ist.)

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

48

20.06.2014, 06:49

Den Ansatz von Evrey finde ich interessant. Ich sehe es zwar nicht als notwendig oder beim Lesen von Dateien als tatsächlich Performance-relevant an (sonst sollte man z.B. verschlüsselte oder gepackte Daten gleich komplett wegwerfen, die sind ja noch viel "schlimmer" [OggVorbis, MP3, Zip, Png, Jpg, ...]), aber interessant.
Ich sehe zwar nicht, wie man es ohne massive Code-Duplikation (Laden aller komplexer Datentypen muss ja einmal für big und einmal für little implementiert sein) oder ohne Polymorphie (die schlimmer ist als jedes if) sinnvoll lösen wollte, aber da ich es eh nicht für Performance-relevant halte, würde ich sicher letzteres einsetzen.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

49

20.06.2014, 12:24

Man könnte es als Templates machen. Wenn ich es mir genau überlege, wäre das Konzept sogar mit meiner IO Lib möglich, dass ist nämlich ein Template unter anderem mit Endianness als Parameter. Sinn sehe ich zwar weiterhin nicht viel ... aber jeden das seine. Die schlechte Performance hängt übrigens soweit ich weiß nicht an dem "operator >>".

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

50

20.06.2014, 12:31

Nö, die liegt an der Seek-Time der Festplatte und dem Bus.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige