Sinn und Zweck von int


  • Mod

    Eisflamme schrieb:

    Ich verstehe. Und Alignment sorgt z.B. innerhalb von Strukturen für genau das, was Du da beschreibst, oder?

    Solche Details der Mikrooptimierung überlasse ich dem Compiler. Ich weiß bloß, dass es solche Mechanismen gibt, aber ich weiß nicht genug um qualifizierte Aussagen über die Details zu machen.
    Da Geschwindigkeitsoptimierungen aber ein gängiger Schwanzvergleich zwischen verschiedenen Compilern sind, darfst du davon ausgehen, dass solche Details ziemlich optimal umgesetzt werden. Wenn es da etwas zu holen gäbe, würde es auch gemacht.
    Ich kann ja mal einen Benchmark machen, während ich meinen Kaffee trinke. Ich melde mich gleich wieder. Aber wenn die 64-Bits gewinnen, würde mich das sehr überraschen.



  • Mich hat nur der Hintergrund von Alignment interessiert, da es für mich gerade logisch erscheint, dass auf Registerbreiten optimiert wird. Alignment hat ja wohl oft 4 Byte, was 32 Bit entspricht. Kompiliert man für 64 Bit, wäre ein Alignment auf 8 Byte wohl logisch. 🙂 Benchmarking klingt gut!


  • Mod

    So, ich habe da was zusammengehackt. Leider erstmal nur für POSIX, da ich gerade keine Zeit hatte, boost::ptime zu lernen:

    #include <iostream>
    #include <ctime>
    
    using namespace std;
    
    struct Foo
    {
      int a;
      int b;
    };
    
    struct Bar
    {
      long a;
      long b;
    };
    
    template<typename T> void waste_time(decltype(T::a) how_much)
    {
      typedef decltype(T::a) type;
    
      timespec start, end;
      clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
    
      T t={12345, 54321};
      for(type i=0; i<how_much; ++i)
        {
          t.a+=t.b;
          t.b*=t.b;
          t.a-=t.b;
          t.b^=t.a;
          t.a<<=4;
          t.b-=t.a;
          t.a>>=3;
          t.b+=7;
          t.a+=t.b;
        }
      clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
    
      cout<<"Anti-Optimierungsausgabe: "<<t.a<<' '<<t.b<<'\n';
      long long nano=(end.tv_sec - start.tv_sec) * 1000000000;
      nano+=(end.tv_nsec - start.tv_nsec);
    
      cout<<"Benötigte Nanosekunden: "<<nano<<"\n\n";
    
    }
    
    int main()
    {
      cout << "Sizeof int: " << sizeof(int) <<'\n'
           << "Sizeof long: " << sizeof(long) <<'\n'
           << "Sizeof Foo: " << sizeof(Foo) <<'\n'
           << "Sizeof Bar: " << sizeof(Bar) <<'\n';
    
      cout<<"\nEine dem Compiler unbekannte Zahl: ";
      int anti_optimizer_input;
      cin>>anti_optimizer_input;
    
      cout<<"Mit Foo:\n";
      waste_time<Foo>(anti_optimizer_input);
      cout<<"Mit Bar:\n";
      waste_time<Bar>(anti_optimizer_input);
      cout<<"Nochmal mit Foo:\n";
      waste_time<Foo>(anti_optimizer_input);
      cout<<"Nochmal mit Bar:\n";
      waste_time<Bar>(anti_optimizer_input);
      cout<<"10x mit Foo:\n";
      waste_time<Foo>(10*anti_optimizer_input);
      cout<<"10x mit Bar:\n";
      waste_time<Bar>(10*anti_optimizer_input);
      cout<<"10x mit Foo:\n";
      waste_time<Foo>(10*anti_optimizer_input);
      cout<<"10x mit Bar:\n";
      waste_time<Bar>(10*anti_optimizer_input);
    }
    

    Die Ergebnisse sind konsistent über viele Läufe auf meinem Core 2:

    [b]seppj@irgendwo{~/src}> [/b]g++ test.cc -Wall -std=c++0x -O3 -Wextra -xHost -lrt
    [b]seppj@irgendwo{~/src}>[/b] ./a.out
    Sizeof int: 4
    Sizeof long: 8
    Sizeof Foo: 8
    Sizeof Bar: 16
    
    Eine dem Compiler unbekannte Zahl: 10000000
    Mit Foo:
    Anti-Optimierungsausgabe: -1332682501 -1166096675
    Benötigte Nanosekunden: 50591275
    
    Mit Bar:
    Anti-Optimierungsausgabe: -1527253877453168389 -1903512469154904355
    Benötigte Nanosekunden: 55345337
    
    Nochmal mit Foo:
    Anti-Optimierungsausgabe: -1332682501 -1166096675
    Benötigte Nanosekunden: 40838216
    
    Nochmal mit Bar:
    Anti-Optimierungsausgabe: -1527253877453168389 -1903512469154904355
    Benötigte Nanosekunden: 55354001
    
    10x mit Foo:
    Anti-Optimierungsausgabe: -1332682501 -1166096675
    Benötigte Nanosekunden: 408339301
    
    10x mit Bar:
    Anti-Optimierungsausgabe: -1527253877453168389 -1903512469154904355
    Benötigte Nanosekunden: 553385774
    
    10x mit Foo:
    Anti-Optimierungsausgabe: -1332682501 -1166096675
    Benötigte Nanosekunden: 408321611
    
    10x mit Bar:
    Anti-Optimierungsausgabe: -1527253877453168389 -1903512469154904355
    Benötigte Nanosekunden: 553377433
    

    Klarer Sieg für int. Wer Spaß hat, kann ja noch ein paar Dummy-chars in die structs einsetzen, um das Alignment und Padding zu testen.


  • Mod

    Und jetzt habe ich das gleiche Programm auf einem i7 ausgeführt und nun sind int und long gleich schnell.

    Und wenn ich 32-Bit Code erzeuge gehen die 64-Bit Zahlen total ein und sind 4x so langsam wie die 32 Bit Zahlen.

    Short verkackt gegen beide und ist ungefähr 1.8 mal langsamer (auf dem i7)

    Schlussfolgerungen:
    32 Bit Systeme: 32 Bit Zahlen liegen ganz klar vorne. Offensichtlich.
    64 Bit Systeme: Kommt auf das System an, aber 32 Bit Zahlen sind jedenfalls nicht langsamer. Die 64 Bit Zahlen bieten mehr berechnete Stellen pro Zeit (auch offensichtlich), aber wenn man diese Stellen gar nicht benötigt, fährt man im Zweifelsfall mit 32 Bit Zahlen besser
    Beide: Short nur wenn man wirklich Speicherplatz sparen muss.



  • ~john schrieb:

    volkard schrieb:

    Sowohl auf 32 Bit, als auch auf 64 Bit ist int 4 Byte lang und long long 8 Byte. Aber long ist entweder 4 oder 8 Byte lang.
    ...
    Wobei das nicht gesetzlich garantiert ist, sonder nur üblich.

    Nein, das ist falsch.

    Warum widersprichst Du hier? Konnte Deiner Erklärung nicht folgen, wo Du nur aus der Norm rausholst, daß es nicht garantiert ist.



  • SeppJ schrieb:

    Klarer Sieg für int. Wer Spaß hat, kann ja noch ein paar Dummy-chars in die structs einsetzen, um das Alignment und Padding zu testen.

    Kommt das nur mir so vor oder sind die Laufzeiten und Ergebnisse für "mit Foo" und "10x mit Foo" sehr nahe beieinander? Ich hab' zwar keine Ahnung, was deine Funktion eigentlich berechnet, aber zumindest bei der Laufzeit hätte ich da größere Abweichungen erwartet.


  • Mod

    CStoll schrieb:

    SeppJ schrieb:

    Klarer Sieg für int. Wer Spaß hat, kann ja noch ein paar Dummy-chars in die structs einsetzen, um das Alignment und Padding zu testen.

    Kommt das nur mir so vor oder sind die Laufzeiten und Ergebnisse für "mit Foo" und "10x mit Foo" sehr nahe beieinander? Ich hab' zwar keine Ahnung, was deine Funktion eigentlich berechnet, aber zumindest bei der Laufzeit hätte ich da größere Abweichungen erwartet.

    😕 Hast du eine Stelle nicht mitgezählt? Es ist doch fast perfekt ein Faktor 10! Eine bessere Bestatigung kann ich mir gar nicht vorstellen, dass die Funktion das macht was sie soll.

    edit: Oder meinst du, dass du größere Schwnakungen erwartest? Wieso? Ich messe CPU-Zeit, nicht Realzeit.



  • SeppJ schrieb:

    CStoll schrieb:

    SeppJ schrieb:

    Klarer Sieg für int. Wer Spaß hat, kann ja noch ein paar Dummy-chars in die structs einsetzen, um das Alignment und Padding zu testen.

    Kommt das nur mir so vor oder sind die Laufzeiten und Ergebnisse für "mit Foo" und "10x mit Foo" sehr nahe beieinander? Ich hab' zwar keine Ahnung, was deine Funktion eigentlich berechnet, aber zumindest bei der Laufzeit hätte ich da größere Abweichungen erwartet.

    😕 Hast du eine Stelle nicht mitgezählt? Es ist doch fast perfekt ein Faktor 10! Eine bessere Bestatigung kann ich mir gar nicht vorstellen, dass die Funktion das macht was sie soll.

    Sorry, mein Fehler *brille putzen geht*. Eventuell solltest du die Ausgabe besser formatieren, so daß man den Unterschied leichter erkennen kann 😉

    (PS: Hat die Berechnung eigentlich einen praktischen Nutzen oder geht es nur darum, dort möglichst viele verschiedenen Ganzzahl-Operationen zu kombinieren?)



  • volkard schrieb:

    Warum widersprichst Du hier?

    Die Behauptung "Sowohl auf 32 Bit, als auch auf 64 Bit ist int 4 Byte lang und long long 8 Byte." ist falsch. Die ISO Norm garantiert nur, daß ein int mindestens 16Bit groß ist. Mehr wird dazu in der ISO Norm nicht garantiert. Von einem 32Bit oder 64Bit Modus ist in der ISO Norm keine Rede. Aber selbst wenn man von den üblichen Betriebssystemen ausgeht stimmt Deine Aussage auch nicht, denn es gibt dort ILP64 Modi.


  • Mod

    CStoll schrieb:

    (PS: Hat die Berechnung eigentlich einen praktischen Nutzen oder geht es nur darum, dort möglichst viele verschiedenen Ganzzahl-Operationen zu kombinieren?)

    Letzteres. Wobei ich Division vermieden habe, denn wenn man das ohne Plan macht, dann tendieren die Ergebnisse dazu, gegen 0 zu konvergieren. Zumindest ist nun festgestellt, dass bei modernen CPUs 64 und 32 Bit Register ungefähr gleich schnell sind und bei älteren Modellen die 32 Bit Teile schneller sein können (eventuell emulieren die modernen CPUs einfach die 32 Bit, aber das ist Spekulation).

    Ich habe übrigens mal den Effekt von Packing und Alignment ausprobiert. Zumindest bei diesem Programm mach Misalignment keinen Unterschied. Das liegt aber wohl daran, dass alles in einem kleinen Loop stattfindet, so dass die Werte sowieso die ganze Zeit in Prozessorregistern stehen.



  • Übrigens, es gibt Systeme (C64x DSPs von Texas Instruments oder Blackfin DSPs von Analog Devices), auf denen ein sizeof(long) gleich 5 ist. Ja, 40 Bit breit. Normale Register sind 32 Bit breit und sizeof(int) ist 4 und es gibt 40 Bit breite Register für "multiply accumulate" o.ä. Operationen.



  • int ist kurz zu schreiben. Das ist ein Grund ihn zu verwenden.



  • SeppJ schrieb:

    Zumindest ist nun festgestellt, dass bei modernen CPUs 64 und 32 Bit Register ungefähr gleich schnell sind und bei älteren Modellen die 32 Bit Teile schneller sein können (eventuell emulieren die modernen CPUs einfach die 32 Bit, aber das ist Spekulation).

    Das liegt einfach daran, dass "die älteren Teile" keine 64 Bit Befehle haben, und daher 64 Bit Operationen aus 32 Bit Operationen zusammenstückeln müssen. Dass das langsamer ist sollte jetzt keinen wundern.
    Auch ein i7 ist mit 32 Bit Code bei 64 Bit Berechnungen viel langsamer als bei 32 Bit Berechnungen. Oder als mit 64 Bit Code bei 64 Bit Berechnungen.
    Ist jetzt irgendwie keine Überraschung.

    Oder hab' ich dich falsch verstanden?


  • Mod

    hustbaer schrieb:

    Oder hab' ich dich falsch verstanden?

    Ich glaube ja. Das Ergebnis war:

    • Alter 64 Bit Prozessor (Core 2 Duo):

    • 32 Bit Befehlssatz:

    • 32-Bit Berechnungen sind viel schneller als 64-Bit, ca. Faktor 4

    • 64 Bit Befehlssatz:

    • 32-Bit Berechnungen sind deutlich schneller als 64-Bit, ca. Faktor 2

    • Moderner 64-Bit Prozessor (i7):

    • 32 Bit Befehlssatz:

    • 32-Bit Berechnungen sind viel schneller als 64-Bit, ca. Faktor 4

    • 64 Bit Befehlssatz:

    • 32-Bit Berechnungen sind ziemlich genau gleich schnell wie 64-Bit, ca. Faktor 1

    Diese Faktoren sind ziemlich exakt. Da ich mir nicht vorstellen kann, dass so Zahlen wie 1, 2 und 4 zufällig Zustande kommen, nehme ich mal an, dass da mehr dahinter steckt.

    Der Faktor 4 ist irgendwie intuitiv. Man hat in einem Fall 2 Werte mit 32-Bit, und im anderen 2x2 Werte zu je 32-Bit. Wenn die Rechnungen linear in der Größe der beiden Eingaben sind (es sind ziemlich triviale Operationen, daher ist dies gut möglich), hat man einen Faktor 4.

    Aber irgendetwas haben sie gemacht, dass entweder der 32-Bit Teil des Core 2 sehr viel besser ist als der 64-Bit Teil, oder der 32-Bit Teil des i7 genauso "schlecht" ist wie der 64-Bit Teil. Ich tippen auf letzteres. Möglicherweise ist beim i7 alles 64-Bit und der Core 2 hatte noch eine reine 32-Bit Untereinheit. Aber ohne die Optimierungsrichtlinien von Intel durchzulesen (worauf ich keine Lust habe), wird man das wohl nicht sicher rausfinden.



  • Also ich hatte das Gefühl:
    Addition und Subtraktion geht in allen Größen in einem Takt.
    Multiplikation dauert um so länger, je breiter die Operanden, aber ist nicht wild. Hatte man einen AMD64, hatte man mit #define FASTMUL das gesagt, nämlich daß der Prozessor die integer-Miltiplikation in 6 oder 8 Takten durchrotzen kann. Intel schneckte da ab. Ich gehe davon aus, daß Intel inzwischen aufgeholt hat.
    Bei der Integer-Division ist noch kein Land in Sicht. Größere Operanden brauchen deutlich mehr Zeit.


Anmelden zum Antworten