2015-03-12 11 views
10

Ist die monadische Programmierung in Java 8 langsamer? Unten ist mein Test (ein rechtsgerichtetes Es wird verwendet, das für jede Berechnung neue Instanzen erstellt). Die imperative Version ist 1000 mal schneller. Wie programmiere ich in Java8 monadisch und vergleiche die Leistung?Wie programmiere ich in Java8 monadisch und erhalte vergleichbare Leistung?

Main.java

public class Main { 

    public static void main(String args[]){ 
     Main m = new Main(); 
     m.work(); 
     m.work2(); 
    } 


    public void work(){ 
     final long start = System.nanoTime(); 
     final Either<Throwable,Integer> result = 
       Try(this::getInput).flatMap((s) -> 
       Try(this::getInput).flatMap((s2) -> 
       parseInt(s).flatMap((i) -> 
       parseInt(s2).map((i2) -> 
       i + i2 
       )))); 
     final long end = System.nanoTime(); 
     result.map(this::println).leftMap(this::println); 
     System.out.println((end-start)/1000+"us to execute"); 
    } 

    public void work2(){ 
     Object result; 
     final long start = System.nanoTime(); 
     try { 
      final String s = getInput(); 
      final String s2 = getInput(); 

      final int i = parzeInt(s); 
      final int i2 = parzeInt(s2); 
      result = i + i2; 
     }catch(Throwable t){ 
      result=t; 
     } 
     final long end = System.nanoTime(); 
     println(result); 
     System.out.println((end-start)/1000+"us to execute"); 
    } 

    public <A> A println(final A a){ 
     System.out.println(a); 
     return a; 
    } 

    public String getInput(){ 
     final Integer value = new Random().nextInt(); 
     if(value % 2 == 0) return "Surprise!!!"; 
     return value+""; 
    } 

    public Either<Throwable,Integer> parseInt(final String s){ 
     try{ 
      return Either.right(Integer.parseInt(s)); 
     }catch(final Throwable t){ 
      return Either.left(t); 
     } 
    } 

    public Integer parzeInt(final String s){ 
     return Integer.parseInt(s); 
    } 
} 

Either.java

public abstract class Either<L,R> 
{ 
    public static <L,R> Either<L,R> left(final L l){ 
     return new Left(l); 
    } 

    public static <L,R> Either<L,R> right(final R r){ 
     return new Right(r); 
    } 

    public static<L,R> Either<L,R> toEither(final Optional<R> oR,final L l){ 
     return oR.isPresent() ? right(oR.get()) : left(l); 
    } 

    public static <R> Either<Throwable,R> Try(final Supplier<R> sr){ 
     try{ 
      return right(sr.get()); 
     }catch(Throwable t){ 
      return left(t); 
     } 
    } 

    public abstract <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f); 

    public abstract <R2> Either<L,R2> map(final Function<R,R2> f); 

    public abstract <L2> Either<L2,R> leftMap(final Function<L,L2> f); 

    public abstract Either<R,L> swap(); 

    public static class Left<L,R> extends Either<L,R> { 
     final L l; 

     private Left(final L l){ 
      this.l=l; 
     } 

     public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){ 
      return (Either<L,R2>)this; 
     } 

     public <R2> Either<L,R2> map(final Function<R,R2> f){ 
      return (Either<L,R2>)this; 
     } 

     public <L2> Either<L2,R> leftMap(final Function<L,L2> f){ 
      return new Left(f.apply(l)); 
     } 

     public Either<R,L> swap(){ 
      return new Right(l); 
     } 
    } 

    public static class Right<L,R> extends Either<L,R> { 
     final R r; 

     private Right(final R r){ 
      this.r=r; 
     } 

     public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){ 
      return f.apply(r); 
     } 

     public <R2> Either<L,R2> map(final Function<R,R2> f){ 
      return new Right(f.apply(r)); 
     } 

     public <L2> Either<L2,R> leftMap(final Function<L,L2> f){ 
      return (Either<L2,R>)this; 
     } 

     public Either<R,L> swap(){ 
      return new Left(r); 
     } 
    } 
} 
+5

Nur noch einige Male "Arbeit" ausführen. Bootstrapping eines Lambda ist teuer. –

+1

Mit 1.000.000 Läufen ist der Unterschied mehr als 10x langsamer. Mit 80% der Zeit in Main.parseInt (String) verbrachte, aus irgendeinem Grund ... –

+0

geändert, um das Zeitdelta zurückzugeben. Lief es für 1.000.000 Läufe und 10.000.000 Läufe. Habe immer noch den Unterschied zu ungefähr 1000 gefunden. Ich habe nur die letzte Bewertung genommen (wenn es ziemlich warm sein sollte). Sie können die Ergebnisse in Mikrosekunden unter Funk: 57411000 \t Imper: 83000 – jordan3

Antwort

9

Während ich nicht ganz Ihre Mühe verstehen – offenbar sind Sie map für Nebenwirkungen verwendet, und Sie haben nicht wirklich Alternative, um das Ergebnis entkoffeiniert von der Entweder Typ – Ich habe Ihre Arbeit an JMH gemessen, wie es ist. Ihre Verwendung von Random war falsch, das habe ich korrigiert. Dies ist der Code, den ich verwenden:

@BenchmarkMode(Mode.AverageTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
@OperationsPerInvocation(Measure.SIZE) 
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 
@State(Scope.Thread) 
@Fork(1) 
public class Measure 
{ 
    static final int SIZE = 1; 

    @Benchmark public Either<Throwable, Integer> workMonadically() { 
    final Either<Throwable,Integer> result = 
     Try(this::getInput).flatMap((s) -> 
      Try(this::getInput).flatMap((s2) -> 
       parseInt(s).flatMap((i) -> 
        parseInt(s2).map((i2) -> 
          i + i2 
        )))); 
    return result; 
    } 

    @Benchmark public Object workImperatively() { 
    Object result; 
    try { 
     final String s = getInput(); 
     final String s2 = getInput(); 

     final int i = parzeInt(s); 
     final int i2 = parzeInt(s2); 
     result = i + i2; 
    }catch(Throwable t){ 
     result=t; 
    } 
    return result; 
    } 

    public String getInput() { 
    final Integer value = ThreadLocalRandom.current().nextInt(); 
    if (value % 2 == 0) return "Surprise!!!"; 
    return String.valueOf(value); 
    } 

    public Either<Throwable,Integer> parseInt(final String s){ 
    try{ 
     return Either.right(Integer.parseInt(s)); 
    }catch(final Throwable t){ 
     return Either.left(t); 
    } 
    } 

    public Integer parzeInt(final String s){ 
    return Integer.parseInt(s); 
    } 

    public static abstract class Either<L,R> 
    { 
    public static <L,R> Either<L,R> left(final L l){ 
     return new Left<>(l); 
    } 

    public static <L,R> Either<L,R> right(final R r){ 
     return new Right<>(r); 
    } 

    public static<L,R> Either<L,R> toEither(final Optional<R> oR,final L l){ 
     return oR.isPresent() ? right(oR.get()) : left(l); 
    } 

    public static <R> Either<Throwable,R> Try(final Supplier<R> sr){ 
     try{ 
     return right(sr.get()); 
     }catch(Throwable t){ 
     return left(t); 
     } 
    } 

    public abstract <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f); 

    public abstract <R2> Either<L,R2> map(final Function<R,R2> f); 

    public abstract <L2> Either<L2,R> leftMap(final Function<L,L2> f); 

    public abstract Either<R,L> swap(); 

    public static class Left<L,R> extends Either<L,R> { 
     final L l; 

     private Left(final L l){ 
     this.l=l; 
     } 

     @Override public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){ 
     return (Either<L,R2>)this; 
     } 

     @Override public <R2> Either<L,R2> map(final Function<R,R2> f){ 
     return (Either<L,R2>)this; 
     } 

     @Override public <L2> Either<L2,R> leftMap(final Function<L,L2> f){ 
     return new Left<>(f.apply(l)); 
     } 

     @Override public Either<R,L> swap(){ 
     return new Right<>(l); 
     } 
    } 

    public static class Right<L,R> extends Either<L,R> { 
     final R r; 

     private Right(final R r){ 
     this.r=r; 
     } 

     @Override public <R2> Either<L,R2> flatMap(final Function<R,Either<L,R2>> f){ 
     return f.apply(r); 
     } 

     @Override public <R2> Either<L,R2> map(final Function<R,R2> f){ 
     return new Right<>(f.apply(r)); 
     } 

     @Override public <L2> Either<L2,R> leftMap(final Function<L,L2> f){ 
     return (Either<L2,R>)this; 
     } 

     @Override public Either<R,L> swap(){ 
     return new Left<>(r); 
     } 
    } 
    } 
} 

und das ist das Ergebnis:

Benchmark     Mode Cnt  Score  Error Units 
Measure.workImperatively avgt 5 1646,874 ± 137,326 ns/op 
Measure.workMonadically avgt 5 1990,668 ± 281,646 ns/op 

So gibt es fast keinen Unterschied.

+0

"Fast kein Unterschied"?!? ** Imperativ ist 15-20% schneller. ** –

+0

@ Anony-Mousse Wenn man aus der Höhe von "1000 mal schneller" schaut, klingt "0,2 mal schneller" tatsächlich wie "fast kein Unterschied". Und selbst in absoluten Zahlen wäre ein solcher Unterschied in jeder praktischen Situation nahezu vernachlässigbar. Aber da der Code von OP nicht genug auf den Unterschied zwischen Idiomen fokussiert, würde ich erwarten, dass das Verhältnis der reinen Overheads der Bindungsschritte ziemlich hoch ist: mehr wie 2-3. –

+0

In so kleinen Fällen funktioniert Inlining normalerweise. Deshalb ist der Unterschied nicht mehr. Aber in komplexeren Situationen kann es sich leider sehr häufen. Insbesondere einmal "Entweder", Ströme usw. sind polymorph oder multimorph usw. –