Performance von Array Boundary Checking

EDIT >>> Auf Vorschlag des Verfassers wurde dieser Thread-Teil in einen eigenen Thread mit neuem Titel verschoben <<< EDIT <<<

In einem anderen Forum hatte Landei diesen Thread gestartet, den ich für eine ziemlich gute Idee halte. Da es das andere Forum nun ja nicht mehr gibt möchte ich den Thread hier wiederbeleben. :slight_smile:

Ich fange dann hier auch mal an, und zwar mit einer Performance-Angelegenheit die ich letztens bemerkt habe: Die automatische Überprüfung von Arraygrenzen scheint in Java schnarchlangsam zu sein… Das sollte man wohl besser manuell erledigen.

Aufgefallen ist mir das als ich verschiedene Methoden testete um Arrays zu vergleichen. Der Effekt lässt sich bei diesem Codeschnipsel prima beobachten:

public class ComparatorSpeedTest {

    static byte[] array1, array2;

    public static void main(String[] args) {
        long startTime, endTime;
        array1 = new byte[10000];
        array2 = new byte[10000];
        Arrays.fill(array1, (byte)0);
        Arrays.fill(array2, (byte)0);
        array1[8000] = 1;
        array2[8000] = 2;

        startTime = System.currentTimeMillis();
        for(int i=0; i<1_000_000; i++) {
            compareBytewise1(array1, array2);
        }
        endTime = System.currentTimeMillis();

        System.out.println("time: "+(endTime-startTime));
    }

    public static int compareBytewise1(byte[] a1, byte[] a2) {
        int index = 0;
        byte value1, value2;
        do {
            value1 = a1[index];
            value2 = a2[index];
            index++;
        } while( (value1 == value2) && (index < a1.length) );
        return index;
    }

    public static int compareBytewise2(byte[] a1, byte[] a2) {
        int index = 0;
        byte value1, value2;
        do {
            value1 = a1[index];
            value2 = a2[index];
            index++;
        } while( value1 == value2 );
        return index;
    }
}

Der einzige Unterschied zwischen den beiden Comparator-Methoden ist, das eine die Einhaltung der Arraygrenzen von array1 von sich aus sicherstellt und die andere nicht. Der Ergebnis hat mich schon verblüfft.

Comparator 1, mit manueller Überprüfung: 5,6 Sekunden
Comparator 2, mit automatischer Überprüfung: 14,6 Sekunden

Na, da hat sich die extra Operation im while(…) doch wohl gelohnt!

Das mit der automatischen Überprüfung hab ich irgwendwie garnicht verstanden. Sowas gibt es doch überhaupt nicht. Die zweite Variante führt nur deswegen nicht zu einer ArrayIndexOutOfBoundsException, weil value1 und value2 rechtzeitig verschieden sind und damit die Schleife abbricht. Die Laufzeitunterschiede sind verdächtig. Die gemessenen Werte kommen mir beide insgesamt sehr lang vor. Da würde ich parallel laufende andere Aktionen vermuten, die das Ergebnis massiv verfälschen.

[QUOTE=nillehammer]Die Laufzeitunterschiede sind mir unerklärlich. Die gemessenen Werte kommen mir aber beide insgesamt sehr lang vor.[/QUOTE]Performanz-Messungen in Java sind eine Kunst. Der JIT-Compiler grätscht einem da öfter zwischen die Beine als man vermutet.

bye
TT

Performanz-Messungen in Java sind eine Kunst.[…]

Japp darauf wollte ich hinaus. Ich hätte vielleicht deutlicher schreiben können/sollen: „Ich zweifele die Messergebnisse an.“

Das schwierige ist, dass bei jedem Beitrag hier die Gefahr besteht, dass er ausdiskutiert wird, … z.B. könnte jetzt jemand sagen, dass bei sowas wie

import java.util.Arrays;

public class ComparatorSpeedTest {


    public static void main(String[] args) {
        
        for (int size = 1000; size<=10000; size+=1000)
        {
            byte array1[] = new byte[size];
            byte array2[] = new byte[size];
            Arrays.fill(array1, (byte)0);
            Arrays.fill(array2, (byte)0);
            array1[(int)(size*0.8)] = 1;
            array2[(int)(size*0.8)] = 2;
            
            for (int n=10000; n<=50000; n+=10000)
            {
                runTest1(n, array1, array2);
                runTest2(n, array1, array2);
            }
        }
    }
    
    private static void runTest1(int n, byte array1[], byte array2[])
    {
        long startTime, endTime;
        long sum = 0;
        startTime = System.currentTimeMillis();
        for(int i=0; i<n; i++) {
            sum += compareBytewise1(array1, array2);
        }
        endTime = System.currentTimeMillis();
        System.out.println("Test1 size "+array1.length+" runs "+n+" sum "+sum+" time: "+(endTime-startTime));
    }

    private static void runTest2(int n, byte array1[], byte array2[])
    {
        long startTime, endTime;
        long sum = 0;
        startTime = System.currentTimeMillis();
        for(int i=0; i<n; i++) {
            sum += compareBytewise2(array1, array2);
        }
        endTime = System.currentTimeMillis();
        System.out.println("Test2 size "+array1.length+" runs "+n+" sum "+sum+" time: "+(endTime-startTime));
    }
    
    public static int compareBytewise1(byte[] a1, byte[] a2) {
        int index = 0;
        byte value1, value2;
        do {
            value1 = a1[index];
            value2 = a2[index];
            index++;
        } while( (value1 == value2) && (index < a1.length) );
        return index;
    }

    public static int compareBytewise2(byte[] a1, byte[] a2) {
        int index = 0;
        byte value1, value2;
        do {
            value1 = a1[index];
            value2 = a2[index];
            index++;
        } while( value1 == value2 );
        return index;
    }
}

und einem start mit “-server” der Unterschied nur sehr gering (d.h. kaum messbar) ist, und eher die zweite Variante einen Hauch schneller ist, aber das hängt sicher auch noch von der JVM (1.6er…) und anderen Faktoren ab…

Trotzdem habe ich mir das mal angeschaut:


compareBytewise1:                compareBytewise2
  Code:				   Code:
   0:   iconst_0		    0:   iconst_0
   1:   istore_2		    1:   istore_2
   2:   aload_0			    2:   aload_0
   3:   iload_2			    3:   iload_2
   4:   baload			    4:   baload
   5:   istore_3		    5:   istore_3
   6:   aload_1			    6:   aload_1
   7:   iload_2			    7:   iload_2
   8:   baload			    8:   baload
   9:   istore  4		    9:   istore  4
   11:  iinc    2, 1		    11:  iinc    2, 1
   14:  iload_3			    14:  iload_3
   15:  iload   4		    15:  iload   4
   17:  if_icmpne       26	    17:  if_icmpeq       2
   20:  iload_2			    20:  iload_2
   21:  aload_0			    21:  ireturn
   22:  arraylength
   23:  if_icmplt       2
   26:  iload_2
   27:  ireturn

Man sieht, dass bei der ersten wirklich eine If-Abfrage und die arraylength-instruktion dazukommen, aber das hat eben am Ende doch recht wenig Einfluß.

Im JF-Thread hatte ich u. a. gepostet, dass throw null; erlaubt ist (und eine NPE auslöst), und dass man in der throws-Klausel auch unchecked Exceptions angeben darf (was keinerlei Konsequenzen nach sich zieht, also de facto “Dokumentation” ist).

Schön, die Messmethode ist sicherlich nicht über alle Zweifel erhaben, aber meinen - zugegebenermaßen oberflächlichen - Recherchen nach gibt es in Java durchaus einen automatischen Array Boundary Check (inklusive Exceptionhandling und allem Pipapo). Dieser wird nur dann deaktiviert wenn durch den Programmcode - auf welche Weise auch immer - sichergestellt wird das die Arraygrenzen nicht überschritten werden. Und das wäre nun auch meine Erklärung für die Laufzeitunterschiede.

Falls bei mir Hintergrundprozesse laufen, dann sollten sie in beiden Fällen identisch sein. Ich habe den Codeschnipsel grob geschätzt > 200 mal laufen lassen und immer die gleichen Ergebnisse erhalten. Auch eine Erweiterung der Schleife auf 10.000.000 Durchläufe ergibt das gleiche Bild:
Comparator 1: 56,734 Sekunden
Comparator 2: 146,796 Sekunden

Klar, beide Varianten führen nicht zu einer IndexOutOfBoundsException, aber der bemerkenswerte Punkt ist halt das die Varianten unterschiedlich viel Zeit benötigen. Meine bisherige Erklärung dafür ist die Langsamkeit des Boundary Checks, aber ich freue mich natürlich über Belehrungen. Schließlich soll das Programm ja fix werden. :slight_smile:

PS: Wenn jemand eine bessere Messmethode für die beiden Comparatoren liefern kann - bitte heraus damit! Ich bin auf die Problematik aufmerksam geworden, weil ich die beiden Methoden (nebst noch anderen) in einer konkreten Anwendung verbaut habe, und weil eben schon dort massive Geschwindigkeitsunterschiede feststellbar waren. Sicher ist diese Messmethode mit currentTimeMilis() nicht der Hit, aber sie dient halt auch nur dazu das Symptom zu verdeutlichen. In der eben erwähnten Anwendung macht’s halt schon einen Unterschied ob der User nun eine Minute warten muss oder 2 1/2 Minuten - da kann man die Messmethoden anzweifeln so viel man will… Wie gesagt - falls jemand eine andere Erklärung dafür hat wieso die beiden Methoden in der Praxis unterschiedlich viel Zeit benötigen, dann nur heraus damit!

Ah, Mist. Das Forum sollte einen wirklich darüber informieren das zwischenzeitlich neue Beiträge geschrieben wurden… Also wenn wir die Messungen mal außen vor lassen und auch akzeptieren das der von Marco gepostete Bytecode mit Boundary Check kaum mehr Zeit beansprucht als der ohne Boundary Check - hat dann noch jemand eine Erklärung dafür warum die Sache (bei mir zumindest) so große Laufzeitunterschiede aufweist?

Vielleicht hattest du meinen Versuch, die Messung… „plausibler“ werden zu lassen, noch nicht gesehen, aber zugegeben: Warum es (wenn man sich den Code so ansieht) bei dir diese Ergebnisse liefert, kann ich spontan auch nicht erklären…

EDIT: Genau… :smiley: erstaunlich wie schnell man sich daran gewöhnt… das war ja auch im java-forum.org noch eine recht neue Funktion…

[QUOTE=Timothy_Truckle;21644]Performanz-Messungen in Java sind eine Kunst. Der JIT-Compiler grätscht einem da öfter zwischen die Beine als man vermutet.

[/QUOTE]
Ich habe den Artikel - neben einem anderen kürzlich im Forum verlinken Artikel zum Microbenchmarking - jetzt gelesen, bin aber immer noch nicht schlauer. Der Autor dort verwendet zwar AspektJ (ich mochte AspectC schon nicht, da werde ich mir jetzt nicht ad Hoc AspectJ antun…), doch das hat soweit ich das übersehe keinen Einfluß auf das gegebene Problem.

Während des Lesens habe ich den obigen Originalcode mit 100.000.000 Iterationen laufen lassen und bekam immer noch die gleichen Ergebnisse:
Comparator 1: 567,235 Sekunden
Comparator 2: 1.468,437 Sekunden
Damit dürften wir über Micro-Benchmarking hinaus sein. Und ich fürchte immer noch das hier irgendetwas JVM-spezifisches gewaltig zuschlägt, für das ich z.Z. keine bessere Erklärung habe als die Prüfung der Arraygrenzen.

Ich sollte an dieser Stelle vielleicht erwähnen das es mir nicht um eine Optimierung des Codes geht* sondern um eine Erklärung für die Laufzeitunterschiede. Der Theoretiker in mir ist halt neugierig…

Falls es relevant ist - ich verwende gerade diese Platform:
Java: Java™ SE Runtime Environment (build 1.7.0_13-b20), Java HotSpot™ 64-Bit Server VM (build 23.7-b01, mixed mode)
OS: Windows XP Professional x64
CPU: AMD Phenom II X4 945

PS: kann ein Mod den Thread bitte verschieben und umbenennen (in „Performance von Array Boundary Checking?“ oder sowas)? Dann kann Landei sein Baby nochmal neu an den Start bringen - sofern er das möchte. Was ich hoffe. :slight_smile:

  • in der Anwendung geht es um die Sortierung von Suffixen eines Strings >= 1.000.000 Zeichen. Dafür gibt es pfiffige Algorithmen; die obigen Methoden waren lediglich Bestandteil einer naiven Implementierung zum Performancevergleich

Also wenn, würde ich das eh’ nur so machen:

  int length = Math.min(a.length, b.length);
  int index = 0;
  byte v1, v2;
  do {
    v1 = a[index];
    v2 = b[index];
    index++;
  } while(index < length && v1 == v2);
  return index;
}```Um Performance geht's dabei eigentlich gar nicht, nur darum, dass man ohne "Math.min()" nicht weis, ob die Arrays wirklich immer gleichlang sind. Ausserdem... gibt's bei Gleichheit der Arrays bei der Version, wo man die Arraylängen nicht einbezieht, ohnehin 'ne ArrayIndexOutOfBoundsException?

Hier aber genau so mit einem leeren Array.
Warum nicht:

    final int length = Math.min(a.length, b.length);
    for (int i = 0; i < length; i++) {
        if (a** != b**) {
            return b** - a**;
        }
    }
    return 0;
}

Da fehlt aber immer noch:

  1. Was soll passieren bei Übergabe von null?
  2. Was soll am Ende (oder ganz allgemein) bei unterschiedlichen Längen passieren?

Das Minimum der beiden Arraylängen zu bestimmen ist sicherlich eine gute Sache, aber bei dem obigen Codebeispiel ging’s mir jetzt nicht darum lehrbuchreife Methoden zu präsentieren. (Mit der Abfrage des Minimums läuft das ganze übrigens immer noch genauso schnell, also 5,672 Sekunden)

Es würde eine Exception fliegen, ja. Dieser Fall ist allerdings sowohl in der Anwendung* als auch in dem Codebeispiel ausgeschlossen. In der konkreten Anwendung wollte ich, des guten Stils halber, in den Methoden trotzdem sicherstellen das die Arraygrenzen nicht überschritten werden. Und eben dadurch bin ich auf diese sehr ungleichen Laufzeiten bei annähernd gleichem Code aufmerksam geworden.

Ich habe es gerade nochmal auf einer ganz anderen Platform getestet:

  • SunOS 5.10 Generic_144488-17 sun4u sparc SUNW,Sun-Fire-V215,
  • java version „1.6.0_30“, Java™ SE Runtime Environment (build 1.6.0_30-b12), Java HotSpot™ Server VM (build 20.5-b03, mixed mode)

Bei 1.000.000 Iterationen ergaben sich die Laufzeiten
Comparator 1: 23,512 Sekunden
Comparator 2: 48,801 Sekunden

Bei 10.000.000 Iterationen
Comparator 1: 213,780 Sekunden
Comparator 2: 485,761 Sekunden

Laut dem time-commando liegt zwischen real- und usertime eine Abweichung von 1%, die systemtime lag immer unter einer halben Sekunde (<0,1%). Da bleibt für mich nach wie vor die Frage wieso eine derart marginale Änderung im Quellcode so große Laufzeitunterschiede verursacht. :confused:
Hat jemand eine bessere Erklärung als das Array Boudary Checking?

  • In der Anwendung gibt es nur ein einziges Array - je nach Implementierung mit einem einmaligen Terminatorsymbol am Ende oder als Ringbuffer. Verglichen werden die Arrayinhalte die man erhält wenn man an zwei verschiedenen Indizes anfängt das Array auszulesen.

[QUOTE=faetzminator]Da fehlt aber immer noch:

  1. Was soll passieren bei Übergabe von null?

  2. Was soll am Ende (oder ganz allgemein) bei unterschiedlichen Längen passieren?[/QUOTE]Erst Denken, dann posten? :wink:

  3. Bei null fliegt bereits bei Math.min() 'ne NPE.

  4. Wenn Arrays unterschiedlicher Länge bis zum Punkt des Unterschieds gleich sind, wird die Länge des kürzeren zurückgegeben, welches nach wie vor den Index darstellt, ab welchem sie unterschiedlich sind.

Als Erklärung hätte ich folgendes zu bieten… Da die JVM ja ständig optimiert, erkennt sie den Umstand, dass die Arraylänge in der Schleife „korrekt“ abgefragt wird, weswegen sie nicht in einem Try and Fail Block ausgeführt, welcher erstens Zeit kostet und zweitens einem letztendlich die Exception an den Kopf wirft.
In Assembler haben „Arrays“ nämlich keine zugehörige Länge, geschweige denn einen Terminator, sondern nur eine Speicheradresse. Elemente können beliebig drangehängt werden oder es kann mit 'nem höheren Index auch der Speicher dahinter oder davor gelesen oder geschrieben werden. Beim Code mit dem Längencheck, landen die Arraylängen anscheinend auf dem Stapel des Anweisungsblocks. Ohne diese Längen müssen sie bei jedem einzelnen Vergleich aus 'ner anderen Quelle festgestellt werden.

Da die Diskussion sich ja nur auf den ersten Beitrag bezog und etwas … (im postiven Sinne) „ausgeufert“ ist, habe ich es mal zu einem eigenen Therad im Allgemeinen Teil gemacht.

@Landei : Deinen ursprünglich vorgesehenen Thread „Man wird alt wie eine Kuh und lernt immer noch dazu…“ kannst du ja neu aufmachen, ich fand die Idee nämlich auch gut :slight_smile:

[QUOTE=Dow_Jones;21678]
Während des Lesens habe ich den obigen Originalcode mit 100.000.000 Iterationen laufen lassen und bekam immer noch die gleichen Ergebnisse:

Damit dürften wir über Micro-Benchmarking hinaus sein. [/QUOTE]

Kurz dazu: Es gibt zwar sicher keinen formal-verbindliche Definition des Begriffes „Micro“-Benchmark, aber mit der Frage, wie lange der Benchmark läuft, hat das IMHO nichts zu tun. Eher mit der „Komplexität“ des Benchmarks, auch wenn das auch wieder nur eine recht schwammige Definition ist. Ich hatte auf einer der letzten JAXes einen Vortrag von Charles Nutter gesehen, und meine, mich zu erinnern, dass Methodenaufrufe erst ab einem bestimmten „Level“ optimiert werden - speziell das Method inlining kann ja nicht direkt auf die main angewendet werden, und soweit ich mich erinnere, auch nicht auf die Methoden, die direkt von der main aus aufgerufen werden (ich lasse gerade den Vortrag nochmal laufen, der ist von einer anderen Konferenz, aber inhaltlich anscheinend der gleiche: http://www.youtube.com/watch?v=FnDHp3Qya6s vielleicht finde ich die relevante Stelle).

Wenn ich es richtig verstanden hatte, ging es ja jetzt auch nicht primär um die Frage, wie man „diesen Code möglichst schnell machen kann“ - das ist ja ein künstliches Beispiel, das man in dieser Form kaum brauchen oder so schreiben würde. Es ging ja wohl ursprünglich NUR um die Frage, ob automatische bounds-checks oder manuelle bounds-checks schneller sind, bzw. WIE langsam die automatischen sind.

Wenn man die beiden Punkte kombiniert, wird es schon schwierig: WENN man die Methode so strukturiert, dass der JIT wirklich drüberläuft, ist es schwer, zu wissen, WAS der JIT daraus macht.

So viel erstmal dazu, mal schauen, ob ich die relevante Stelle finde, dann mach’ ich ggf. noch ein paar Tests.

Besten Dank, Meister! :slight_smile:

Nach etwas herumsuchen kann ich nun sagen daß das Problem offenbar bekannt zu sein scheint. Ich zitiere hier mal fröhlich aus diversen Papers:

Okay, es haben sich also schon einige Leute mit der Eleminierung der Bounds Checks befasst da es ihnen ziemlich langsam erschien. Bei Oracle habe ich jetzt gerade gelesen:

Nunja. Das ist ist jetzt nicht so ergiebig.

Sehe ich auch so. Ich hatte ursprünglich schon erwartet das der JIT ein so ein banales Codebeispiel optimiert; zumindest aber hatte ich nicht mit so großen Laufzeitunterschieden gerechnet. Compilieren mit -g und ausführen mit -Xprof liefert bei 10.000.000 Iterationen

Methode 1


Flat profile of 57.08 secs (4859 total ticks): main

  Interpreted + native   Method
  0.2%     8  +     0    ComparatorSpeedTest.main
  0.0%     1  +     0    ComparatorSpeedTest.compareBytewise1
  0.2%     9  +     0    Total interpreted

     Compiled + native   Method
 99.8%  4850  +     0    ComparatorSpeedTest.main
 99.8%  4850  +     0    Total compiled

Methode 2


Flat profile of 146.41 secs (16 total ticks): main

  Interpreted + native   Method
 87.5%    14  +     0    ComparatorSpeedTest.main
  6.3%     1  +     0    sun.launcher.LauncherHelper.getMainMethod
 93.8%    15  +     0    Total interpreted

     Compiled + native   Method
  6.3%     1  +     0    UncommonTrapBlob
  6.3%     1  +     0    Total compiled

Ich interpretiere das so, das der JIT bei Methode 2 eben nicht großartig optimiert (im Sinne von vorcompiliert) sondern ständig die ganze langsame Maschinerie, die sich (u.a. auch) um die Prüfung der Grenzen kümmert, durchorgelt. Das verbuche ich jetzt mal als Bekräftigung meiner Ursprünglichen These: „Die manuelle Überprüfung von Arraygrenzen ist schneller als die automatische Überprüfung“. Auch wenn man das sicher schöner/präziser formulieren könnte.

[QUOTE=Dow_Jones;21845]Auch wenn man das sicher schöner/präziser formulieren könnte.[/QUOTE]Evtl. ja…
Denkt man in Assembler, könnte man sagen, dass man die Arraylängen dem Anweisungsblock “übergibt”. Der Virtuelle Prozessor muss sie sich deswegen nicht mühselig zusammen suchen. Man darf dabei nie vergessen, dass “Objektorientiert” bei Assembler ausfällt. Member wie “.length” gibt es da nicht.
Immerhin kann mans mit OOP aber emulieren, ein Beipiel dafür wäre z.B Object-Reuse. In Java übergeben wir das Objekt und auf Assemblerbasis ist nur noch dessen Speicheradresse (Referenz) interessant.

OK, da sind einige Referenzen dabei, die vielleicht mal einen Blick wert sein könnten. Bei dem verlinkten Video sind (so ab 20:30, aber zum Ende hin noch mehr) einigen interessante Infos, wie man sich denn genauer ansehen kann, was der JIT da macht. Ein wichtiger Punkt ist, dass eine Methode standardmäßig erst für den JIT in betracht gezogen wird, wenn sie 10000 mal aufgerufen wird, deswegen müßte man die beiden Codeschnipsel eigentlich beide (!) erst mal 10000mal aufrufen (ggf. mit kleineren Arrays, falls das das Ergebnis nicht verfälscht) und dann nochmal den eigentlichen Zeittest machen - und das ganze natürlich “protokolliert” mit Ausgabe der JIT-Optimierungen. Im Moment kann ich da nicht sooo viel Zeit investieren, aber da es ganz spannend ist (speziell eben die Frage, WARUM dieser große Zeitunterschied auftritt), werd’ ich mir das wohl nochmal genauer anshen.

Eigentlich hatte ich nie vor hier ein großes Faß aufzumachen… Mir war letztens halt nur aufgefallen das eine so harmlos aussehende Änderung im Quellcode eine so große Änderung in der Laufzeit verursachen kann. Das fand ich bemerkenswert, daher hielt ich den Kuh-Thread für einen guten Platz um das zu erwähnen. Interessant was daraus geworden ist. :slight_smile:

PS @ Spacerat: Mit x86 Assembler habe ich mich nie großartig auseinandergesetzt (nur 6510 Assembler ist mir wirklich geläufig). Aber gut zu wissen das sich hier Leute damit auskennen. :smiley:
Wie auch immer die CPU an .length herankommt, sie müsste den Wert ja sowohl bei Methode 1 als auch bei Methode 2 aus den Datenstrukturen hervorkramen (und pfiffigerweise cachen, wo die Methode doch schon „geInlined“ wird). Von daher habe ich das als nicht besonders relevant betrachtet.

PS @ Marco13: Danke für den Link, das werde ich mir (morgen) nicht entgehen lassen da rein zu schauen. Bin mal gespannt. Das mit den 10.000 hatte ich auch noch von irgendwoher in Erinnerung, daher hatte ich das Ganze ja auch mit weit größeren Iterationen getestet.

[QUOTE=Dow_Jones;21864]PS @ Spacerat: Mit x86 Assembler habe ich mich nie großartig auseinandergesetzt (nur 6510 Assembler ist mir wirklich geläufig). Aber gut zu wissen das sich hier Leute damit auskennen. :smiley:
Wie auch immer die CPU an .length herankommt, sie müsste den Wert ja sowohl bei Methode 1 als auch bei Methode 2 aus den Datenstrukturen hervorkramen (und pfiffigerweise cachen, wo die Methode doch schon „geInlined“ wird). Von daher habe ich das als nicht besonders relevant betrachtet.[/QUOTE]Im Prinzip ist Assembler (oder auch SPS-AWL) immer gleich. Opcodesprache halt. Arrays sind in Assembler immer nur Ansammlungen von Objektreferenzen oder Primitiven Datentypen, einen Arraypointer muss man stets selbst berechnen. Berechnete Arraypointer und -längen (eigentlich jede Art von Objekt- bzw. Primitivtyp) kann man aber auch an Funktionen übergeben, indem man sie entweder auf den Userstack legt, oder eine Adresse in ein Prozessorregister schreibt, wo die Funktion die Daten findet. Tut man es nicht, muss man in der Funktion dafür sorgen, dass man an solche Werte kommt und da muss auch eine JVM evtl. richtig buckeln. Beim Amiga war es damals glaub’ ich Adressregister A6 (kann aber auch sein, dass ich Stapelzeiger A5 auch damit verwechsel).