Я прагну вивчити Scala, і у мене є одне основне запитання, на яке я не можу знайти відповіді: загалом, чи є різниця у продуктивності та використанні пам'яті між Scala та Java?
Я прагну вивчити Scala, і у мене є одне основне запитання, на яке я не можу знайти відповіді: загалом, чи є різниця у продуктивності та використанні пам'яті між Scala та Java?
Відповіді:
Скала дозволяє дуже просто використовувати величезні обсяги пам'яті, не усвідомлюючи цього. Зазвичай це дуже потужно, але періодично це може дратувати. Наприклад, припустимо, у вас є масив рядків (називається 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 я можу отримувати стомлюючі критичні неефективні частини з меншими зусиллями і витрачати більше уваги на оптимізацію алгоритмів і код важливих для продуктивності частин.
Я новий користувач, тому я не в змозі додати коментар до відповіді Рекса Керра вище (дозволяти новим користувачам "відповідати", але не "коментувати" - дуже дивне правило 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 секунд на продумання глибоких алгоритмічних думок), але я можу також ввести свій код на змаганнях з придушення та потенційно заробляти додаткові гроші на свята.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
Пишіть свою Scala як Java, і ви можете очікувати, що буде випущений майже однаковий байт-код - з майже однаковими показниками.
Напишіть це "ідіоматично", з незмінними об'єктами та функціями вищого порядку, і це буде трохи повільніше і трохи більше. Єдиним винятком із цього правила є використання загальних об'єктів, у яких парами типу використовують@specialised
анотацію, це створить ще більший байт-код, який може перевершити продуктивність Java, уникаючи боксу / розпакування.
Також варто згадати той факт, що більше пам'яті / менша швидкість - це неминучий компроміс при написанні коду, який можна запускати паралельно. Ідіоматичний код Scala набагато декларативніший за своєю суттю, ніж типовий код Java, і часто є лише 4 символи (.par
) від того, щоб бути повністю паралельним.
Так що якщо
Ви б тоді сказали, що код Scala зараз порівняно на 25% повільніше, або в 3 рази швидше?
Правильна відповідь залежить від того, як саме ви визначаєте "продуктивність" :)
.par
це в 2,9.
.par
.
map
метод, буде зникаючим малим.
Гра для комп'ютерних мов Бенчмарки:
Тест на швидкість java / scala 1,71 / 2,25
Тест пам'яті java / scala 66.55 / 80.81
Отже, ці орієнтири говорять про те, що java на 24% швидше, а scala використовує на 21% більше пам’яті.
Все-в-всьому, це не велика справа і не має значення в реальних додатках, де більшість часу займає база даних та мережа.
Підсумок: Якщо Scala робить вас та вашу команду (і людей, які займаються проектом, коли ви виходите) більш продуктивними, то вам слід піти на це.
Інші відповіли на це запитання щодо вузьких циклів, хоча, мабуть, очевидна різниця в роботі між прикладами Рекса Керра, яку я прокоментував.
Ця відповідь дійсно орієнтована на людей, які могли б розслідувати необхідність жорсткої оптимізації, як вади дизайну.
Я відносно новачок Скали (приблизно рік і більше), але відчуваю це, поки що, це дозволяє вам відкласти досить легко багато аспектів проектування, реалізації та виконання (з достатньою інформацією для читання та експериментів :)
Особливості відкладеного дизайну:
Відкладені можливості реалізації:
Особливості відкладеного виконання: (вибачте, немає посилань)
Мені ці особливості допомагають нам пройти шлях до швидких та щільних застосувань.
Приклади Рекса Керра відрізняються тим, які аспекти виконання відкладені. У прикладі 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 вже знаходяться на цьому шляху.
Презентація @higherkinded на цю тему - міркування щодо продуктивності Scala, яка робить деякі порівняння Java / Scala.
Інструменти:
Чудовий блог:
І Java, і Scala збираються до байт-коду JVM, тому різниця не така вже й велика. Найкраще порівняння, яке ви можете отримати, - це, мабуть, в грі з орієнтирами на комп'ютерній мові , яка, по суті, говорить про те, що і Java, і Scala мають однакове використання пам'яті. Скала лише незначна деякими з перелічених еталонів повільніше, ніж Java, але це може бути просто тому, що реалізація програм відрізняється.
Дійсно, хоча вони обоє так близько, що про це не варто турбуватися. Підвищення продуктивності, яке ви отримуєте, використовуючи більш виразну мову, як-от Scala, коштує набагато більше, ніж мінімальний (якщо такий є) показник ефективності.
Java and Scala both compile down to JVM bytecode,
яке поєднувалося із so
заявою, про яку diffence isn't that big.
я хотів показати, що so
це лише риторична хитрість, а не аргументативний висновок.
Приклад 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.