# array.length in For-Schleife verwenden?



## Rock Lobster (4. Feb 2008)

Servus,

wenn ihr z.B. ein Array habt, und in einer For-Schleife die Array-Länge als Endwert benutzen wollt, benutzt ihr dann direkt array.length oder speichert ihr das vorher kurz in eine Variable?


```
for(int i=0; i<buffer.length; i++)

-- oder --

int bufferLength = buffer.length
for(int i=0; i<bufferLength; i++)
```

Anders gefragt, macht es einen Unterschied in der Geschwindigkeit? Da length ja eine Variable ist und keine Funktion, müßte ja das Auslesen dieser Variable keinen erheblichen Unterschied machen - aber wird da dann "direkt adressiert", also weiß die VM sofort, wo sie nachschauen muß, oder wird da erst ein Umweg über die Variable "buffer" gemacht, um auf "length" zuzugreifen? Weil dann wäre die zweite Lösung bei großen Schleifen sicherlich sinnvoller...


----------



## Wildcard (4. Feb 2008)

Mach es straight forward:

for(int i=0; i<buffer.length; i++)

Besser noch foreach


----------



## Rock Lobster (4. Feb 2008)

Naja foreach kann ich in meinen Fällen oft nicht gebrauchen, da ich den Index sehr häufig benötige.

Daß buffer.length mehr straight-forward ist finde ich natürlich auch, aber das beantwortet noch nicht meine Frage  ich programmiere Audio-Tools die so schnell wie möglich an den Daten rumfummeln müssen, daher wäre es für mich schon interessant zu wissen, ob buffer.length direkt oder indirekt auf .length zugreift


----------



## HeRaider (4. Feb 2008)

Also ich benutze immer. 
	
	
	
	





```
for(int i=0; i<buffer.length; i++)
```
Ist meiner Meinung nach übersichtlicher. Ich glaube übrigens nicht, dass es da große Unterschiede gibt. Müsste alles eigentlich mehr oder weniger gleichschnell sein. Kannst ja mal wenn du Lust hast bei beiden Methoden die Zeit messen.


----------



## Rock Lobster (4. Feb 2008)

Hmm ja, Zeitmessung wäre vielleicht wirklich mal 'nen Versuch wert.

Was passiert eigentlich bei so etwas:

```
for(int i=0; i<buffer.length*2; i++)
```

Ist der Compiler dazu in der Lage, sowas als Konstante einzubauen? Die .length-Variablen sind ja eh final, von daher müßte er das ja evtl. so optimieren, daß er es vorher ausliest, *2 nimmt und hinterher nur diesen Temp-Wert in der Schleife testet.

Und falls es so ist, funktioniert das auch bei normalen Variablen? Also daß der Compiler z.B. merkt, daß sich die verwendete Variable in der Schleife eh niemals ändern kann... kann er dann auch temporär diese Variable *2 nehmen und nur damit dann die Schleife durchführen?

EDIT: Also bei sowas hier:

```
int size=70;
for(int i=0; i<size*3; i++)
{
    // size ändert sich hier drin nie
}
```


----------



## Marco13 (4. Feb 2008)

Nimm buffer.length. FALLS das Programm extrem zeitkritisch ist, dann lass zwischendurch einen Profiler drüberlaufen. FALLS die meiste Zeit in so einer Schleife flöten geht (die schon SEHR kurz sein muss, damit so eine lächerlicher Derefernzierung ins Gewicht fällt) dann ersetze das buffer.length durch ein 'int bufferLength', und lass' den Profiler nochmal laufen. FALLS es dann merklich schneller ist (was ich für praktisch ausgeschlossen halte) dann lass das mit dem bufferLength drin - ansonsten nicht. 

(Meine Kristallkugel sagt mir, dass die meiste Zeit an GANZ anderen Stellen flöten gehen wird... :wink: )


----------



## outbreaker (4. Feb 2008)

habe mal schnell ein Programm geschrieben zum Zeit messen:


```
package testen;

public class T5 {

	String[] array = new String[100000];
	long timeD1 = 0;
	long timeD2 = 0;
	long timeD3 = 0;
	
	public T5() 
	{
		init();
		test();
	}
	
	private void init()
	{
		for (int a = 0; a < 100000; a++)
		{
			array[a] = "a";
		}
	}
	
	private void test()
	{
		for (int tests = 0; tests < 100000; tests++)
		{
		
			int z = 0;
			long time1 = System.nanoTime();
			for (int i = 1; i < array.length; i++)
			{
				z += 10;
			}
			long time2 = System.nanoTime();
			
			//Test 2
			
			long time3 = System.nanoTime();
			int bl = array.length;
			for (int i = 1; i < bl; i++)
			{
				z += 10;
			}
			long time4 = System.nanoTime();
			
			
			//Test 3
			
			int bl2 = array.length;
			long time5 = System.nanoTime();
			for (int i = 1; i < bl2; i++)
			{
				z += 10;
			}
			long time6 = System.nanoTime();
			
			timeD1 += (time2 - time1);
			timeD2 += (time4 - time3);
			timeD3 += (time6 - time5);
			
		}
		
		
		System.out.println("Zeit in nanoSekunden");
		System.out.println("time1: " + (timeD1/100000));
		System.out.println("time2: " + (timeD2/100000));
		System.out.println("time3: " + (timeD3/100000));
	}
	
	public static void main(String[] args) {
		new T5();
	}
}
```

Meine Ausgabe war diese:

Zeit in nanoSekunden
time1: 292545
time2: 146132
time3: 148758

Also grob würde ich sagen das die Variante mit dem vorher in eine Variable speichern doppelt so schnell ist 

Das Programm berechnet das nicht für einen Versuch sondern führt 100000 mal die Schleifen mit je 100000 Durchgängen aus


----------



## SlaterB (4. Feb 2008)

Vertausche mal Test 1 mit Test 2, dann ist die andere Variante aufeinmal doppelt so langsam 

so richtig erklären kann ich es nicht, aber die Differenz der Nanosekunden ist auf jeden Fall Quark, so genau kann niemand messen,

lasse jede Operation für sich x Mio. mal laufen und miss dann die Zeit, die mindestens ein paar Sekunden betragen muss,
dann wirds zumindest nicht total unrealistisch


----------



## Rock Lobster (4. Feb 2008)

@ Marco13: Es ist mehr so 'ne prinzipielle Sache, da ich wirklich tonnenweise Schleifen in meinem Programm habe, die sich sehr oft nach der Größe eines Arrays richten. Wenn ich sowas nur 1-2x mache, dann bin ich da auch nicht so pingelig, aber da mein Programm wirklich sehr oft Daten hin- und herrechnen muß, wollte ich da halt nicht unnötig Zeit vergeuden


----------



## SlaterB (4. Feb 2008)

> aber da mein Programm wirklich sehr oft Daten hin- und herrechnen muß

Zeit ist relativ , egal ob eine oder 1000 Schleifen, du wirst immer nur 0.001% verlieren,

was macht es für einen Unterschied, ob deine Operation 1 sec dauert oder 0,99999 sec?
was macht es für einen Unterschied, ob dein Programm 1 Tag läuft oder 23:59:30?


----------



## Ariol (4. Feb 2008)

Ich hab jetzt gerade ein int[100000] jeweils 100000-mal durch beide Möglichkeiten gejagt und Durchschnittsdauern errechnet:


```
public class SpeedTest
{
	public static void main(String[] args)
	{
		long[] run = new long[2];
		

		int[] array = new int[100000];
		int runs = 100000;
		
		for (int c = 1; c <= runs; c++)
		{
			long start1 = System.nanoTime();
			for (int i = 0; i < array.length; i++)
			{}
			long stop1 = System.nanoTime();

			long start2 = System.nanoTime();
			int length = array.length;
			for (int i = 0; i < length; i++)
			{}
			long stop2 = System.nanoTime();

			run[0] += stop1-start1;
			run[1] += stop2-start2;
		}
		
		System.out.println(run[0]/runs);
		System.out.println(run[1]/runs);
	}
}
```

Bei meinem armen schwachen Laptop ( ;-( ) ein ziemlicher Unterschied:


```
315429 // ohne "Auslagern"
158087 // mit
```


----------



## outbreaker (4. Feb 2008)

SlaterB hat gesagt.:
			
		

> Vertausche mal Test 1 mit Test 2, dann ist die andere Variante aufeinmal doppelt so langsam
> 
> so richtig erklären kann ich es nicht, aber die Differenz der Nanosekunden ist auf jeden Fall Quark, so genau kann niemand messen,
> 
> ...



Also das ist bei mir irgenwie nicht der Fall  ???:L 
Bei mir bleibt der Unterschied auch beim Tausch erhalten aber er ist nicht mehr ganz so groß

Zeit in nanoSekunden
time1: 229335
time2: 171863
time3: 175713

aber das kann schon an dem nanoTime Aufruf liegen


----------



## outbreaker (4. Feb 2008)

@Ariol das sind entspricht vom Verhältnis dem was ich auch ermittelt habe


----------



## SlaterB (4. Feb 2008)

hier mal eine fairere Variante von Ariols Test


```
package test;

public class SpeedTest
{
    public static void main(String[] args)
    {
        a();
        b();
        a();
        b();
        a();
        b();

    }

    static void a()
    {
        long[] run = new long[2];


        int[] array = new int[10000];
        int runs = 100000;

        for (int c = 1; c <= runs; c++)
        {
            long start1 = System.nanoTime();
            for (int i = 0; i < array.length; i++)
            {
            }
            long stop1 = System.nanoTime();

            long start2 = System.nanoTime();
            int length = array.length;
            for (int i = 0; i < length; i++)
            {
            }
            long stop2 = System.nanoTime();

            run[0] += stop1 - start1;
            run[1] += stop2 - start2;
        }

        System.out.println(run[0] / runs);
        System.out.println(run[1] / runs);

    }

    static void b()
    {
        long[] run = new long[2];


        int[] array = new int[10000];
        int runs = 100000;

        for (int c = 1; c <= runs; c++)
        {
            long start1 = System.nanoTime();
            int length = array.length;
            for (int i = 0; i < length; i++)
            {
            }
            long stop1 = System.nanoTime();

            long start2 = System.nanoTime();
            for (int i = 0; i < array.length; i++)
            {
            }
            long stop2 = System.nanoTime();

            run[0] += stop1 - start1;
            run[1] += stop2 - start2;
        }

        System.out.println(run[0] / runs);
        System.out.println(run[1] / runs);
    }
}


19082
28950
16277
37274
17184
15889
15880
16358
17490
15868
15787
15801
```

der ist aber auch so einfach, dass man annehmen muss, dass der Compiler hier vereinfacht


----------



## Wildcard (4. Feb 2008)

Und da sind wir wieder bei total falschen Mikro Benchmarks weil sich niemand Gedanken darüber macht was der JIT Compiler daraus bastelt  :roll:


----------



## Marco13 (4. Feb 2008)

Man kann aber - so weit das möglich ist - versuchen, den JIT mit einzubeziehen. Also nicht nur TestA und TestB nacheinander laufen lassen, sondern TestA und TestB vielleicht 10 mal abwechselnd, evtl. noch mit größer werdenden Eingabedaten - von daher ist SlaterB's Test wohl noch am repräsentativsten - und wie man sieht, sieht man keinen praktisch relevanten Unterschied.


----------



## Rock Lobster (4. Feb 2008)

Naja ich hab ja nicht direkt gesagt, daß ich nur wissen will, wie es auf Bytecode-Ebene aussieht


----------



## Wildcard (4. Feb 2008)

Marco13 hat gesagt.:
			
		

> und wie man sieht, sieht man keinen praktisch relevanten Unterschied.


Eben. Wer meint solche Trivialitäten auf Teufel komm raus 'optimieren' zu müssen, hat einfach nicht genug echte Arbeit.


----------



## JavaFred (4. Feb 2008)

Rock Lobster hat gesagt.:
			
		

> benutzt ihr dann direkt array.length oder speichert ihr das vorher kurz in eine Variable?


Du kannst das auch innerhalb des for-Konstrukts benutzen:


```
for(int i=0, len=buffer.length; i<len; ++i) { ... }
```

(So würde ich das machen.)


----------



## Rock Lobster (8. Feb 2008)

@ Wildcard: Vielleicht programmierst Du ja irgendwelche Business-Datenbank-Applikationen oder so, wo das vielleicht nicht so ins Gewicht fällt, aber bei Audio-Sachen will man schon höchstmögliche Performance haben. Und da finde ich es nicht verkehrt, zumindest mal drüber nachzudenken, zumal ich solche Schleifen wirklich ständig benötige, weil ich während des Abspielens zig Samples hin- und herkopieren will. Wenn sich hinterher rausstellt, daß es auf die Performance keinen großartigen Einfluß hat, weil der JIT da klüger ist als der Programmierer, dann ist das natürlich schön und für mich ja auch definitiv eine Antwort, die mir weiterhilft. Sich darüber aber überhaupt keine Gedanken zu machen, finde ich dagegen falsch. Und falls ich innerhalb einer Schleife z.B. 32768 mal unnötigerweise (!) einen Wert auslese, dann zählt das für mich nicht automatisch als "Trivialität" 

@ JavaFred: Über diese Möglichkeit hab ich noch gar nicht nachgedacht... sieht unter Umständen eleganter aus. Werd ich wohl mal im Hinterkopf behalten


----------



## Marco13 (8. Feb 2008)

array.length ist final. Wenn der Compiler das nicht wegoptimieren würde, sollte man sich schon Gedanken machen ....


----------



## SlaterB (8. Feb 2008)

der Compiler müsste erkennen, dass zwischendurch nicht das Array wechselt,
auch kein Hexenwerk, aber nicht trivial,

bei nicht lokalen Variablen wohl grundsätzlich ausgeschlossen?


----------



## schalentier (8. Feb 2008)

Also habs mir mal im Disassemble angesehen.

Wie man unschwer erkennen kann, spart man durch das vorherige Ablegen von array.length genau eine Operation, naemlich 
  //* 7   11:arraylength

In der VM Spezifikation kann man nachlesen was das macht:

Nimmt den ersten Wert vom Stack (was ein Array sein muss) und leg die Laenge zurueck. Wie das genau funktioniert steht da nicht. Vermutlich aber muss die Laenge von irgendwoher gelesen werden, um sie danach auf den Stack zu packen. 

Im anderen Fall wuerde wohl der JIT den Speicherzugriff auf die lokale Variable (length im Beispiel) durch einen Registerzugriff wegoptimieren. 

Demnach sollte die zweite Variante theoretisch schneller sein. Allerdings liegt das wahrscheinlich in einem Bereich, den man vernachlaessigen kann. Viel mehr kann durch das Optimieren des Schleifenbodies rausgeholt werden, denk ich mal. Sicherheit erhaeltst du nur, wenn du das mit einem Profiler ueberpruefst...


```
public static void main(String args[])
    {
        String array[] = new String[20000];
    //    0    0:sipush          20000
    //    1    3:anewarray       String[]
    //    2    6:astore_1        
        for(int i = 0; i < array.length; i++)
    //*   3    7:iconst_0        
    //*   4    8:istore_2        
    //*   5    9:iload_2         
    //*   6   10:aload_1         
    //*   7   11:arraylength     
    //*   8   12:icmpge          30
            System.out.println(array[i]);
    //    9   15:getstatic       #3   <Field PrintStream System.out>
    //   10   18:aload_1         
    //   11   19:iload_2         
    //   12   20:aaload          
    //   13   21:invokevirtual   #4   <Method void PrintStream.println(String)>

    //   14   24:iinc            2  1
    //*  15   27:goto            9
        int length = array.length;
    //   16   30:aload_1         
    //   17   31:arraylength     
    //   18   32:istore_2        
        for(int i = 0; i < length; i++)
    //*  19   33:iconst_0        
    //*  20   34:istore_3        
    //*  21   35:iload_3         
    //*  22   36:iload_2         
    //*  23   37:icmpge          55
            System.out.println(array[i]);
    //   24   40:getstatic       #3   <Field PrintStream System.out>
    //   25   43:aload_1         
    //   26   44:iload_3         
    //   27   45:aaload          
    //   28   46:invokevirtual   #4   <Method void PrintStream.println(String)>

    //   29   49:iinc            3  1
    //*  30   52:goto            35
    //   31   55:return          
    }
```


----------

