2015-03-12 28 views
10

La programmazione monadica in Java 8 è più lenta? Di seguito è riportato il mio test (viene utilizzato un parallelismo di destra che crea nuove istanze per ogni computazione). La versione imperativa è 1000 volte più veloce. Come si programma monadicaly in Java8 mentre si ottengono prestazioni comparabili?Come si programma in modo monodico in Java8 mentre si ottengono prestazioni comparabili?

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

Basta eseguire 'work' un paio di volte. Il bootstrap di una lambda è costoso. –

+1

Con 1.000.000 di corse, la differenza è più simile a 10 volte più lenta. Con l'80% del tempo trascorso in Main.parseInt (String), per qualche motivo ... –

+0

cambiato lavoro per restituire il delta orario. L'ho provato per 1.000.000 di corse e 10.000.000 di corse. Ho ancora trovato la differenza di essere circa 1000. Ho afferrato solo l'ultima valutazione (quando dovrebbe essere abbastanza caldo). Puoi vedere i risultati in microsecondi sotto Func: 57411000 \t Imper: 83000 – jordan3

risposta

9

Mentre io non capisco il vostro sforzo – a quanto pare si sta utilizzando map per gli effetti collaterali e non hanno davvero alcun alternativa per ottenere il risultato in unbox da Either type – Ho misurato il tuo lavoro su JMH così com'è. Il tuo uso di Random era sbagliato, l'ho corretto. Questo è il codice che ho usato:

@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); 
     } 
    } 
    } 
} 

e questo è il risultato:

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 

Quindi non c'è quasi nessuna differenza.

+0

"Quasi nessuna differenza"?!? ** Imperativo è 15-20% più veloce. ** –

+0

@ Anony-Mousse Quando guardato dall'altezza di "1000 volte più veloce", "0,2 volte più veloce" sembra davvero "quasi nessuna differenza". E anche in termini assoluti, tale differenza sarebbe quasi trascurabile in qualsiasi situazione pratica. Ma dal momento che il codice OP non si concentra abbastanza sulla sola differenza tra le espressioni idiomatiche, mi aspetterei che il rapporto tra i puri overhead delle fasi vincolanti sia piuttosto elevato: più come 2-3. –

+0

In casi di dimensioni minuscole, l'inlining di solito funziona. Ecco perché la differenza non è più di questo. Ma in situazioni più complesse, può benissimo accumularsi, sfortunatamente. In particolare una volta "Either", stream, ecc. Sono polimorfi o multimorph ecc. –