2015-09-04 13 views
6

Stosując Scala i iskry, że ma następującą budowę:W jaki sposób kompilator Scala obsługuje nieużywane wartości zmiennych?

val rdd1: RDD[String] = ... 
val rdd2: RDD[(String, Any)] = ... 

val rdd1pairs = rdd1.map(s => (s, s)) 
val result = rdd2.join(rdd1pairs) 
       .map { case (_: String, (e: Any, _)) => e } 

Celem odwzorowania rdd1 w PairRDD jest przyłączyć rdd2 w następnym etapie. Jednak w rzeczywistości interesują mnie tylko wartości rdd2, stąd krok mapowania w ostatniej linii, która pomija klawisze. W rzeczywistości jest to skrzyżowanie między rdd2 i rdd1 wykonane z Spark's join() ze względów wydajnościowych.

Moje pytanie dotyczy klawiszy rdd1pairs: są one tworzone wyłącznie z powodów składniowych (w celu umożliwienia łączenia) w pierwszym kroku mapy, a następnie są odrzucane bez żadnego użycia. Jak radzi sobie z tym kompilator? Czy ma znaczenie pod względem zużycia pamięci, czy używam Stringa s (jak pokazano w przykładzie)? Czy powinienem go zastąpić przez null lub 0, aby zaoszczędzić trochę pamięci? Czy kompilator faktycznie tworzy i przechowuje te obiekty (odniesienia), czy zauważa, że ​​nigdy nie są używane?

Odpowiedz

3

W tym przypadku to, co zrobi sterownik Sparka, ma wpływ na wynik, a nie na kompilator. Niezależnie od tego, czy Spark może zoptymalizować swój potok wykonawczy, aby uniknąć tworzenia zbędnego duplikowania s. Nie jestem pewien, ale myślę, że Spark utworzy rdd1pairs, w pamięci.

Zamiast odwzorowywania (String, String) można użyć (String, Unit):

rdd1.map(s => (s,())) 

Co robisz to w zasadzie filtr rdd2 podstawie rdd1. Jeśli rdd1 jest znacznie mniejsza niż rdd2, inną metodą byłoby reprezentowanie danych rdd1 jako zmiennej rozgłaszania, a nie RDD, i po prostu filtrowanie rdd2. Pozwala to uniknąć tasowania lub zmniejszenia fazy, więc może być szybsze, ale zadziała tylko wtedy, gdy dane z rdd1 są wystarczająco małe, aby zmieścić się w każdym węźle.

EDIT:

Biorąc pod uwagę, jak za pomocą modułu zamiast String oszczędność miejsca, należy rozważyć następujące przykłady:

object size extends App { 

    (1 to 1000000).map(i => ("foo"+i,())) 
    val input = readLine("prompt> ") 
} 

i

object size extends App { 

    (1 to 1000000).map(i => ("foo"+i, "foo"+i)) 
    val input = readLine("prompt> ") 
} 

pomocą polecenia jstat jak opisano w tej kwestii How to check heap usage of a running JVM from the command line? pierwsza wersja wykorzystuje znacznie mniej sterty niż ta ostatnia.

Edit 2:

Unit jest faktycznie Singleton obiekt bez treści, więc logicznie, to nie powinno wymagać serializacji. Fakt, że definicja typu zawiera Unit mówi wszystko, czego potrzebujesz, aby móc deserializować strukturę, która ma pole typu Unit.

Spark domyślnie używa Serializacji Java.Rozważmy następujący:

object Main extends App { 

    import java.io.{ObjectOutputStream, FileOutputStream} 

    case class Foo (a: String, b:String) 
    case class Bar (a: String, b:String, c: Unit) 

    val str = "abcdef" 
    val foo = Foo("abcdef", "xyz") 
    val bar = Bar("abcdef", "xyz",()) 

    val fos = new FileOutputStream("foo.obj") 
    val fo = new ObjectOutputStream(fos) 
    val bos = new FileOutputStream("bar.obj") 
    val bo = new ObjectOutputStream(bos) 
    fo writeObject foo 
    bo writeObject bar 
} 

Oba pliki są identyczne wymiary:

�� sr Main$Foo3�,�z \ L at Ljava/lang/String;L bq ~ xpt abcdeft xyz 

i

�� sr Main$Bar+a!N��b L at Ljava/lang/String;L bq ~ xpt abcdeft xyz 
+0

Brzmi rozsądnie, dzięki. Jednak nadal nie jestem pewien, jak przechowywanie odniesień do jednostki oszczędza znaczną ilość pamięci w porównaniu do oryginalnego wariantu ciągów. Czy to? – Carsten

+0

Mam rozszerzoną odpowiedź na ten temat – mattinbits

+1

Ale w pierwotnym pytaniu nie są tworzone żadne nowe ciągi. Odniesienie do ciągu jest takie samo jak odwołanie do '()'. –

Powiązane problemy