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ą?
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ą?
Odpowiedzi:
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.
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.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
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ą @specialised
adnotacji, 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
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ść” :)
.par
jest w 2.9.
.par
.
map
metody, będzie znikomo mała.
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ć.
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)
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 Option
wystąpień itp ta zachowuje również typ pobierania, dzięki czemu bigEnough
jest to typ Array[File]
- Array
jest collect
realizacja 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)
withFilter
Metoda Użyłem tutaj zamiast filter
poprawek 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.
Prezentacja @higherkinded na ten temat - Scala Performance Considerations, w której dokonano porównania między Javą a Scalą.
Przybory:
Świetny post na blogu:
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ść.
Java and Scala both compile down to JVM bytecode,
które zostało połączone z a so
do tego stwierdzenia diffence isn't that big.
chciałem pokazać, że so
jest to tylko chwyt retoryczny, a nie argumentacyjny wniosek.
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.