scala vs java, продуктивність та пам'ять? [зачинено]


160

Я прагну вивчити Scala, і у мене є одне основне запитання, на яке я не можу знайти відповіді: загалом, чи є різниця у продуктивності та використанні пам'яті між Scala та Java?


3
Я чув, що твердження можуть бути дуже близькими. Я підозрюю, що це дуже залежить від того, що ти робиш. (як це стосується Java проти C)
Пітер Лорі

Відповідь на подібні запитання - це "це залежить" - практично для будь-якого порівняння системи X проти системи Y. Плюс це дублікат stackoverflow.com/questions/2479819/…
Джеймс Мур

Відповіді:


261

Скала дозволяє дуже просто використовувати величезні обсяги пам'яті, не усвідомлюючи цього. Зазвичай це дуже потужно, але періодично це може дратувати. Наприклад, припустимо, у вас є масив рядків (називається array) та карта з цих рядків у файли (звані mapping). Припустимо, ви хочете отримати всі файли, які є на карті, і походять із рядків довжиною більше двох. На Java ви можете

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);
}

Вау! Тяжка робота. У Scala найбільш компактний спосіб зробити те саме:

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

Легко! Але, якщо ви досить не знайомі з тим, як працюють колекції, ви, можливо, не усвідомлюєте, що такий спосіб цього створює додатковий проміжний масив (з filter) та додатковий об'єкт для кожного елемента масивуmapping.get, який повертається варіант). Він також створює два функціональних об'єкти (один для фільтра та один для flatMap), хоча це рідко є основною проблемою, оскільки об’єктів функцій мало.

Отже, в основному використання пам'яті на примітивному рівні однакове. Але в бібліотеках Scala є багато потужних методів, які дозволяють вам створювати величезну кількість (як правило, короткочасних) об’єктів дуже легко. Збирач сміття, як правило, дуже гарний з таким сміттям, але якщо ви повністю не зважаючи на те, що використовується пам'ять, ви, швидше за все, зіткнетеся з проблемою в Scala, ніж на Java.

Зауважте, що Scala Код комп’ютерних мов Benchmark Game написаний у досить схожий на Java стилі, щоб отримати схожу на Ява продуктивність і, таким чином, використовувати Java-пам'ять. Це можна зробити в Scala: якщо ви пишете свій код, щоб він виглядав як високопродуктивний код Java, це буде високоефективний код Scala. (Ви можете бути в змозі написати його в більш ідіоматичним стилі Scala і до сих пір отримати хорошу продуктивність, але це залежить від специфіки) .

Варто додати, що за кількість часу, витраченого на програмування, мій код Scala, як правило, швидше, ніж мій код Java, оскільки в Scala я можу отримувати стомлюючі критичні неефективні частини з меншими зусиллями і витрачати більше уваги на оптимізацію алгоритмів і код важливих для продуктивності частин.


172
+1 для цього заключного абзацу. Це важливий момент , що залишилося з розгляду далеко надто часто.
Кевін Райт

2
Я думав, що погляди можуть багато допомогти у питаннях, про які ви згадуєте. Або конкретно це не так з масивами?
Ніколя Пайетт

1
@Kevin Wright - "Це життєво важливий момент, який занадто часто залишається поза увагою" - Це те, що легко сказати і важко продемонструвати, і розкажи нам щось про навички Рекса Керра, а не про те, чого досягають інші менш кваліфіковані.
igouy

1
>> в ідіоматичному стилі з чудовою продуктивністю << У грі з орієнтирами є місце для ідіоматичних програм Scala, які не досягають "чудової" продуктивності.
igouy

2
@RexKerr - чи ваш приклад Java не шукає ключ відображення двічі для кожної можливої ​​рядки, де ваш приклад Scala робить це лише один раз після вибору рядків? Тобто вони оптимізовані по-різному для різних наборів даних?
Сет

103

Я новий користувач, тому я не в змозі додати коментар до відповіді Рекса Керра вище (дозволяти новим користувачам "відповідати", але не "коментувати" - дуже дивне правило btw).

Я підписався просто, щоб відповісти на "феу, Ява настільки багатослівний і така наполеглива праця", накидання популярної відповіді Рекса вище. Хоча ви, звичайно, можете написати більш стислий код Scala, наведений приклад Java чітко роздутий. Більшість розробників Java кодує щось подібне:

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

І звичайно, якщо ми будемо робити вигляд, що Eclipse не робить більшу частину фактичного набору тексту для вас і що кожен збережений персонаж справді робить вас кращим програмістом, то ви можете це кодувати:

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

Тепер я не тільки заощадив час, який знадобився мені, щоб набрати повні назви змінних та фігурних дужок (звільнивши мене витратити ще 5 секунд на продумання глибоких алгоритмічних думок), але я можу також ввести свій код на змаганнях з придушення та потенційно заробляти додаткові гроші на свята.


7
Чому ви не є членом клубу "мова хіп місяця"? Приємні коментарі. Особливо мені сподобалось читати останній абзац.
степанець

21
Чудово кажучи! Я стомлююсь надуманими прикладами, згідно з якими за завищеним кодом Java слідує ретельний побудований короткий приклад Scala (або якась інша мова FP), а потім поспішно зроблений висновок, що Scala має бути кращим за Java через це. Хто все-таки писав щось важливе у Скалі! ;-) І не кажіть Twitter ...
chrisjleu

2
Ну, рішення Рекса попередньо розміщує пам'ять для масиву, що зробить скомпільований код запустити швидше (адже з підходом ви дозволяєте JVM періодично перерозподіляти свій масив у міру його зростання). Незважаючи на те, що було більше задіяних в машинопису, він може бути переможцем.
Ашалінд

5
поки ми в ньому, в java8 це буде:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl

2
Те, що робить Scala деяким способом "кращим", ніж Java, - це розширені можливості системи типів, які полегшують вираження більш загальних шаблонів як типів (наприклад, Monads, Functors тощо). Це дозволяє створювати типи, які не заважають вам через занадто жорсткі контракти, як це часто трапляється на Java. Суворі контракти, що не базуються на дійсних шаблонах у коді, є причиною інверсії відповідальності, необхідної лише для того, щоб правильно перевірити свій код (спочатку приходить на думку залежність вливання та в пекло XML). Доповнення. стислість, яку приносить гнучкість, є лише бонусом.
Джосія

67

Пишіть свою Scala як Java, і ви можете очікувати, що буде випущений майже однаковий байт-код - з майже однаковими показниками.

Напишіть це "ідіоматично", з незмінними об'єктами та функціями вищого порядку, і це буде трохи повільніше і трохи більше. Єдиним винятком із цього правила є використання загальних об'єктів, у яких парами типу використовують@specialised анотацію, це створить ще більший байт-код, який може перевершити продуктивність Java, уникаючи боксу / розпакування.

Також варто згадати той факт, що більше пам'яті / менша швидкість - це неминучий компроміс при написанні коду, який можна запускати паралельно. Ідіоматичний код Scala набагато декларативніший за своєю суттю, ніж типовий код Java, і часто є лише 4 символи (.par ) від того, щоб бути повністю паралельним.

Так що якщо

  • Код Scala займає 1,25x довше, ніж код Java в одному потоці
  • Його можна легко розділити на 4 ядра (зараз це звичайно навіть у ноутбуках)
  • для паралельного часу виконання (1,24 / 4 =) 0,3125x вихідної Java

Ви б тоді сказали, що код Scala зараз порівняно на 25% повільніше, або в 3 рази швидше?

Правильна відповідь залежить від того, як саме ви визначаєте "продуктивність" :)


4
Між іншим, ви можете згадати, що .parце в 2,9.
Рекс Керр

26
>> Ви б сказали, що код Scala зараз порівняно на 25% повільніше, або в 3 рази швидше? << Я б сказав, чому ви не гіпотетичне порівняння з багатопотоковим кодом Java?
igouy

17
@igouy - Справа в тому, що зазначеного гіпотетичного коду не існує, імперативний характер "швидшого" коду Java значно ускладнює паралелізацію, так що співвідношення витрат і вигод означає, що це навряд чи станеться взагалі. Ідіоматична Скала, з іншого боку, будучи набагато декларативнішою за своєю природою, часто може бути узгоджена не більше, ніж тривіальною зміною.
Кевін Райт

7
Наявність паралельних програм Java не означає, що типова програма Java може бути легко адаптована до паралельності. Якщо що-небудь, я б сказав, що конкретний стиль вилки fork є особливо рідкісним на Java і повинен бути чітко закодований, тоді як прості операції, такі як пошук мінімального вмісту, що міститься, або сума значень у колекції можуть бути тривіально виконані паралельно в Scala шляхом простого використання .par.
Кевін Райт

5
Ні, я не можу. Така річ є основоположним складовим елементом для багатьох алгоритмів, і бачити її на такому низькому рівні в мові та стандартних бібліотеках (ті ж стандартні бібліотеки, які будуть використовувати всі програми, а не лише типові) - це свідчення того, що ви ' Ви вже наближаєтесь до одночасності, просто вибираючи мову. Наприклад, відображення колекції за своєю суттю придатне для паралелізації, і кількість програм Scala, які не використовують mapметод, буде зникаючим малим.
Кевін Райт

31

Гра для комп'ютерних мов Бенчмарки:

Тест на швидкість java / scala 1,71 / 2,25

Тест пам'яті java / scala 66.55 / 80.81

Отже, ці орієнтири говорять про те, що java на 24% швидше, а scala використовує на 21% більше пам’яті.

Все-в-всьому, це не велика справа і не має значення в реальних додатках, де більшість часу займає база даних та мережа.

Підсумок: Якщо Scala робить вас та вашу команду (і людей, які займаються проектом, коли ви виходите) більш продуктивними, то вам слід піти на це.


34
Розмір коду java / scala 3.39 / 2.21
hammar

22
Будьте обережні з такими цифрами, вони звучать жахливо точно, тоді як насправді вони майже нічого не означають. Це не так, як якщо Scala завжди в середньому на 24% швидша, ніж Java, і т. Д.
Jesper

3
Цитовані в Афаїку цифри вказують на протилежне: Java на 24% швидша за шкалу. Але, як ви кажете - вони є мікробензиновими позначками, які не повинні відповідати тому, що відбувається в реальних додатках. І різний спосіб або рішення проблеми на різних мовах може призвести до менш порівнянних програм врешті-решт.
користувач невідомий

9
"Якщо Scala зробить вас і вашу команду ..." Підсумок: Ви будете знати, що не раніше :-)
igouy

Сторінка довідки гри з орієнтирами надає приклад того, як "Порівняти швидкість та розмір програми для двох мовних реалізацій". Для Scala та Java підходящою веб-сторінкою порівняння є - shootout.alioth.debian.org/u64q/scala.php
igouy

20

Інші відповіли на це запитання щодо вузьких циклів, хоча, мабуть, очевидна різниця в роботі між прикладами Рекса Керра, яку я прокоментував.

Ця відповідь дійсно орієнтована на людей, які могли б розслідувати необхідність жорсткої оптимізації, як вади дизайну.

Я відносно новачок Скали (приблизно рік і більше), але відчуваю це, поки що, це дозволяє вам відкласти досить легко багато аспектів проектування, реалізації та виконання (з достатньою інформацією для читання та експериментів :)

Особливості відкладеного дизайну:

Відкладені можливості реалізації:

Особливості відкладеного виконання: (вибачте, немає посилань)

  • Нитки безпечні ліниві значення
  • Перейти до імені
  • Монадійські речі

Мені ці особливості допомагають нам пройти шлях до швидких та щільних застосувань.


Приклади Рекса Керра відрізняються тим, які аспекти виконання відкладені. У прикладі Java виділення пам'яті відкладається до тих пір, поки не буде обчислений її розмір, де приклад Scala відкладає пошук відображення. Мені вони здаються зовсім іншими алгоритмами.

Ось що, на мій приклад Java, - це більше яблука до яблук:

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

Немає проміжні колекції, немає Optionпримірників і т.д. Це також не зберігає тип колекції так bigEnough«типу s це Array[File]- Array" s collectреалізація, ймовірно , буде робити що - то вздовж ліній , що робить Java код містера Керра.

Перелічені вище функції відкладеного дизайну також дозволять розробникам API колекції Scala реалізовувати цю швидку специфічну для масиву реалізацію збирання у майбутніх випусках, не порушуючи API. Це те, про що я маю на увазі, ступаючи шлях до швидкості.

Також:

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

withFilterМетод , який я використовував тут замість filterвиправлення проміжної проблеми збору , але є ще проблема примірника Option.


Один із прикладів простої швидкості виконання в Scala - це ведення журналів.

На Java ми можемо написати щось на зразок:

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

У Scala це просто:

logger.debug("trace")

тому що параметр повідомлення для налагодження в Scala має тип " => String", який я вважаю функцією без параметрів, яка виконується при її оцінці, але яку документація називає "пройти по імені".

EDIT {Функції в Scala - це об'єкти, тому тут є додатковий об'єкт. Для моєї роботи вагу тривіального об’єкта варто усунути можливість журнального повідомлення, яке зайве оцінювати. }

Це не робить код швидшим, але це робить його швидше швидшим, і ми рідше матимемо досвід перегляду та очищення коду інших людей масово.

На мою думку, це відповідна тема Scala.


Жорсткий код не вдається зрозуміти, чому Scala швидше, хоча це трохи натякає.

Я вважаю, що це поєднання повторного використання коду та межі якості коду у Scala.

На Яві дивовижний код часто змушений стати незрозумілим безладом, тому він не є справді життєздатним в межах API якості виробництва, оскільки більшість програмістів не зможе ним користуватися.

Я сподіваюся, що Scala може дозволити einsteins серед нас реалізувати набагато більш компетентні API, потенційно виражені через DSL. Основні API в Scala вже знаходяться на цьому шляху.


Ваш запис даних є хорошим прикладом для підводних каменів Scala: logger.debug ("слід") створює новий об'єкт для функції без параметрів.
jcsahnwaldt Відновлення Моніки

Справді - як це впливає на мою пов’язану точку?
Сет

Вищезазначені об'єкти також можуть бути використані для виготовлення прозорих структур управління ІОК задля ефективності. Так, той же результат теоретично можливий і на Java, але це було б щось, що різко вплинуло / заплутало спосіб написання коду - отже, мій аргумент, що спритність Скали за відстрочку багатьох елементів розробки програмного забезпечення допомагає нам рухатися до швидшого коду - швидше за все швидше на практиці порівняно з кращою швидкістю роботи пристрою.
Сет

Гаразд, я перечитав це, і я написав "просту швидкість виконання" - я додаду примітку. Хороший вопрос :)
Сет

3
Передбачувано, якщо оператор (в основному вільний на суперскалярному процесорі) проти розподілу об'єктів + ​​сміття. Код Java, очевидно, швидше (зауважте, що він оцінює лише умову, виконання не досягне оператора журналу). . "
Елофф


10

І Java, і Scala збираються до байт-коду JVM, тому різниця не така вже й велика. Найкраще порівняння, яке ви можете отримати, - це, мабуть, в грі з орієнтирами на комп'ютерній мові , яка, по суті, говорить про те, що і Java, і Scala мають однакове використання пам'яті. Скала лише незначна деякими з перелічених еталонів повільніше, ніж Java, але це може бути просто тому, що реалізація програм відрізняється.

Дійсно, хоча вони обоє так близько, що про це не варто турбуватися. Підвищення продуктивності, яке ви отримуєте, використовуючи більш виразну мову, як-от Scala, коштує набагато більше, ніж мінімальний (якщо такий є) показник ефективності.


7
Я бачу тут логічну помилку: обидві мови складаються в байт-код, але досвідчений програміст і новачок - їхній код також складається до байт-коду - але не до того ж байт-коду, тому висновок, що різниця не може бути такою великою , може помилятися. Насправді, у колишні часи цикл у той час, як масштаб, міг би бути набагато, набагато швидшим, ніж семантично еквівалентний цикл (якщо я правильно пам’ятаю, сьогодні він набагато кращий). І те, і інше було складено до байт-коду, звичайно.
користувач невідомий

@user невідомо - "цикл while може бути набагато, набагато швидшим у масштабі масштабу, ніж семантично еквівалентний цикл", - зауважте, що ці ігрові програми Scala орієнтири записуються за допомогою циклів.
igouy

@igouy: Я не говорив про результати цього мікробігу, а про аргументацію. Справжнє твердження, Java and Scala both compile down to JVM bytecode, яке поєднувалося із soзаявою, про яку diffence isn't that big.я хотів показати, що soце лише риторична хитрість, а не аргументативний висновок.
користувач невідомий

3
напрочуд неправильна відповідь напрочуд високими голосами.
shabunc

4

Приклад Java насправді не є ідіомою для типових прикладних програм. Такий оптимізований код може бути знайдений у методі системної бібліотеки. Але тоді він буде використовувати масив потрібного типу, тобто File [], і не кидає IndexOutOfBoundsException. (Різні умови фільтра для підрахунку та додавання). Моя версія буде (завжди (!) З фігурними дужками, тому що я не люблю витрачати годину на пошук помилки, яку було введено, заощадивши дві секунди, щоб натиснути одну клавішу в 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);
    }
  }
}

Але я можу принести вам багато інших потворних прикладів коду Java з мого поточного проекту. Я намагався уникати поширеного стилю кодування та модифікації, розбиваючи загальні структури та поведінку.

У своєму абстрактному базовому класі DAO у мене є абстрактний внутрішній клас для загального механізму кешування. Для кожного типу об'єктів конкретної моделі існує підклас абстрактного базового класу DAO, в якому внутрішній клас підкласифікований, щоб забезпечити реалізацію методу, який створює бізнес-об'єкт при його завантаженні з бази даних. (Ми не можемо використовувати інструмент ORM, оскільки ми отримуємо доступ до іншої системи за допомогою власного API.)

Цей код підкласифікації та інстанції зовсім не зрозумілий на Яві і був би дуже читабельним у Scala.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.