2015-09-08 2 views
7

Ich versuche, die Summe der Quadrate der Werte in der Liste zu berechnen. Unten sind drei Varianten, die alle den erforderlichen Wert berechnen. Ich möchte wissen, welcher der effizienteste ist. Ich erwarte die dritte effizienter zu sein, da Auto-Boxen nur einmal durchgeführt wird.Gibt es einen Vorteil von map nach mapToInt aufzurufen, wo immer erforderlich

// sum of squares 
    int sum = list.stream().map(x -> x * x).reduce((x, y) -> x + y).get(); 
    System.out.println("sum of squares: " + sum); 

    sum = list.stream().mapToInt(x -> x * x).sum(); 
    System.out.println("sum of squares: " + sum); 

    sum = list.stream().mapToInt(x -> x).map(x -> x * x).sum(); 
    System.out.println("sum of squares: " + sum); 
+3

Während in diesem speziellen Fall die Doppelkarte wenig fügt hinzu, gibt es Fälle, in denen eine Transformation in mehrere Teile zu trennen wird die Berechnung besser lesbar oder einfacher machen. (Und da die Mapping-Operation zunimmt, wird der Overhead der zusätzlichen Stufe proportional geringer.) –

Antwort

8

Im Zweifelsfall Test! Mit jmh ich die folgenden Ergebnisse auf einer Liste von 100k Elemente erhalten (in Mikrosekunden, kleiner ist besser):

Benchmark      Mode Samples  Score Error Units 
c.a.p.SO32462798.for_loop  avgt  10 119.110 0.921 us/op 
c.a.p.SO32462798.mapToInt  avgt  10 129.702 1.040 us/op 
c.a.p.SO32462798.mapToInt_map avgt  10 129.753 1.516 us/op 
c.a.p.SO32462798.map_reduce  avgt  10 1262.802 12.197 us/op 
c.a.p.SO32462798.summingInt  avgt  10 134.821 1.203 us/op 

So haben Sie, von schneller zu langsamer:

  • for(int i : list) sum += i*i;
  • mapToInt(x -> x * x).sum() und mapToInt(x -> x).map(x -> x * x).sum()
  • collect(Collectors.summingInt(x -> x * x))
  • map(x -> x * x).reduce((x, y) -> x + y).get()

Beachten Sie, dass die Ergebnisse stark von den JIT-Optimierungen abhängig sind. Wenn die Logik im Mapping komplexer ist, können einige der Optimierungen nicht verfügbar sein (längerer Code = weniger Inlining). In diesem Fall können die Streamversionen 4-5x mehr Zeit benötigen als die for-Schleife - aber wenn diese Logik CPU-lastig ist, Differenz wird sich wieder verringern. Profiling Ihrer tatsächlichen Anwendung erhalten Sie weitere Informationen.


Benchmark-Code als Referenz:

@State(Scope.Benchmark) 
@BenchmarkMode(Mode.AverageTime) 
public class SO32462798 { 

    List<Integer> list; 

    @Setup public void setup() { 
    list = new Random().ints(100_000).boxed().collect(toList()); 
    } 

    @Benchmark public int for_loop() { 
    int sum = 0; 
    for (int i : list) sum += i * i; 
    return sum; 
    } 

    @Benchmark public int summingInt() { 
    return list.stream().collect(Collectors.summingInt(x -> x * x)); 
    } 

    @Benchmark public int mapToInt() { 
    return list.stream().mapToInt(x -> x * x).sum(); 
    } 

    @Benchmark public int mapToInt_map() { 
    return list.stream().mapToInt(x -> x).map(x -> x * x).sum(); 
    } 

    @Benchmark public int map_reduce() { 
    return list.stream().map(x -> x * x).reduce((x, y) -> x + y).get(); 
    } 
} 
+1

Es wäre nett, auch eine Plain-Old-Loop-Implementierung als Referenz hinzuzufügen. Wie "int sum = 0; für (int i: Liste) sum + = i * i; Summe zurückgeben; '. Eine weitere Alternative ist 'list.stream(). Collect (Collectors.summingInt (x -> x * x));' –

+1

@TagirValeev 'summingInt' ist schneller als mapToInt + sum - interesting. For-Schleife ist wie erwartet in einer eigenen Kategorie. – assylias

+1

Btw, mit 'list = new Random(). Ints (100_000) .boxed(). Sammeln (Collectors.toList());' in 'setup()' wäre ordentlich! –

1

Ich erwarte, dass die zweite die schnellste ist.

Es gibt Boxen weder im zweiten noch im dritten Beispiel (wenn die Liste bereits Boxed-Elemente enthält). Aber, es gibt Unboxing.

Ihr zweites Beispiel könnte zwei Unboxing haben (eine für jede x in x*x), während die dritte nur einmal Unboxing. Unboxing ist jedoch schnell und ich denke, es lohnt sich nicht, dies zu optimieren, da eine längere Pipeline mit einem zusätzlichen Funktionsaufruf es sicherlich verlangsamen wird.

Hinweis: Im Allgemeinen sollten Sie nicht erwarten, dass Stream s schneller ist als reguläre Iterationen in Arrays oder Listen. Bei mathematischen Berechnungen, bei denen es auf Geschwindigkeit ankommt, ist es besser, in die andere Richtung zu gehen: einfach durch die Elemente zu iterieren. Wenn Ihre Ausgabe ein aggregierter Wert ist, aggregieren Sie sie, wenn es sich um ein Mapping handelt, weisen Sie dann ein neues Array oder eine Liste derselben Größe zu und füllen Sie sie mit den berechneten Werten.

+2

Da die Operation der Frage eine Summe berechnet, hat es keinen Sinn, ein Array zu erstellen. Oder anders gesagt, in diesem Fall gibt es nichts, was eine manuelle Iteration besser machen könnte als die Stream-Operation ... – Holger

+0

@Holger Sie haben Recht mit dem Mapping. Sie können jedoch 'list.size()' -Funktionsaufrufe mit regelmäßiger Iteration speichern. –

+0

Welche Funktionsaufrufe sprechen Sie? Wenn du darüber streust, z.B. eine 'ArrayList', gibt es überhaupt keinen' size() 'Funktionsaufruf. – Holger

Verwandte Themen