scala vs java, wydajność i pamięć? [Zamknięte]

160

Chętnie przyjrzę się Scali i mam jedno podstawowe pytanie, na które nie mogę znaleźć odpowiedzi: ogólnie, czy istnieje różnica w wydajności i wykorzystaniu pamięci między Scalą i Javą?

John Smith
źródło
3
Słyszałem, że wydajność może być bardzo bliska. Podejrzewam, że zależy to w dużym stopniu od tego, co robisz. (tak jak w przypadku Java vs C)
Peter Lawrey
Odpowiedź na tego rodzaju pytania brzmi „to zależy” - dla praktycznie każdego porównania systemu X z systemem Y. Dodatkowo jest to duplikat stackoverflow.com/questions/2479819/ ...
James Moore

Odpowiedzi:

261

Scala sprawia, że ​​korzystanie z ogromnej ilości pamięci jest bardzo łatwe, nie zdając sobie z tego sprawy. Zwykle jest to bardzo potężne, ale czasami może być denerwujące. Na przykład, załóżmy, że masz tablicę ciągów (tzw. array) Oraz mapę z tych ciągów do plików (tzw mapping.). Załóżmy, że chcesz pobrać wszystkie pliki, które są na mapie i pochodzą z ciągów o długości większej niż dwa. W Javie możesz

int n = 0;
for (String s: array) {
  if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
  if (s.length <= 2) continue;
  bigEnough[n++] = map.get(s);
}

Uff! Ciężka praca. W Scali najbardziej kompaktowym sposobem zrobienia tego samego jest:

val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)

Łatwo! Ale jeśli nie jesteś dość zaznajomiony z tym, jak działają kolekcje, możesz nie zdawać sobie sprawy, że w ten sposób powstała dodatkowa tablica pośrednia (z filter) i dodatkowy obiekt dla każdego elementu tablicy (z mapping.get, który zwraca opcja). Tworzy również dwa obiekty funkcji (jeden dla filtru i jeden dla flatMap), chociaż rzadko jest to poważny problem, ponieważ obiekty funkcji są małe.

Zasadniczo zużycie pamięci jest na prymitywnym poziomie takie samo. Ale biblioteki Scali mają wiele potężnych metod, które pozwalają bardzo łatwo tworzyć olbrzymią liczbę (zazwyczaj krótkotrwałych) obiektów. Odśmiecacz zwykle radzi sobie całkiem nieźle z tego rodzaju śmieciami, ale jeśli wejdziesz całkowicie nieświadomy tego, jaka pamięć jest używana, prawdopodobnie wcześniej napotkasz problemy w Scali niż w Javie.

Zwróć uwagę, że kod Computer Languages ​​Benchmark Game Scala jest napisany w stylu raczej podobnym do języka Java, aby uzyskać wydajność podobną do Java, a zatem ma zużycie pamięci podobne do Javy. Możesz to zrobić w Scali: jeśli napiszesz swój kod tak, aby wyglądał jak kod Java o wysokiej wydajności, będzie to kod Scala o wysokiej wydajności. (Ty może być w stanie napisać go w bardziej idiomatycznych stylu Scala i nadal uzyskać dobre wyniki, ale to zależy od specyfiki).

Powinienem dodać, że na czas spędzony na programowaniu mój kod Scala jest zwykle szybszy niż mój kod Java, ponieważ w Scali mogę wykonać żmudne części, które nie mają krytycznego znaczenia dla wydajności, przy mniejszym wysiłku i poświęcić więcej uwagi na optymalizację algorytmów i kod dla części krytycznych dla wydajności.

Rex Kerr
źródło
172
+1 za ostatni akapit. Jest to istotny punkt, co pozostało z uwzględnieniem pory zbyt często.
Kevin Wright
2
Pomyślałem, że poglądy mogą bardzo pomóc w poruszonych kwestiach. A może nie jest to prawdą konkretnie w przypadku tablic?
Nicolas Payette
1
@Kevin Wright - „To ważny punkt, który zbyt często jest pomijany” - To coś, co łatwo powiedzieć i trudno zademonstrować, i powiedz nam coś o umiejętnościach Rexa Kerra, a nie o tym, co osiągają inni mniej wykwalifikowani.
igouy
1
>> w idiomatycznym stylu z doskonałą wydajnością << W grze benchmarków jest miejsce na idiomatyczne programy Scala, które nie osiągają "doskonałej" wydajności.
igouy
2
@RexKerr - czy twój przykład Java nie wyszukuje klucza mapowania dwukrotnie dla każdego możliwego ciągu, gdzie przykład Scala robi to tylko raz po wybraniu ciągów? To znaczy są optymalizowane na różne sposoby dla różnych zestawów danych?
Seth
103

Jestem nowym użytkownikiem, więc nie mogę dodać komentarza do powyższej odpowiedzi Rexa Kerra (przy okazji zezwalanie nowym użytkownikom na „odpowiadanie”, ale nie na „komentowanie”, jest bardzo dziwną zasadą).

Zapisałem się po prostu, by odpowiedzieć na „Uff, Java jest tak rozwlekła i taka ciężka”, insynuacja popularnej odpowiedzi Rexa powyżej. Chociaż możesz oczywiście napisać bardziej zwięzły kod Scala, podany przykład Java jest wyraźnie nadęty. Większość programistów Java stworzyłaby coś takiego:

List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
  if(s.length() > 2 && mapping.get(s) != null) {
    bigEnough.add(mapping.get(s));
  }
}

I oczywiście, jeśli zamierzamy udawać, że Eclipse nie wykonuje większości rzeczywistego pisania za Ciebie i że każdy zapisany znak naprawdę czyni Cię lepszym programistą, możesz zakodować to:

List b=new ArrayList();
for(String s:array)
  if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));

Teraz nie tylko zaoszczędziłem czas potrzebny mi na wpisanie pełnych nazw zmiennych i nawiasów klamrowych (pozwalając mi spędzić 5 więcej sekund na przemyśleniu głębokich myśli algorytmicznych), ale mogę również wprowadzić swój kod w konkursach zaciemniania i potencjalnie zarobić dodatkowe pieniądze na wakacje.

Nie spać
źródło
7
Dlaczego nie należysz do klubu „modny język miesiąca”? Miłe komentarze. Szczególnie podobała mi się lektura ostatniego akapitu.
stepanian
21
Znakomicie powiedziane! Męczą mnie wymyślone przykłady, w których po zawyżonym kodzie Javy pojawia się starannie skonstruowany, zwięzły przykład Scali (lub jakiegoś innego języka FP), a następnie szybko wyciągam wniosek, że Scala musi być z tego powodu lepsza od Javy. Kto i tak napisał coś znaczącego w Scali! ;-) I nie mów Twittera ...
chrisjleu
2
Cóż, rozwiązanie Rexa wstępnie alokuje pamięć dla tablicy, co sprawi, że skompilowany kod będzie działał szybciej (ponieważ przy twoim podejściu pozwalasz JVM okresowo zmieniać alokację tablicy w miarę jej wzrostu). Mimo że wymagało to więcej pisania, pod względem wydajności może być zwycięzcą.
Ashalynd
5
podczas gdy my to robimy, w java8 będzie to:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl
2
To, co sprawia, że ​​Scala jest „lepsza” pod pewnymi względami niż Java, to rozszerzone możliwości systemu typów, które ułatwiają wyrażanie bardziej ogólnych wzorców jako typów (takich jak monady, funktory itp.). Pozwala to na tworzenie typów, które nie przeszkadzają z powodu zbyt surowych umów, jak to często ma miejsce w Javie. Ścisłe kontrakty, które nie są oparte na rzeczywistych wzorcach w kodzie, są powodem, dla którego odwrócenie wzorców odpowiedzialności jest konieczne tylko po to, aby poprawnie przetestować jednostkowy kod (najpierw przychodzi na myśl Dependence Injection i piekło XML, które przynosi). Dodatek. zwięzłość, jaką zapewnia elastyczność, to tylko bonus.
josiah
67

Napisz swoją Scalę jak Java i możesz oczekiwać, że zostanie wyemitowany prawie identyczny kod bajtowy - z prawie identycznymi metrykami.

Napisz to bardziej „idiomatycznie”, z niezmiennymi obiektami i funkcjami wyższego rzędu, a będzie trochę wolniejsze i trochę większe. Jedynym wyjątkiem od tej praktycznej reguły jest użycie ogólnych obiektów, w których parametry typu używają @specialisedadnotacji, spowoduje to utworzenie jeszcze większego kodu bajtowego, który może przewyższyć wydajność Javy, unikając pudełkowania / rozpakowywania.

Warto również wspomnieć, że więcej pamięci / mniejsza prędkość jest nieuniknionym kompromisem podczas pisania kodu, który można uruchomić równolegle. Idiomatyczny kod Scala ma znacznie bardziej deklaratywny charakter niż typowy kod Javy i często dzieli go zaledwie 4 znaki ( .par) od bycia w pełni równoległym.

Więc jeśli

  • Kod Scala trwa 1,25 razy dłużej niż kod Java w jednym wątku
  • Można go łatwo podzielić na 4 rdzenie (teraz powszechne nawet w laptopach)
  • dla równoległego czasu wykonywania (1,24 / 4 =) 0,3125x oryginalnej Java

Czy możesz powiedzieć, że kod Scala jest teraz stosunkowo 25% wolniejszy lub 3x szybszy?

Prawidłowa odpowiedź zależy od tego, jak dokładnie zdefiniujesz „wydajność” :)

Kevin Wright
źródło
4
Nawiasem mówiąc, możesz wspomnieć, że .parjest w 2.9.
Rex Kerr
26
>> Czy mógłbyś zatem powiedzieć, że kod Scala jest teraz stosunkowo 25% wolniejszy lub 3x szybszy? << Powiedziałbym, dlaczego nie jest to hipotetyczne porównanie z wielowątkowym kodem Java?
igouy
17
@igouy - Chodzi o to, że wspomniany hipotetyczny kod nie istnieje, imperatywny charakter „szybszego” kodu Javy znacznie utrudnia zrównoleglenie, tak że stosunek kosztów do korzyści oznacza, że ​​w ogóle się to nie zdarzy. Z drugiej strony, idiomatyczna Scala, będąca z natury znacznie bardziej deklaratywna, często może być współbieżna z jedynie trywialną zmianą.
Kevin Wright
7
Istnienie współbieżnych programów w języku Java nie oznacza, że typowy program w języku Java można łatwo dostosować do współbieżności. Jeśli już, powiedziałbym, że konkretny styl łączenia rozwidlonego jest szczególnie rzadki w Javie i musi być jawnie zakodowany, podczas gdy proste operacje, takie jak znajdowanie minimalnej zawartej wartości lub sumy wartości w kolekcji, można wykonać równolegle w Scali, po prostu używając .par.
Kevin Wright
5
Nie, może nie. Takie rzeczy są podstawowym budulcem dla wielu algorytmów, a widok tego na tak niskim poziomie w języku i bibliotekach standardowych (tych samych bibliotekach standardowych, których będą używać wszystkie programy, a nie tylko typowe), jest dowodem na to, że: są już bliżej współbieżności, po prostu wybierając język. Na przykład mapowanie kolekcji jest z natury odpowiednie do zrównoleglenia, a liczba programów Scala, które nie używają tej mapmetody, będzie znikomo mała.
Kevin Wright
31

Gra z testami językowymi komputera:

Test prędkości java / scala 1.71 / 2.25.0

Test pamięci java / scala 66.55 / 80.81.0

Tak więc te testy porównawcze mówią, że java jest o 24% szybsza, a scala zużywa o 21% więcej pamięci.

Podsumowując, to nic wielkiego i nie powinno mieć znaczenia w rzeczywistych aplikacjach, gdzie większość czasu zajmuje baza danych i sieć.

Konkluzja: Jeśli Scala sprawia, że ​​Ty i Twój zespół (oraz ludzie przejmujący projekt, kiedy odchodzicie), jesteście bardziej produktywni, to powinniście to zrobić.

Peter Knego
źródło
34
Rozmiar kodu java / scala 3,39 / 2,21
hammar
22
Uważaj na takie liczby, brzmią niesamowicie precyzyjnie, podczas gdy w rzeczywistości prawie nic nie znaczą. To nie jest tak, że Scala jest zawsze średnio 24% szybsza niż Java itd.
Jesper
3
Przytaczane liczby Afaik wskazują na coś przeciwnego: Java jest 24% szybsza niż scala. Ale jak mówisz - to mikroznakowania, które nie muszą pasować do tego, co dzieje się w prawdziwych aplikacjach. A inny sposób lub rozwiązanie problemu w różnych językach może ostatecznie doprowadzić do mniej porównywalnych programów.
użytkownik nieznany
9
„Jeśli Scala sprawi, że Ty i Twój zespół ...” Konkluzja: Dowiesz się o tym nie wcześniej :-)
igouy
Strona pomocy dotycząca gier porównawczych zawiera przykład „Porównanie szybkości i rozmiaru programu dla implementacji w dwóch językach”. Dla Scala i Java odpowiednia strona porównawcza to - shootout.alioth.debian.org/u64q/scala.php
igouy
20

Inni odpowiedzieli na to pytanie w odniesieniu do ciasnych pętli, chociaż wydaje się, że między przykładami Rexa Kerra, które skomentowałem, jest oczywista różnica w wydajności.

Ta odpowiedź jest naprawdę skierowana do osób, które mogą rozważyć potrzebę optymalizacji w ciasnej pętli jako wadę projektową.

Jestem stosunkowo nowy w Scali (około roku lub więcej), ale jak dotąd wydaje mi się, że pozwala to odłożyć stosunkowo łatwe wielu aspektów projektowania, wdrażania i wykonania (z wystarczającą ilością czytania w tle i eksperymentowania :)

Funkcje odroczonego projektu:

Funkcje odroczonej implementacji:

Funkcje odroczonego wykonania: (przepraszam, brak linków)

  • Wartości leniwych bezpieczne dla wątków
  • Pass-by-name
  • Monadyczne rzeczy

Według mnie te cechy pomagają nam podążać ścieżką do szybkich, wąskich aplikacji.


Przykłady Rexa Kerra różnią się pod względem odroczonych aspektów egzekucji. W przykładzie Java alokacja pamięci jest odroczona do momentu obliczenia jej rozmiaru, przy czym przykład Scala odracza wyszukiwanie mapowania. Wydają mi się zupełnie innymi algorytmami.

Oto, jak sądzę, odpowiednik jabłka na jabłka w jego przykładzie w Javie:

val bigEnough = array.collect({
    case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})

Brak zbiory pośredniczących, żadnych Optionwystąpień itp ta zachowuje również typ pobierania, dzięki czemu bigEnoughjest to typ Array[File]- Arrayjest collectrealizacja będzie prawdopodobnie robić coś wzdłuż linii co kod Mr Kerr Java robi.

Funkcje odroczonego projektowania, które wymieniłem powyżej, pozwoliłyby również programistom API kolekcji Scala na zaimplementowanie tej szybkiej implementacji zbierania danych specyficznej dla tablicy w przyszłych wersjach bez przerywania API. To jest to, do czego mam na myśli kroczenie ścieżką do prędkości.

Również:

val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)

withFilterMetoda Użyłem tutaj zamiast filterpoprawek pośredni problemu gromadzenia ale jest jeszcze kwestia instancja Option.


Jednym z przykładów prostej szybkości wykonywania w Scali jest logowanie.

W Javie możemy napisać coś takiego:

if (logger.isDebugEnabled())
    logger.debug("trace");

W Scali to tylko:

logger.debug("trace")

ponieważ parametr wiadomości do debugowania w Scali ma typ „ => String”, o którym myślę jako o funkcji bez parametrów, która jest wykonywana, gdy jest oceniany, ale którą w dokumentacji nazywa pass-by-name.

EDYCJA {Funkcje w Scali są obiektami, więc jest tutaj dodatkowy obiekt. W mojej pracy wartość trywialnego obiektu jest warta wyeliminowania możliwości niepotrzebnego oszacowania komunikatu dziennika. }

To nie przyspiesza kodu, ale zwiększa prawdopodobieństwo, że będzie szybszy i rzadziej będziemy mieć doświadczenie w masowym przeglądaniu i czyszczeniu kodu innych osób.

Dla mnie jest to spójny temat w Scali.


Twardy kod nie potrafi uchwycić, dlaczego Scala jest szybsza, choć trochę to podpowiada.

Uważam, że jest to połączenie ponownego wykorzystania kodu i pułapu jakości kodu w Scali.

W Javie niesamowity kod jest często zmuszany do przekształcenia się w niezrozumiały bałagan, a więc nie jest tak naprawdę opłacalny w przypadku API o jakości produkcyjnej, ponieważ większość programistów nie byłaby w stanie go używać.

Mam duże nadzieje, że Scala pozwoli einsteinom wśród nas na wdrożenie znacznie bardziej kompetentnych API, potencjalnie wyrażonych przez DSL. Podstawowe interfejsy API w Scali są już daleko na tej ścieżce.

Seth
źródło
Twoje logowanie jest dobrym przykładem pułapek wydajnościowych Scala: logger.debug ("trace") tworzy nowy obiekt dla funkcji bez parametrów.
jcsahnwaldt Przywróć Monikę
Rzeczywiście - jak to wpływa na mój powiązany punkt?
Seth
Wyżej wymienione obiekty mogą również służyć do tworzenia przezroczystych struktur sterowania IoC ze względu na wydajność. Tak, ten sam wynik jest teoretycznie możliwy w Javie, ale byłby to coś, co dramatycznie wpłynęło / zaciemniło sposób pisania kodu - stąd mój argument, że talent Scali do odkładania wielu elementów rozwoju oprogramowania pomaga nam przejść w kierunku szybszego kodu - z większym prawdopodobieństwem szybszy w praktyce w porównaniu z nieznacznie większą wydajnością jednostki.
Seth
Ok, ponownie to przeczytałem i napisałem „prosta szybkość wykonania” - dopisuję uwagę. Słuszna uwaga :)
Seth
3
Przewidywalna instrukcja if (w zasadzie wolna na superskalarnym procesorze) vs alokacja obiektów + śmieci. Kod w Javie jest oczywiście szybszy (pamiętaj, że ocenia tylko warunek, wykonanie nie osiągnie instrukcji log). W odpowiedzi na pytanie „W mojej pracy waga trywialnego obiektu jest warta usunięcia możliwości niepotrzebnego oszacowania komunikatu dziennika ”.
Eloff,
10

Java i Scala kompilują się do kodu bajtowego JVM, więc różnica nie jest tak duża. Najlepsze porównanie, jakie można uzyskać, jest prawdopodobnie w testach porównawczych języków komputerowych , które zasadniczo mówią, że Java i Scala mają takie samo zużycie pamięci. Scala jest tylko nieznacznie wolniejsza niż Java w niektórych z wymienionych testów porównawczych, ale może to wynikać po prostu z innego sposobu implementacji programów.

Ale naprawdę oboje są tak blisko, że nie warto się o to martwić. Wzrost produktywności uzyskany dzięki używaniu bardziej wyrazistego języka, takiego jak Scala, jest wart o wiele więcej niż minimalne (jeśli w ogóle) uderzenie w wydajność.

ryeguy
źródło
7
Widzę tutaj błąd logiczny: oba języki kompilują się do kodu bajtowego, ale doświadczony programista i nowicjusz - ich kod również kompiluje się do kodu bajtowego - ale nie do tego samego kodu bajtowego, więc wniosek, że różnica nie może być tak duża , może się mylić. I faktycznie, w dawnych czasach pętla while mogła być dużo, dużo szybsza w scali niż semantycznie równoważna pętla for (o ile dobrze pamiętam, dziś jest znacznie lepsza). I oczywiście oba zostały skompilowane do kodu bajtowego.
użytkownik nieznany
@user nieznany - „pętla while może być dużo, dużo szybsza w scali niż semantyczny odpowiednik pętli for” - zauważ, że te programy do gier testowych Scala są napisane z wykorzystaniem pętli while.
igouy
@igouy: Nie mówiłem o wynikach tego mikroznaku, ale o argumentacji. Prawdziwe stwierdzenie, Java and Scala both compile down to JVM bytecode, które zostało połączone z a sodo tego stwierdzenia diffence isn't that big.chciałem pokazać, że sojest to tylko chwyt retoryczny, a nie argumentacyjny wniosek.
użytkownik nieznany
3
zaskakująco niepoprawna odpowiedź z zaskakująco wysoką liczbą głosów.
shabunc
4

Przykład Java nie jest idiomem dla typowych programów użytkowych. Taki zoptymalizowany kod można znaleźć w metodzie biblioteki systemowej. Ale wtedy użyłby tablicy odpowiedniego typu, tj. File [] i nie zgłosiłby wyjątku IndexOutOfBoundsException. (Różne warunki filtrowania dla liczenia i dodawania). Moja wersja byłaby (zawsze (!) Z nawiasami klamrowymi, ponieważ nie lubię spędzać godziny na szukaniu błędu, który został wprowadzony, oszczędzając 2 sekundy na naciśnięcie jednego klawisza w Eclipse):

List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
  if(s.length() > 2) {
    File file = mapping.get(s);
    if (file != null) {
      bigEnough.add(file);
    }
  }
}

Ale mógłbym przynieść wiele innych brzydkich przykładów kodu Java z mojego obecnego projektu. Próbowałem uniknąć powszechnego kopiowania i modyfikowania stylu kodowania, uwzględniając wspólne struktury i zachowania.

W mojej abstrakcyjnej klasie bazowej DAO mam abstrakcyjną klasę wewnętrzną dla wspólnego mechanizmu buforowania. Dla każdego typu obiektu konkretnego modelu istnieje podklasa abstrakcyjnej klasy bazowej DAO, w której klasa wewnętrzna jest podklasą, aby zapewnić implementację metody, która tworzy obiekt biznesowy po załadowaniu z bazy danych. (Nie możemy użyć narzędzia ORM, ponieważ uzyskujemy dostęp do innego systemu za pośrednictwem zastrzeżonego interfejsu API).

Ta podklasa i kod instancji nie są wcale jasne w Javie i byłyby bardzo czytelne w Scali.

MickH
źródło