2016-06-18 10 views
6

ho riscontrato questo problema, mentre a fare i compiti da Coursera "scala di specializzazione" (questo è semplificata la versione e non contiene alcun dettaglio compiti a casa, è solo attraversamento serie)Scala modello di prestazioni corrispondenti

val chars: Array[Char] = some array 

def fun1(idx:Int):Int = { 
    some code here (including the stop condition) 

    val c = chars(idx) 
    c match{ 
     case '(' => fun1(idx+1) 
     case _ => fun1(idx+1) 
    } 

} 

Questo codice è 4 volte più lento di

def fun2(idx: Int):Int = { 
    some code here (including the stop condition) 

    val c = chars(idx) 
    (c == '(') match{ 
     case true => fun2(idx+1) 
     case _ => fun2(idx+1) 
    } 
} 

Tutto quello che sto facendo sta cambiando il pattern matching (io sono in esecuzione utilizzando ScalMeter così credo nelle statistiche).

Qualcuno può spiegare questo comportamento?

risposta

2

Posso solo confermare che il primo match è ~ 50% più lento, non 4x (2.11.8). Ad ogni modo se guardi il bytecode puoi scoprire che il primo match viene tradotto nell'istruzione tableswitch, che viene normalmente utilizzata per l'istruzione Java switch con più scelte ed è fondamentalmente una ricerca goto, mentre la seconda viene convertita in if. Così il secondo match è semplicemente:

if (c == '(') fun2(idx+1) else fun2(idx+1) 
0

Aggiornamento il sottostante è sbagliato (la maggior parte del tempo in queste prove è stato speso generazione dei dati, quindi la differenza nei tempi di attraversamento effettivi non era evidente esecuzione di questo stesso benchmark. con input costante mostra ~ 125 ms per 100 milioni di voci per case ')' caso vs ~ 35 ms per l'altro caso.)

Non vedo la differenza che si sta descrivendo. Non sicuro come ScalaMeter lo fa, ma in esecuzione in REPL (dopo aver lasciato "scaldare" eseguendo un paio di volte "a secco"), ottengo praticamente le stesse prestazioni:

def func(s: Seq[Char], idx: Int): String = 
    if(idx == s.length) "foo" else s(idx) match { 
    case ')' => func(s, idx+1) 
    case _ => func(s, idx+1) 
    } 

def func1(s: Seq[Char], idx: Int): String = 
    if(idx == s.length) "foo" else (s(idx) == '(') match { 
    case true => func(s, idx+1) 
    case _ => func(s, idx+1) 
    } 

import scala.util.Random 
def randy = Stream.continually(Random.nextPrintableChar) 


def doit(n: Int)(f: (Seq[Char], Int) => String) = { 
val start = System.currentTimeMillis;  
f(randy.take(n).toIndexedSeq, 0); 
System.currentTimeMillis - start 
} 


scala> doit(1000000)(func) 
res9: Long = 231 

scala> doit(1000000)(func1) 
res10: Long = 238 

scala> doit(1000000)(func) 
res11: Long = 234 

scala> doit(1000000)(func1) 
res12: Long = 201 

Ecc Come si può vedere, nessuna differenza considerevole.

+0

Vorrei dubitare che questo sia un metodo di benchmark tutt'altro che valido. A ScalaMeter piace fare un paio di dozzine di scaldini finché i risultati non si stabilizzano. Non hai nemmeno provato a usare gli stessi dati. –

+0

Sì, non gli stessi dati. Ma sto ottenendo risultati piuttosto vicini tra le piste. Lo stddev è estremamente basso, il che suggerisce che se avessi usato gli stessi dati, non avrei visto molta differenza. Come accennato nella risposta, ho fatto anche dei warm up, quindi non sono sicuro di quale sia il parametro di riferimento che trovi "non valido". Resto fermo alle mie scoperte e ti sfido a confutarle in modo definitivo (e riproducibile) se puoi. – Dima

+0

Prendendo in prestito le tue funzioni, ecco il benchmark con scala meter: https://gist.github.com/lukaszwawrzyk/a2505d5b3083bb72de51b8445fbb9a76 Dare tempo char: 13.172258374999998 ms' e 'bool time: 4.739404575 ms'. È fatto per gli array come nella domanda, se l'uso di un seq indicizzato i risultati sono effettivamente più vicini ma non uguali (95 secondi contro 80 secondi) –

Problemi correlati