11

Sto considerando l'utilizzo di Scala su un programma piuttosto impegnativo dal punto di vista computazionale. Il profiling della versione C++ del nostro codice rivela che potremmo trarre vantaggi significativi dalla valutazione Lazy. L'ho provato in Scala 2.9.1 e mi piace molto. Tuttavia, quando ho eseguito la lezione attraverso un decompilatore, l'implementazione non è sembrata corretta. Sto assumendo che si tratta di un manufatto del decompilatore, ma ho voluto ottenere una risposta più definitiva ...Si tratta di un errore nell'implementazione lenta di Scala 2.9.1 o solo un artefatto di decompilazione.

si consideri il seguente esempio banale:

class TrivialAngle(radians : Double) 
{ 
    lazy val sin = math.sin(radians) 
} 

quando ho decompilarlo, ottengo questo:

import scala.ScalaObject; 
import scala.math.package.; 
import scala.reflect.ScalaSignature; 

@ScalaSignature(bytes="omitted") 
public class TrivialAngle 
    implements ScalaObject 
{ 
    private final double radians; 
    private double sin; 
    public volatile int bitmap$0; 

    public double sin() 
    { 
    if ((this.bitmap$0 & 0x1) == 0); 
    synchronized (this) 
    { 
     if (
     (this.bitmap$0 & 0x1) == 0) 
     { 
     this.sin = package..MODULE$.sin(this.radians); 
     this.bitmap$0 |= 1; 
     } 
     return this.sin; 
    } 
    } 

    public TrivialAngle(double radians) 
    { 
    } 
} 

Per me, il blocco di ritorno si trova nel punto sbagliato e si acquisirà sempre il blocco. Questo non può essere ciò che sta facendo il codice reale, ma non posso confermarlo. Qualcuno può confermare o negare che ho una falsa decompilazione e che l'implementazione lenta è in qualche modo ragionevole (vale a dire, solo blocchi quando calcola il valore e non acquisisce il blocco per le chiamate successive?)

Grazie!

Per riferimento, questo è il decompilatore che ho usato: http://java.decompiler.free.fr/?q=jdgui

+0

Computazionalmente intensivo e vuoi fare le serrature? –

+0

no, ho un sacco di elementi che voglio calcolare solo se/quando ho bisogno di loro, e vorrei che quei risultati fossero memorizzati nella cache dopo il calcolo. A seconda dell'implementazione, pigro fa esattamente quello che vorrei. Se potessi specificare nessun blocco, sarebbe ancora meglio, ma non è questo il punto di questa domanda. – fbl

+1

Beh, ho fatto un sacco di tuning del codice C/C++/Fortran ad uso intensivo di calcolo (simulazione farmaceutica). Il metodo che uso [è questo] (http://stackoverflow.com/questions/375913/what-can-i-use-to-profile-c-code-in-linux/378024#378024). (Non si può sempre credere ai profiler, anche quando parlano chiaramente.) –

risposta

9

scala -Xprint:jvm rivela la vera storia:

[[syntax trees at end of jvm]]// Scala source: lazy.scala 
package <empty> { 
    class TrivialAngle extends java.lang.Object with ScalaObject { 
    @volatile protected var bitmap$0: Int = 0; 
    <paramaccessor> private[this] val radians: Double = _; 
    lazy private[this] var sin: Double = _; 
    <stable> <accessor> lazy def sin(): Double = { 
     if (TrivialAngle.this.bitmap$0.&(1).==(0)) 
     { 
      TrivialAngle.this.synchronized({ 
      if (TrivialAngle.this.bitmap$0.&(1).==(0)) 
       { 
       TrivialAngle.this.sin = scala.math.`package`.sin(TrivialAngle.this.radians); 
       TrivialAngle.this.bitmap$0 = TrivialAngle.this.bitmap$0.|(1); 
       () 
       }; 
      scala.runtime.BoxedUnit.UNIT 
      }); 
     () 
     }; 
     TrivialAngle.this.sin 
    }; 
    def this(radians: Double): TrivialAngle = { 
     TrivialAngle.this.radians = radians; 
     TrivialAngle.super.this(); 
    () 
    } 
    } 
} 

E 'una (dal JVM 1.5) al sicuro, e molto veloce, blocco ricontrollato.

Maggiori dettagli:

What's the (hidden) cost of Scala's lazy val?

essere consapevoli del fatto che se si dispone di più membri della val pigri in una classe, solo uno di essi può essere inizializzato in una sola volta, in quanto sono sorvegliate da synchronized(this) { ... }.

+0

Grazie! Se potessi contrassegnare 2 risposte come "corrette", lo farei. Ho scelto questo perché è leggermente più leggibile;) Sono pienamente consapevole del fatto che i campi pigri possono essere inizializzati solo uno alla volta. Questo può o non può influenzare il modo in cui architettiamo la nostra soluzione, ma certamente informa la decisione. – fbl

9

quello che ottengo con javap -c non corrisponde al vostro decompilare. In particolare, non viene immesso alcun monitor quando il campo viene trovato inizializzato. Anche la versione 2.9.1. C'è ancora la barriera di memoria implicata dall'accesso volatile, naturalmente, quindi non viene completamente liberata. Commenti che iniziano con /// sono miei

public double sin(); 
    Code: 
    0: aload_0 
    1: getfield  #14; //Field bitmap$0:I 
    4: iconst_1 
    5: iand 
    6: iconst_0 
    7: if_icmpne  54 /// if getField & 1 == O goto 54, skip lock 
    10: aload_0 
    11: dup 
    12: astore_1 
    13: monitorenter 
      /// 14 to 52 reasonably equivalent to synchronized block 
      /// in your decompiled code, without the return 
    53: monitorexit 
    54: aload_0 
    55: getfield  #27; //Field sin:D 
    58: dreturn  /// return outside lock 
    59: aload_1  /// (this would be the finally implied by the lock) 
    60: monitorexit 
    61: athrow 
    Exception table: 
    from to target type 
    14 54 59 any 
+0

Grazie! Se potessi contrassegnare 2 risposte come "corrette", lo farei. Sia questa risposta che il retronym rivelano la vera natura di Lazy. – fbl

+0

Nessun problema, preferisco anche la risposta del retronym, tanto più che non l'ho fatto di nuovo -Xprint: opzione jvm. javap potrebbe ancora essere il giudice finale se davvero non ti fidi di scala, ma meglio se non arriva a quello. –

Problemi correlati