Продуктивність C ++ порівняно з Java / C #


119

Я розумію, що C / C ++ виробляє нативний код для роботи на певній архітектурі машини. І навпаки, такі мови, як Java та C #, працюють над віртуальною машиною, яка абстрагує рідну архітектуру. Логічно здається, що для Java або C # неможливо порівняти швидкість C ++ через цей проміжний крок, однак мені сказали, що останні компілятори ("гаряча точка") можуть досягти цієї швидкості або навіть перевищити її.

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


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

Деякі фактичні вимірювання, щоб перевірити, перш ніж прочитати багато дуже невмілих теорій, у цих відповідях: shootout.alioth.debian.org/u32/…
Justicle

Відповіді:


178

Як правило, C # і Java можуть бути настільки ж швидкими або швидшими, тому що компілятор JIT - компілятор, який компілює ваш IL вперше, коли він виконується - може зробити оптимізацію, яку компільована програма C ++ не може, тому що може запитувати машину. Він може визначити, чи машина є Intel чи AMD; Pentium 4, Core Solo або Core Duo; або якщо підтримує SSE4 тощо

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

Крім того, певні мовні функції дозволяють компілятору в C # та Java робити припущення щодо вашого коду, що дозволяє йому оптимізувати певні частини, які просто не безпечні для компілятора C / C ++. Коли у вас є доступ до покажчиків, існує безліч оптимізацій, які просто не є безпечними.

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

Зараз я не можу говорити на Java в наступному пункті, але я знаю, що, наприклад, C # фактично видалить методи та виклики методів, коли знає, що тіло методу порожнє. І він використовуватиме таку логіку у всьому коді.

Отже, як ви бачите, є безліч причин, чому певні реалізації C # або Java будуть швидшими.

Тепер все це сказано, в C ++ можуть бути зроблені спеціальні оптимізації, які підірвуть все, що можна зробити з C #, особливо в графічній царині та в будь-який час, коли ти близький до обладнання. Тут покажчики творять чудеса.

Тож залежно від того, що ви пишете, я б пішов з тим чи іншим. Але якщо ви пишете щось, що не залежить від апаратного забезпечення (драйвер, відеоігра тощо), я б не хвилювався про продуктивність C # (знову не можу говорити про Java). Це буде добре.

Сторона Java, @Swati вказує на гарну статтю:

https://www.ibm.com/developerworks/library/j-jtp09275


Ваші міркування є хибними - програми C ++ будуються для їх цільової архітектури, їм не потрібно перемикатися під час виконання.
Justicle

3
@Justicle Найкращий компілятор c ++ запропонує для різних архітектур, як правило, x86, x64, ARM та ін. Тепер ви можете сказати йому використовувати конкретні функції (скажімо, SSE2), і якщо вам пощастить, він навіть генерує якийсь резервний код, якщо ця функція недоступна, але це приблизно так дрібно, як можна отримати. Звичайно, немає спеціалізації залежно від розмірів кешу та чого іншого.
Voo

4
Див. Shootout.alioth.debian.org/u32/…, щоб приклади цієї теорії не відбувалися.
Justicle

1
Якщо чесно, це одна з найгірших відповідей. Це так необгрунтовано, що я міг би просто його перевернути. Занадто багато узагальнення, занадто багато невідомого (оптимізація порожніх функцій - це справді лише верхівка айсберга). Один розкішний компілятор C ++ має: Час. Ще одна розкіш: перевірка не застосовується. Але знайти більш stackoverflow.com/questions/145110/c-performance-vs-java-c / ... .
Себастьян Мах

1
@OrionAdrian ок, ми зараз повне коло ... Див. Shootout.alioth.debian.org/u32/…, щоб приклади цієї теорії не відбувалися. Іншими словами, покажіть нам, що ваша теорія може бути доведена правильною, перш ніж робити неясні спекулятивні твердження.
Justicle

197

JIT проти статичного компілятора

Як уже було сказано в попередніх публікаціях, JIT може компілювати IL / байт-код у нативний код під час виконання. Вартість цього була згадана, але не до її завершення:

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

Звичайно, C # (або Java, або VB) зазвичай швидше виробляють життєздатне і надійне рішення, ніж це C ++ (хоча б тому, що C ++ має складну семантику, а стандартна бібліотека C ++, хоча і цікава і потужна, досить бідна порівняно з повною сфера стандартної бібліотеки від .NET або Java), тому зазвичай різниця між C ++ і .NET або Java JIT не буде видимою для більшості користувачів, а для тих бінарних файлів, які є критичними, добре, ви все одно можете викликати обробку C ++ від C # або Java (навіть якщо цей вид натільних дзвінків сам по собі може бути досить дорогим) ...

C ++ метапрограмування

Зауважте, що зазвичай ви порівнюєте код виконання C ++ з його еквівалентом у C # або Java. Але у C ++ є одна особливість, яка може перевершити Java / C # поза полем, це метапрограмування шаблонів: Обробка коду буде виконана під час компіляції (таким чином, значно збільшується час компіляції), що призводить до нульового (або майже нульового) часу виконання.

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

Редагувати 2011-06-10: У C ++ гра з типами проводиться під час компіляції, тобто виробляється загальний код, який викликає негенеричний код (наприклад, загальний аналізатор від рядка до типу T, викликаючи стандартний API бібліотеки для типів T, який він розпізнає, і зробити парсер легко розширюваним його користувачем) дуже легко і дуже ефективно, тоді як еквівалент в Java або C # в кращому випадку болісний для написання, і завжди буде повільнішим і вирішується під час виконання, навіть коли типи відомі під час компіляції, Це означає, що ваша єдина надія полягає в тому, щоб СІТ все це розкрив.

...

Редагувати 2011-09-20: Команда Blitz ++ ( Домашня сторінка , Вікіпедія ) пішла цим шляхом, і, мабуть, їх мета - досягти ефективності FORTRAN на наукових розрахунках, максимально перемістившись від виконання програми до часу компіляції за допомогою метапрограмування шаблонів C ++. . Тож « я ще не бачив, як реально впливає на це життя », про яку я писав вище, мабуть , існує в реальному житті.

Нативне використання пам'яті C ++

C ++ має використання пам'яті, відмінну від Java / C #, і, таким чином, має різні переваги / недоліки.

Незалежно від оптимізації JIT, нічого не пройде швидко, як прямий доступ до пам’яті (давайте на хвилину ігноруємо кеші процесора тощо). Отже, якщо у вас є суміжні дані в пам'яті, доступ до них за допомогою покажчиків C ++ (тобто, покажчиків C ... Давайте надамо цезарю належне) піде в рази швидше, ніж у Java / C #. І у C ++ є RAII, що робить обробку набагато простішою, ніж у C # або навіть на Java. C ++ не потребує usingобсягу існування своїх об'єктів. А у C ++ немає finallyзастереження. Це не помилка.

:-)

І незважаючи на C # примітивні структури, об'єкти C ++ "на стеці" нічого не коштуватимуть при розподілі та знищенні, і GC не потребуватиме роботи в незалежній потоці для очищення.

Що стосується фрагментації пам’яті, то розподільники пам’яті 2008 року - це не старі алокатори пам’яті 1980 року, які зазвичай порівнюються з розподілом GC: C ++ не можна переміщувати в пам’яті, правда, але тоді, як у файловій системі Linux: Кому потрібен жорсткий диск дефрагментація, коли фрагментації не відбувається? Використання правильного алокатора для правильного завдання повинно бути частиною набору інструментів для розробників C ++. Зараз написати алокатори непросто, і тоді, у більшості з нас є кращі справи, і для більшої частини використання RAII або GC - це більш ніж добре.

Редагувати 2011-10-04: Для прикладів щодо ефективних розподільників. На платформах Windows, починаючи з Vista, Heap Fragmentation Heap увімкнено за замовчуванням. Для попередніх версій LFH можна активувати, викликавши функцію WinAPI HeapSetInformation ). На інших ОС передбачені альтернативні розподільники (дивhttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc для списку)

Тепер модель пам'яті дещо ускладнюється із зростанням багатоядерної та багатопотокової технологій. Я думаю, що в цьому полі перевагу .NET має перевагу, і Java, як мені сказали, тримала верхню позицію. Деяким хакерам "на голий метал" легко похвалити його код "біля машини". Але зараз скласти кращу збірку вручну набагато складніше, ніж дозволити компілятору свою роботу. Для C ++ компілятор ставав, як правило, кращим, ніж хакер, починаючи з десятиліття. Для C # та Java це ще простіше.

І все-таки новий стандарт C ++ 0x накладе просту модель пам’яті компіляторам C ++, яка стандартизує (і тим самим спростить) ефективний багатопроцесорний / паралельний / нарізаючий код у C ++ та зробить оптимізацію простішою та безпечнішою для компіляторів. Але потім, ми побачимо через кілька років, чи виконують його обіцянки.

C ++ / CLI проти C # / VB.NET

Примітка. У цьому розділі я говорю про C ++ / CLI, тобто C ++, розміщений у .NET, а не на рідному C ++.

Минулого тижня я пройшов тренінг з оптимізації .NET і виявив, що статичний компілятор дуже важливий у будь-якому випадку. Як важливіше, ніж JIT.

Сам же код, складений в C ++ / CLI (або його предка, керований C ++), може бути в рази швидшим, ніж той самий код, що створюється в C # (або VB.NET, компілятор якого виробляє той же IL, що і C #).

Оскільки статичний компілятор C ++ був набагато кращим для створення вже оптимізованого коду, ніж для C #.

Наприклад, функція, вбудована в .NET, обмежується функціями, байт-код яких менше або дорівнює 32 байтам. Отже, деякий код у C # створить 40-байтний аксесуар, який JIT ніколи не накреслить. Цей же код у C ++ / CLI створить 20-байтний аксесуар, який буде накреслений JIT.

Інший приклад - тимчасові змінні, які просто компілюються компілятором C ++, при цьому все ще згадуються в IL, що виробляється компілятором C #. Статична оптимізація компіляції C ++ призведе до меншої кількості коду, що дозволить знову агресивніше оптимізувати JIT.

Причиною цього вважалося, що компілятор C ++ / CLI отримав прибуток від величезних методів оптимізації від власного компілятора C ++.

Висновок

Я люблю C ++.

Але наскільки я це бачу, C # або Java - в цілому краще. Не тому, що вони швидші за C ++, а тому, що, коли ви додаєте їхні якості, вони в кінцевому підсумку стають більш продуктивними, потребують меншої кількості тренувань та мають більш повні стандартні бібліотеки, ніж C ++. А що стосується більшості програм, то їх різниця в швидкості (так чи інакше) буде незначною ...

Редагувати (06.06.2011)

Мій досвід роботи на C # /. NET

Зараз у мене вже 5 місяців майже ексклюзивного професійного кодування C # (що додає до мого резюме вже повне C ++ та Java, а також C ++ / CLI).

Я грав з WinForms (Ahem ...) і WCF (круто!), І WPF (Cool !!!! Як через XAML, так і з сирої C #. WPF - це так просто, я вважаю, що Swing просто не може з цим порівнятись) і C # 4.0.

Висновок полягає в тому, що хоча простіше / швидше створити код, який працює в C # / Java, ніж у C ++, набагато складніше створити міцний, безпечний і надійний код у C # (і навіть складніше в Java), ніж у C ++. Причин багато, але їх можна підсумувати:

  1. Загальна версія не настільки потужна, як шаблони ( спробуйте написати ефективний загальний метод Parse (від рядка до T) або ефективний еквівалент boost :: lexical_cast в C #, щоб зрозуміти проблему )
  2. RAII залишається незрівнянним ( GC все ще може просочитися (так, мені довелося впоратися з цією проблемою) і буде працювати з пам'яттю. Навіть C # usingне так просто і потужно, тому що писати правильні реалізації Dispose складно )
  3. C # readonlyі Java finalніде не є такими корисними, як C ++const ( Немає можливості викрити лише складні дані (наприклад, Дерево вузлів) у C # без величезної роботи, хоча це вбудована функція C ++. Невідмінні дані - цікаве рішення , але не все можна зробити непорушним, так що, навіть далеко, далеко ).

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

Java ще більше засмучує, оскільки у неї є ті самі проблеми, що і у C #, і багато іншого: Не вистачаючи еквівалента usingключового слова C # , мій дуже кваліфікований колега витратив занадто багато часу, переконуючись, що його ресурси звільнені правильно, тоді як еквівалент C ++ мав би було легко (за допомогою деструкторів та розумних покажчиків).

Тому я здогадуюсь, що підвищення продуктивності C # / Java видно для більшості кодів ... до дня, коли вам потрібен код, щоб бути максимально досконалим. Того дня ви будете знати біль. (Ви не повірите, що запитують від нашого сервера та GUI-додатків ...).

Про сервер Java та C ++

Я підтримував зв’язок із серверними командами (я працював 2 роки серед них, перш ніж повернутися до команди GUI), з іншого боку будівлі, і дізнався щось цікаве.

Останніми роками тенденція полягала в тому, щоб додатки для сервера Java були призначені замінити старі серверні програми C ++, оскільки Java має багато рамок / інструментів, і їх легко обслуговувати, розгортати тощо тощо.

... Доки проблема з низькою затримкою не виросла свою потворну голову в останні місяці. Тоді програми Java-сервера, незалежно від того, яку оптимізацію намагалася наша кваліфікована команда Java, просто та чисто програли гонку проти старого, не дуже оптимізованого сервера C ++.

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

Висновок

Ніщо не є таким простим, як очікувалося.

Java і навіть більше C # - це круті мови, з великими стандартними бібліотеками та фреймворками, де ви можете швидко вводити коди та мати результат дуже скоро.

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

Це як би вам потрібно менше часу та менш досвідчені розробники на C # / Java, ніж у C ++, щоб створити код середньої якості, але, з іншого боку, у той момент, коли вам знадобився відмінний для вдосконалення коду якості, раптом було простіше та швидше отримати результати прямо в C ++.

Звичайно, це моє власне сприйняття, можливо, обмежене нашими конкретними потребами.

Але все-таки це відбувається сьогодні, як в командах GUI, так і в командах на сервері.

Звичайно, я оновлю цю посаду, якщо трапиться щось нове.

Редагувати (2011-06-22)

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

[...] Версія Java, мабуть, була найпростішою у здійсненні, але найскладнішою для аналізу продуктивності. Зокрема, ефекти навколо вивезення сміття були складними і дуже важко налаштовані ".

Джерела:

Редагувати (2011-09-20)

"Слово у Facebook полягає в тому, що" розумно написаний код C ++ просто працює швидко ", що підкреслює величезні зусилля, витрачені на оптимізацію PHP та Java-коду. Як не парадоксально, C ++ код важче написати, ніж на інших мовах, але ефективний код - це набагато простіше [писати на C ++, ніж іншими мовами]. "

- Herb Sutter у // build / , цитуючи Андрія Олександреску

Джерела:


8
Ви редагуєте через 5 місяців C # описує саме мій власний досвід (шаблони краще, краще, RAII). +1. Ці троє залишаються моїми особистими вбивчими властивостями для C ++ (або D, на що я ще не мала часу)
Себастьян Мах

"Обробка коду буде здійснена під час компіляції". Отже, метапрограмування шаблонів працює лише в програмі під час компіляції, що часто не буває так, наприклад, неможливо написати бібліотеку регулярних виразів конкурентоспроможної мови у ванілі C ++, оскільки вона не здатна генерувати код часу виконання (важливий аспект метапрограмування).
JD

"гра з типами проводиться під час компіляції ... еквівалент в Java або C # - в кращому випадку болісно писати, і завжди буде повільніше і вирішується під час виконання, навіть коли типи відомі під час компіляції". У C # це справедливо лише для типів посилань і не відповідає дійсним типам значень.
JD

1
"Незалежно від оптимізації JIT, нічого не пройде швидко, оскільки прямий доступ до пам’яті вказівник ... якщо у вас є суміжні дані в пам'яті, доступ до них за допомогою покажчиків C ++ (тобто, покажчиків C ... Давайте надамо Цезареві належне) піде в рази швидше, ніж у Java / C # ". Люди спостерігали, як Java тестує C ++ на тесті SOR з еталону SciMark2 саме тому, що покажчики перешкоджають оптимізаціям, пов'язаним із збудженням. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

Також варто відзначити, що .NET вводить спеціалізацію дженериків у динамічно пов'язаних бібліотеках після посилання, тоді як C ++ не може, оскільки шаблони повинні бути вирішені перед посиланням. І очевидно, що велика перевага дженериків перед шаблонами - це зрозумілі повідомлення про помилки.
JD

48

Кожен раз, коли я розмовляв із керованою та некерованою продуктивністю, я хотів би вказати на серію, в якій Ріко (і Реймонд) порівнювали C ++ та C # версії китайсько-англійського словника. Цей пошук у Google дозволить вам прочитати для себе, але мені подобається резюме Ріко.

Тож я соромлюсь своєї нищівної поразки? Навряд чи. Керований код отримав дуже хороший результат за майже будь-які зусилля. Щоб перемогти керованого Реймонда довелося:

  • Напишіть власні файли вводу-виводу
  • Напишіть свій власний клас струн
  • Напишіть власний розподільник
  • Написати власне міжнародне картографування

Звичайно, він використовував доступні бібліотеки нижчого рівня для цього, але це ще велика робота. Чи можете ви назвати те, що залишилося програмою STL? Я не думаю, що так, я думаю, що він тримав клас std :: vector, що в кінцевому підсумку ніколи не було проблемою, і він зберігав функцію пошуку. Досить багато всього іншого.

Отже, так, ви можете безперечно перемогти CLR. Раймонд може зробити його програму ще швидшою, я думаю.

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

Для мене суть полягає в тому, що для керованої версії, що була простим портом оригінального некерованого коду, знадобилося 6 версій для керованої версії. Якщо вам потрібен кожен останній шматочок продуктивності (і у вас є час і досвід, щоб отримати це), вам доведеться залишатися некерованими, але для мене я прийму перевагу на порядок у перших версіях над 33 % Я отримую, якщо спробую 6 разів.


3
посилання мертве, знайдено згадану статтю тут: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Перш за все, якщо ми подивимось на код Реймонда Чена, він явно не дуже добре розуміє C ++ або структури даних. Його код майже досягає прямого коду низького рівня навіть у тих випадках, коли код C не має переваг щодо продуктивності (він просто здається, що це якась недовіра та, можливо, недостатнє знання, як користуватися профілями). Він також не зрозумів найбільш алгоритмічно обгрунтований спосіб реалізації словника (він використовував std :: find заради Христа). Якщо є щось хороше в Java, Python, C # і т. Д. - всі вони надають дуже ефективні словники ...
stinky472

Пробні або навіть std :: map будуть набагато вигіднішими щодо C ++ або навіть хеш-таблиці. Нарешті, словник - це саме той тип програми, який найбільше виграє від бібліотек та фреймворків високого рівня. Це не демонструє відмінностей у мові настільки, як бібліотеки, що займаються (з яких, я б із задоволенням сказав, що C # набагато повніше і надає набагато більше інструментів, придатних для виконання завдання). Покажіть програму, яка маніпулює великими блоками пам'яті порівняно, як масштабний матричний / векторний код. Це вирішиться досить швидко, навіть якщо, як і в цьому випадку, кодери не знають, що ...
stinky472

26

Компіляція для конкретних оптимізацій процесора зазвичай завищена. Просто візьміть програму на C ++ і компілюйте з оптимізацією для pentium PRO і запустіть на pentium 4. Потім перекомпілюйте з оптимізацією для pentium 4. Я пройшов довгі години, роблячи це з кількома програмами. Загальні результати ?? Зазвичай менше, ніж на 2-3% збільшується продуктивність. Тож теоретичних переваг JIT майже немає. Більшість відмінностей у роботі можна спостерігати лише при використанні функцій скалярної обробки даних, що, зрештою, потребуватиме ручного точного налаштування для досягнення максимальної продуктивності. Оптимізації такого роду виконуються повільно і дорого, що робить їх часом непридатними для JIT.

У реальному світі та в реальному застосуванні C ++ все ще швидше, ніж java, головним чином через легший слід пам'яті, що призводить до кращої продуктивності кешу.

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


4
Ви не стільки збираєтеся для оптимізації процесора, скільки ви збираєтеся для оптимізації шляху виконання. Якщо ви виявите, що метод дуже часто викликається певним параметром, ви можете заздалегідь скласти цю процедуру з таким параметром як константа, яка могла б (у випадку булевого режиму, який керує потоком) виводити гігантські куски роботи. C ++ не може наблизитися до такої оптимізації.
Білл К

1
То як же JIT робити при перекомпілюванні процедур, щоб скористатися спостережуваними пробіжками, і наскільки це різниця?
Девід Торнлі

2
@ Можливо, я можу змішувати дві речі ... але чи не прогнозування гілок, здійснене під час виконання інструментальної програми, не досягає подібних цілей незалежно від мови?
Харді

@ Гарді так, центральний процесор може передбачати гілки незалежно від мови, але він не може визначити весь цикл, спостерігаючи, що цикл не впливає ні на що. Він також не помітить, що мульти (0) є провідним для повернення 0 і просто замінить весь виклик методу на if (param == 0) результат = 0; і уникати всієї функції / виклику методу. C міг би зробити це, якщо компілятор мав вичерпний огляд того, що відбувається, але, як правило, він не має достатньої кількості інформації під час компіляції.
Білл К

21

JIT (Just In Time Compiling) може бути неймовірно швидким, оскільки він оптимізується для цільової платформи.

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

Основна концепція .NET JIT працює так (значно спрощено):

Виклик методу вперше:

  • Ваш програмний код викликає метод Foo ()
  • CLR розглядає тип, який реалізує Foo () і отримує пов'язані з ним метадані
  • З метаданих CLR знає, в якій пам'яті зберігається IL-адреса (проміжний байт-код).
  • CLR виділяє блок пам'яті і викликає JIT.
  • JIT компілює IL в нативний код, розміщує його у виділеній пам'яті, а потім змінює вказівник функції у метаданих типу Foo (), щоб вказати на цей нативний код.
  • Рідний код запускається.

Виклик методу вдруге:

  • Ваш програмний код викликає метод Foo ()
  • CLR розглядає тип, який реалізує Foo () і знаходить покажчик функції у метаданих.
  • Народний код у цьому місці пам'яті запускається.

Як бачите, вдруге навколо, це практично той самий процес, що і C ++, за винятком переваг оптимізацій у режимі реального часу.

Однак, є й інші накладні проблеми, які сповільнюють керовану мову, але JIT дуже допомагає.


До речі, Джонатане, я думаю, хтось все-таки заперечує твої речі. Коли я проголосував за вас, у вас на цій посаді був -1.
Брайан Р. Бонді,

12

Мені подобається відповідь Оріона Адріана , але в цьому є інший аспект.

Це ж питання було поставлене десятиліттями тому щодо мови асемблера проти "людських" мов, як FORTRAN. І частина відповіді схожа.

Так, програма C ++ здатна бути швидшою за C # за будь-яким заданим (нетривіальним?) Алгоритмом, але програма в C # часто буде настільки ж швидкою або швидшою, ніж "наївна" реалізація в C ++ і оптимізована версія в C ++ буде потрібно більше часу, щоб розробити, і, можливо, все-таки перемогти C # версію дуже невеликим запасом. Отже, чи дійсно воно того варте?

На це запитання вам доведеться відповісти по одному.

Це сказав, що я давно прихильник C ++, і я думаю, що це неймовірно виразна і потужна мова - іноді недооцінена. Але в багатьох проблемах "реального життя" (особисто мені це означає "вид, за який я плачу, щоб вирішити"), C # отримає роботу швидше і безпечніше.

Найбільший штраф, який ви платите? Багато програм .NET і Java - це пристрасті пам’яті. Я бачив програми .NET і Java займають "сотні" мегабайт пам'яті, коли програми C ++ подібної складності ледь не дряпають "десятки" МБ.


7

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

Подумайте про складений код Java як інтерпретовану машинну мову для JVM. Коли процесор Hotspot помічає, що певні фрагменти складеного коду будуть використовуватися багато разів, він виконує оптимізацію машинного коду. Оскільки вручну налаштування збірки майже завжди швидше, ніж компільований код C ++, добре зрозуміти, що програмно налаштований машинний код не буде занадто поганим.

Отже, для дуже повторюваного коду я міг бачити, де можна було б, щоб Hotspot JVM запустив Java швидше, ніж C ++ ..., поки збирання сміття не вступить у гру. :)


Чи можете ви розширити твердження Since hand-tuning Assembly is almost always faster than C++ compiled code? Що ви маєте на увазі під "налаштуванням вручну" і "компільованим кодом C ++"?
paercebal

Ну, це базується на ідеї, що оптимізатор компілятора дотримується правил, а кодери - ні. Тому завжди буде код, який оптимізатор вважає, що він не може оптимізувати ідеально, тоді як людина може, дивлячись на більшу картину чи знаючи більше про те, що насправді робить код. Я додам, що це 3-річний коментар, і я знаю більше про HotSpot, ніж раніше, і я легко бачу, як динамічна оптимізація ДУЖЕ хороший спосіб швидшого запуску коду.
billjamesdev

1. Оптимізація з Hotspot або будь-якого іншого JIT все ще є оптимізацією компілятора. JIT має перевагу перед статичним компілятором у тому, щоб мати можливість вбудовувати деякі результати (код часто називають) або навіть робити оптимізацію на основі виконуваного процесора, але це все ж оптимізація компілятора. . . 2. Гадаю, ви говорите про оптимізацію алгоритму, а не про "тонку настройку збірки". "ручна тонка настройка за допомогою людського кодера" не дала кращих результатів, ніж оптимізація компілятора вже більше десяти років. Насправді людина, що грає в збори, зазвичай
відкручує

Гаразд, я розумію, що я використовую неправильну термінологію, "оптимізацію компілятора", а не "статичну оптимізацію". Я хотів би зазначити, що, принаймні, в ігровій індустрії, так як нещодавно для PS2 ми все ще використовували вручну кодовану збірку місцями, щоб "оптимізувати" конкретні чіпи, про які ми знали, що знаходяться на консолі; крос-компілятори для цих нових мікросхем ще не такі складні, як архітектури x86. Повернутися до початкового питання вище: JIT має користь перед тим, як можна виміряти до оптимізації, що є хорошою річчю (TM)
billjamesdev

Зауважте, що більшість виробничих GC також використовують рукописний асемблер, оскільки C / C ++ не перерізає його.
JD

6

Як правило, алгоритм вашої програми буде набагато важливішим для швидкості вашої програми, ніж мова . Ви можете реалізувати поганий алгоритм будь-якою мовою, включаючи C ++. Зважаючи на це, ви, як правило, зможете швидше писати прогони коду мовою, яка допоможе вам реалізувати більш ефективний алгоритм.

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

Крім того, C ++ наздоганяє такі "нові" (відзначте цитати) функції, як контейнери STL, автоматичні вказівники тощо - див., Наприклад, бібліотеку підвищення. І іноді ви можете виявити, що найшвидший спосіб виконати якесь завдання вимагає такої техніки, як арифметика вказівника, заборонена мовою вищого рівня, хоча вони, як правило, дозволяють викликати бібліотеку, написану мовою, яка може реалізувати її за бажанням .

Головне - знати мову, якою ви користуєтесь, це пов’язаний API, що він може робити та які обмеження є.


5

Я навіть не знаю ... мої програми Java завжди повільні. :-) Хоча я ніколи не помічав, що програми C # особливо повільні.


4

Ось ще один показник інтерстингу, який ви можете спробувати на власному комп’ютері.

Він порівнює ASM, VC ++, C #, Silverlight, аплет Java, Javascript, Flash (AS3)

Демо демонстрація швидкості плагінів Roozz

Зверніть увагу, що швидкість роботи javascript варіюється в залежності від того, який браузер виконує його. Те саме стосується Flash та Silverlight, оскільки ці плагіни працюють у тому ж процесі, що і хостинг-браузер. Але плагін Roozz запускає стандартні .exe-файли, які запускаються у їхньому власному процесі, таким чином, на швидкість не впливає хостинг-браузер.


4

Ви повинні визначити "виконувати результати краще, ніж ..". Ну, я знаю, ви запитували про швидкість, але це не все, що має значення.

  • Чи виконують віртуальні машини більше режиму виконання? Так!
  • Вони їдять більше робочої пам’яті? Так!
  • Чи мають вони більш високі витрати на запуск (ініціалізація виконання та компілятор JIT)? Так!
  • Чи потрібна встановлена ​​величезна бібліотека? Так!

І так далі, її упередженість, так;)

За допомогою C # та Java ви платите ціну за отримане (швидше кодування, автоматичне управління пам’яттю, велика бібліотека тощо). Але у вас не так вже й багато місця, щоб торгуватися з деталями: візьміть повний пакет або нічого.

Навіть якщо ці мови можуть оптимізувати деякий код для виконання швидше, ніж компільований код, весь підхід (IMHO) неефективний. Уявіть, щодня проїжджайте 5 км до свого робочого місця за допомогою вантажівки! Його комфортно, він відчуває себе добре, ви в безпеці (надзвичайно зруйновані зони) і після того, як на деякий час ви наступите на газ, це буде навіть так швидко, як і звичайний автомобіль! Чому ми не маємо вантажівку, щоб їхати на роботу? ;)

В C ++ ви отримуєте те, за що платите, не більше, не менше.

Цитуючи Bjarne Stroustrup: "C ++ - це моя улюблена зібрана сміття, тому що вона генерує так мало сміття" текст посилання


Ну, я думаю, що він добре розуміє його недоліки, він також сказав: "C полегшує застрелити себе в ногу; C ++ робить це важче, але коли ти це робиш, то відбиває всю ногу";)
Фрунсі

"Чи потрібна їм встановлена ​​величезна бібліотека", Java вважає цю проблему за допомогою головоломки проекту.
toc777

"В C ++ ви отримуєте те, за що платите, не більше, не менше". Приклад лічильника: Я орієнтував на реалізацію дерева RB в OCaml та C ++ (GNU GCC), який використовував виняток для вистрибування з рекурсії, якщо доданий елемент вже присутній для повторного використання наявного набору. OCaml був до 6 разів швидше, ніж C ++, оскільки він не платить за перевірку деструкторів, оскільки стек розкручений.
JD

3
@Jon: але в якийсь (пізній?) Момент він все одно повинен знищити об'єкти (принаймні, він повинен звільнити свою пам'ять). А також зауважте, що винятки є для виняткових випадків, принаймні у C ++ це правило слід дотримуватися. Винятки C ++ можуть бути важкими, коли трапляються винятки, тобто компроміс.
Frunsi

@Jon: можливо, спробуйте повторити свій орієнтир timesна оболонці. Так що вона перевіряє всю програму, а не лише один аспект. Чи схожі результати тоді?
Frunsi

3

Виконаний код, створений з компілятора Java або C #, не інтерпретується - він компілюється в початковий код "просто вчасно" (JIT). Отже, перший код часу в програмі Java / C # зустрічається під час виконання, є деякий накладний вигляд, оскільки "компілятор виконання" (він же компілятор JIT) перетворює байтовий код (Java) або код IL (C #) в інструкції до рідної машини. Однак наступного разу, коли цей код зустрінеться, поки програма все ще працює, нативний код виконується негайно. Це пояснює, як деякі програми Java / C # спочатку здаються повільними, але потім краще, ніж довше вони працюють. Хороший приклад - веб-сайт ASP.Net. Уперше, коли доступ до веб-сайту, це може бути трохи повільніше, оскільки компілятор JIT збирає код C # у нативний код.


3

Тут є кілька хороших відповідей щодо конкретного запитання. Я хотів би відступити назад і подивитися на більшу картину.

Майте на увазі, що на сприйняття користувачем швидкості програмного забезпечення, яке ви пишете, впливає багато інших факторів, а не те, наскільки добре оптимізується кодеген. Ось кілька прикладів:

  • Ручне управління пам’яттю важко зробити правильно (без протікань), а ще важче зробити ефективно (звільнити пам'ять незабаром після того, як ви закінчите з цим). Загалом, використання GC швидше створює програму, яка добре управляє пам'яттю. Чи готові ви дуже наполегливо працювати та затягувати доставку свого програмного забезпечення, намагаючись перемогти GC?

  • Мій C # легше читати та розуміти, ніж мій C ++. У мене також є більше способів переконати себе, що мій код C # працює правильно. Це означає, що я можу оптимізувати свої алгоритми з меншим ризиком введення помилок (а користувачі не люблять програмне забезпечення, яке виходить з ладу, навіть якщо це робиться швидко!)

  • Я можу створити своє програмне забезпечення швидше в C #, ніж у C ++. Це звільняє час для роботи над продуктивністю і все-таки доставляє своє програмне забезпечення вчасно.

  • Простіше писати хороший інтерфейс у C #, ніж на C ++, тому я, швидше за все, зможу відсунути роботу на другий план, поки інтерфейс залишається чуйним, або забезпечити прогрес або прослуховування інтерфейсу користувача, коли програмі потрібно деякий час блокувати. Це не робить нічого швидшим, але робить користувачів щасливішими в очікуванні.

Все, що я сказав про C #, ймовірно, стосується Java, я просто не маю досвіду сказати напевно.


3

Якщо ви програміст Java / C #, який вивчає C ++, вам буде до душі продовжувати думати з точки зору Java / C # і перекладати дослівно в синтаксис C ++. У цьому випадку ви отримуєте лише згадані раніше переваги нативного коду порівняно з інтерпретованим / JIT. Щоб отримати найбільший приріст продуктивності в C ++ порівняно з Java / C #, вам доведеться навчитися мислити на C ++ та розробляти код спеціально для використання сильних сторін C ++.

Якщо перефразовувати Едсгера Дайкстра : [ваша перша мова] мучить розум після одужання.
Перефразовуючи Джеффа Етвуда : ви можете написати [свою першу мову] будь-якою новою мовою.


1
Я підозрюю, що приказка "Можна написати FORTRAN будь-якою мовою" передує кар'єрі Джеффа.
Девід Торнлі

3

Однією з найбільш значущих оптимізацій JIT є вбудований метод. Java може навіть вбудовувати віртуальні методи, якщо це може гарантувати правильність виконання. Такий тип оптимізації зазвичай не може бути виконаний стандартними статичними компіляторами, оскільки він потребує цілопрограмного аналізу, що є важким через окрему компіляцію (на відміну від JIT, уся програма доступна йому). Вбудований метод покращує інші оптимізації, надаючи більші блоки коду для оптимізації.

Стандартний розподіл пам’яті в Java / C # також швидше, а делокація (GC) не набагато повільніше, але лише менш детермінована.


Зверніть увагу , що freeі deleteдетермінований або і GC може бути детермінованим, не виділяючи.
JD

3

Мови віртуальної машини навряд чи перевершують компільовані мови, але вони можуть наблизитися достатньо, що це не має значення з (принаймні) наступних причин (я тут кажу для Java, оскільки я ніколи не робив C #).

1 / Навколишнє середовище Java Runtime, як правило, здатне виявляти фрагменти коду, які запускаються часто, і виконувати компіляцію цих розділів, що вчасно (JIT), щоб у майбутньому вони працювали з повною швидкістю компіляції.

2 / Великі частини бібліотек Java складені так, що при виклику функції бібліотеки ви виконуєте компільований код, а не інтерпретується. Ви можете побачити код (в С), завантаживши OpenJDK.

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

4 / Оскільки велика перевірка байт-коду Java робиться під час завантаження класу, нормальна накладні перевірки часу виконання виконуються значно.

5 / У найгіршому випадку, інтенсивно діючий код може бути вилучений у складений модуль і викликаний з Java (див. JNI), щоб він працював на повній швидкості.

Підсумовуючи, байт-код Java ніколи не перевершує рідну мову машини, але є способи пом'якшити це. Великою перевагою Java (як я бачу) є ВЕЛИЧЕЗНА стандартна бібліотека та кросплатформенний характер.


1
З п. 2, "2 / Орієнтовні частини бібліотек Java складені так, що, коли ви викликаєте функцію бібліотеки, ви виконуєте скомпільований код, а не інтерпретується": У вас є цитування для цього? Якби це було справді так, як ви описуєте, я б очікував, що у мого налагоджувача багато наткнеться на рідний код, але я цього не роблю.
cero

Re: cero Налагоджувачі часто використовують менш ефективні, але більш виразні шляхи, і тому не є хорошим маркером для будь-якого пов'язаного з продуктивністю.
Гуванте

2
У цій бібліотеці HUGH є ще одне величезне підвищення продуктивності - код бібліотеки, мабуть, краще написаний, ніж те, що багато програмістів будуть писати самостійно (зважаючи на обмежений час та відсутність спеціальних знань) та на Java, оскільки з багатьох причин програмісти часто використовують бібліотека.
Ліран Ореві

3

Оріоне Едріане , дозвольте мені перевернути вашу публікацію, щоб побачити, наскільки ваші зауваження необгрунтовані, адже багато чого можна сказати і про C ++. І кажучи про те, що компілятор Java / C # оптимізує порожні функції насправді робить вас звуком, ніби ви не мій експерт в оптимізації, тому що а) чому справжня програма повинна містити порожні функції, за винятком дійсно поганого застарілого коду, б) що насправді немає оптимізація чорних і кровотокових країв.

Окрім цієї фрази, ви кричуще оцінювали покажчики, але чи об’єкти в Java та C # в основному не працюють, як покажчики C ++? Може вони не перетинаються? Вони можуть бути недійсними? C (і більшість реалізацій C ++) мають ключове слово обмеження, обидва мають типи значень, C ++ має посилання на значення з ненульовою гарантією. Що пропонують Java та C #?

>>>>>>>>>>

Взагалі, C і C ++ можуть бути настільки ж швидкими або швидшими, тому що компілятор AOT - компілятор, який збирає ваш код до розгортання, раз і назавжди, на вашій високошвидкій пам’яті багатьох сердерах побудови ядра - може зробити оптимізацію, що програма, складена на C # не може, тому що у нього є багато часу для цього. Компілятор може визначити, чи машина є Intel чи AMD; Pentium 4, Core Solo або Core Duo; або якщо підтримує SSE4 тощо, і якщо ваш компілятор не підтримує відправлення, ви можете вирішити це самостійно, розгорнувши кілька спеціалізованих бінарних файлів.

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

Крім того, деякі мовні функції дозволяють компілятору в C ++ або C робити припущення щодо вашого коду, що дозволяє йому оптимізувати певні частини, які просто не безпечні для компілятора Java / C #. Якщо у вас немає доступу до повного ідентифікатора типу generics або гарантованого потоку програми, існує багато оптимізацій, які просто не є безпечними.

Також C ++ і C роблять багато розподілів стеків одночасно лише одним збільшенням реєстру, що, безумовно, є більш ефективним, ніж розподіли Javas та C #, що стосується шару абстрагування між сміттєзбірником та вашим кодом.

Тепер я не можу говорити для Java в наступному пункті, але я знаю, що компілятори C ++, наприклад, фактично видалять методи та виклики методів, коли він знає, що тіло методу порожнє, воно усуне загальні піддепресії, може спробувати і повторити спробу щоб знайти оптимальне використання реєстру, він не примушує перевіряти межі, він автоматично перемикає петлі та внутрішні петлі та перетворюватиме внутрішні на зовнішні, він переміщує умовні дії з циклів, він розбиває та не розбиває петлі. Він розширить std :: vector на рідні нульові накладні масиви так, як ви це зробите на шляху C. Це зробить міжпроцедурні оптимізації. Він побудує значення повернення безпосередньо на сайті абонента. Це складе і поширить вирази. Він буде впорядковувати дані в кеш-пам'яті. Це зробить стрибкові нарізки. Це дозволяє писати компілювати промінь часу променів з нульовим режимом виконання. Це зробить дуже дорогі оптимізації на основі графіків. Це дозволить зменшити силу, якщо замінить певні коди синтаксично абсолютно нерівним, але семантично еквівалентним кодом (старий "xor foo, foo" - це просто найпростіша, хоча застаріла оптимізація такого роду). Якщо ви ласкаво запитаєте про це, ви можете опустити стандарти плаваючої точки IEEE і включити ще більше оптимізацій, таких як повторне замовлення операнду з плаваючою комою. Після того, як він помасажував і розправив ваш код, він може повторити весь процес, тому що часто певні оптимізації лежать в основі ще більш певних оптимізацій. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. чи замінює він певні коди синтаксично абсолютно нерівним, але семантично еквівалентним кодом (старий "xor foo, foo" - просто найпростіша, хоча застаріла оптимізація такого типу). Якщо ви ласкаво запитаєте про це, ви можете опустити стандарти плаваючої точки IEEE і включити ще більше оптимізацій, таких як повторне замовлення операнду з плаваючою комою. Після того, як він помасажував і розправив ваш код, він може повторити весь процес, адже часто певні оптимізації лежать в основі ще більш певних оптимізацій. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. чи замінює він певні коди синтаксично абсолютно нерівним, але семантично еквівалентним кодом (старий "xor foo, foo" - просто найпростіша, хоча застаріла оптимізація такого роду). Якщо ви ласкаво запитаєте про це, ви можете опустити стандарти плаваючої точки IEEE і включити ще більше оптимізацій, таких як повторне замовлення операнду з плаваючою комою. Після того, як він помасажував і розправив ваш код, він може повторити весь процес, адже часто певні оптимізації лежать в основі ще більш певних оптимізацій. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. Якщо ви ласкаво запитаєте про це, ви можете опустити стандарти плаваючої точки IEEE і включити ще більше оптимізацій, таких як повторне замовлення операнду з плаваючою комою. Після того, як він помасажував і розправив ваш код, він може повторити весь процес, адже часто певні оптимізації лежать в основі ще більш певних оптимізацій. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. Якщо ви ласкаво запитаєте про це, ви можете опустити стандарти плаваючої точки IEEE і включити ще більше оптимізацій, таких як повторне замовлення операнду з плаваючою комою. Після того, як він помасажував і розправив ваш код, він може повторити весь процес, адже часто певні оптимізації лежать в основі ще більш певних оптимізацій. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді. Він також може просто спробувати з перетасованими параметрами і побачити, як інший варіант балів у своєму внутрішньому рейтингу. І він використовуватиме таку логіку у всьому коді.

Отже, як ви бачите, є безліч причин, чому деякі C ++ або C реалізації будуть швидшими.

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

Тож залежно від того, що ви пишете, я б пішов з тим чи іншим. Але якщо ви пишете щось, що не залежить від апаратного забезпечення (драйвер, відеоігра тощо), я б не хвилювався про продуктивність C # (знову не можу говорити про Java). Це буде добре.

<<<<<<<<<<

Як правило, певні узагальнені аргументи можуть звучати круто в конкретних дописах, але, як правило, не звучать напевно достовірними.

У будь-якому разі, щоб помиритися: AOT - це чудово, як і JIT . Єдиною правильною відповіддю може бути: Це залежить. А справжні розумні люди знають, що ти можеш використовувати найкраще з обох світів у будь-якому разі.


2

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

Однак шанси на те, що насправді відбувається, досить низькі - якщо, можливо, у Java є дуже добре написана бібліотека, і у вас є власна погано написана бібліотека C ++.


Я також вважаю, що є і певна мовна вага, коли ви працюєте на нижчому рівні, з меншою абстракцією, ви будете розробляти швидшу програму. Це не пов'язане з пунктами щодо самого виконання байт-коду.
Брайан Р. Бонді,

2

Насправді, C # насправді не працює у віртуальній машині, як це робить Java. IL компілюється в асемблерну мову, яка є повністю рідним кодом і працює з тією ж швидкістю, що і власний код. Ви можете попередньо JIT програму .NET, яка повністю знімає вартість JIT, і тоді ви використовуєте повністю нативний код.

Уповільнення роботи з .NET настане не тому, що .NET-код повільніше, а тому, що він робить набагато більше за кадром, щоб робити такі речі, як збирання сміття, перевірка посилань, зберігання повних кадрів стека тощо. Це може бути досить потужним і корисним, коли створення додатків, але також поставляється за вартістю. Зауважте, що ви можете робити все це і в програмі C ++ (більшість основних функцій .NET - це насправді .NET-код, який ви можете переглянути в ROTOR). Однак, якщо ви вручну написали той самий функціонал, ви, ймовірно, отримаєте набагато повільнішу програму, оскільки час роботи .NET був оптимізований і тонко налаштований.

Однак, однією з сильних сторін керованого коду є те, що він може бути повністю перевіреним, тобто. ви можете переконатися, що код ніколи не матиме доступу до пам'яті інших процесів або робити несанкціоновані речі перед його виконанням. Майкрософт має дослідний прототип повністю керованої операційної системи, який несподівано показав, що 100% кероване середовище насправді може працювати значно швидше, ніж будь-яка сучасна операційна система, скориставшись цією верифікацією, щоб вимкнути функції безпеки, які більше не потрібні керованим програмам (ми говоримо як 10 разів у деяких випадках). Радіо SE має чудовий епізод про цей проект.


1

У деяких випадках керований код насправді може бути швидшим, ніж нативний код. Наприклад, алгоритми збору сміття дозволяють середовищам, таким як JRE або CLR, звільняти велику кількість короткочасних (зазвичай) об'єктів за один прохід, де більшість об'єктів купівлі C / C ++ звільняються один на один Час.

З Вікіпедії :

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

З цього приводу я написав багато C # і багато C ++, і я провів безліч еталонів. На мій досвід, C ++ набагато швидше, ніж C #, двома способами: (1) якщо ви берете якийсь код, який ви написали на C #, перенесіть його на C ++, то рідний код, як правило, швидше. На скільки швидше? Що ж, це багато в чому різниться, але не рідкість можна побачити 100% підвищення швидкості. (2) У деяких випадках збирання сміття може значно уповільнити керовану програму. .NET CLR виконує жахливу роботу з великими купи (скажімо,> 2 Гб), і може закінчити витратити багато часу в GC - навіть у додатках, у яких мало - або навіть немає - об'єктів проміжних строків життя.

Звичайно, у більшості випадків, з якими я стикався, керовані мови є досить швидкими, дальньою стрілкою, а підтримка та компроміс кодування для додаткової продуктивності C ++ просто не є гарною.


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

3
Я не спостерігав, щоб у великих програмах Unix, які покликані працювати вічно. Вони, як правило, записуються на C, що навіть гірше для управління пам'яттю, ніж C ++.
Девід Торнлі

Звичайно, питання полягає в тому, чи порівнюємо ми реалізацію програми з керованим та некерованим кодом чи теоретичну найкращу ефективність мови. Очевидно, що некерований код може завжди бути принаймні так швидко , як вдалося, як і в самому гіршому випадку , ви могли б просто написати некеровану програму , яка зробила точно те ж саме , як керований код! Але більшість питань продуктивності є алгоритмічними, а не мікро. Крім того, ви не оптимізуєте керований і некерований код однаково, тому "C ++ у C #" зазвичай не працює.
kyoryu

2
У C / C ++ ви можете виділяти короткочасні об'єкти на стеку, і ви робите це, коли це доречно. У керованому коді ви не можете , у вас немає вибору. Крім того, в C / C ++ ви можете виділити списки об'єктів у суміжних областях (новий Foo [100]), в керованому коді ви не можете. Отже, ваше порівняння не вірно. Ну, ця сила вибору покладає тягар на розробників, але таким чином вони вчаться пізнавати світ, у якому вони живуть (пам'ять ......).
Фрунсі

@frunsi: "в C / C ++ ви можете виділити списки об'єктів у суміжних областях (новий Foo [100]), в керованому коді ви не можете". Це неправильно. Місцеві типи значень виділяються стеком, і ви навіть можете стек виділяти масиви з них у C #. Існують навіть виробничі системи, написані на C #, які повністю не розподілені в стаціонарному стані.
JD


1

Насправді JVM Sun's HotSpot використовує "змішаний режим". Він інтерпретує байт-код методу до тих пір, поки не визначить (як правило, через якийсь лічильник), що певний блок коду (метод, цикл, блок-пробування тощо) збирається виконати багато, а потім його JIT компілює. Час, необхідний для складання методу JIT, часто займає більше часу, ніж якби метод був інтерпретований, якщо це метод рідко запускається. Продуктивність зазвичай вища для "змішаного режиму", оскільки JVM не витрачає час на JITing-код, який рідко, якщо і коли-небудь, запускається. C # і .NET цього не роблять. .NET JITs все, що часто тратить час.


1

Прочитайте про « Динамо » лабораторії HP Labs ' , інтерпретатора PA-8000, який працює на ПА-8000, і часто запускає програми швидше, ніж у них вдома. Тоді це не здасться зовсім дивним!

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

Часто зводиться до:

  • програми мають гарячі точки, тому навіть якщо ви повільніше виконуєте 95% частини коду, який потрібно запустити, ви все одно можете бути конкурентоспроможними, якщо ви швидше на гарячих 5%

  • HLL знає більше про ваш намір, ніж LLL, як C / C ++, і тому може генерувати більш оптимізований код (OCaml має ще більше, а на практиці часто навіть швидше)

  • компілятор JIT має багато інформації, якої не має статичний компілятор (наприклад, фактичні дані, які у вас є у цей час)

  • компілятор JIT може робити оптимізацію під час виконання, що традиційним посиланням не дозволяється робити (наприклад, упорядкування гілок, щоб загальний випадок був рівним, або вбудовані виклики бібліотеки)

Загалом, C / C ++ - це досить важка мова для продуктивності: є відносно мало інформації про ваші типи даних, немає інформації про ваші дані та немає динамічного часу виконання, що дозволяє значно заважати оптимізації часу роботи.


1

Якщо у Java або CLR швидше, ніж у C ++, ви можете отримати короткі розриви, але в цілому продуктивність гірша за весь час роботи програми: для деяких результатів див. Www.codeproject.com/KB/dotnet/RuntimePerformance.aspx.



1

Я розумію, що C / C ++ виробляє нативний код для роботи на певній архітектурі машини. І навпаки, такі мови, як Java та C #, працюють над віртуальною машиною, яка абстрагує рідну архітектуру. Логічно здається, що для Java або C # неможливо порівняти швидкість C ++ через цей проміжний крок, однак мені сказали, що останні компілятори ("гаряча точка") можуть досягти цієї швидкості або навіть перевищити її.

Це нелогічно. Використання проміжного представлення по суті не погіршує продуктивність. Наприклад, llvm-gcc компілює C та C ++ за допомогою LLVM IR (що є віртуальною машиною безмежної реєстрації) до нативного коду, і він досягає відмінних показників (часто перемагаючи GCC).

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

Ось кілька прикладів:

  • Віртуальні машини з компіляцією JIT полегшують генерацію коду під час виконання (наприклад, System.Reflection.Emitу .NET), тому ви можете компілювати згенерований код під час руху на таких мовах, як C # і F #, але повинні вдатися до написання порівняно повільного інтерпретатора на C або C ++. Наприклад, реалізувати регулярні вирази.

  • Частини віртуальної машини (наприклад, бар'єр запису та розподільник) часто записуються в ручному кодувальному асемблері, оскільки C і C ++ не генерують досить швидкого коду. Якщо програма наголошує на цих частинах системи, вона, можливо, може перевершити все, що можна записати на C або C ++.

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

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

Обробка коду буде здійснена під час компіляції ...

Отже, метапрограмування шаблонів працює лише в тому випадку, якщо програма доступна в час компіляції, що часто не буває так, наприклад, неможливо написати конкурентоспроможну бібліотеку регулярних виразів у ванілі C ++, оскільки вона не здатна генерувати код часу виконання (важливий аспект метапрограмування).

... гра з типами проводиться під час компіляції ... еквівалент у Java або C # в кращому випадку болісно писати, і завжди буде повільніше і вирішується під час виконання, навіть коли типи відомі під час компіляції.

У C # це справедливо лише для типів посилань і не відповідає дійсним типам значень.

Незалежно від оптимізації JIT, нічого не пройде швидко, оскільки прямий доступ до пам'яті вказівник ... якщо у вас є суміжні дані в пам'яті, доступ до них за допомогою покажчиків C ++ (тобто, покажчиків C ... Давайте надамо Цезареві належне) піде в рази швидше ніж у Java / C #.

Люди спостерігали, як Java тестує C ++ на тесті SOR з еталону SciMark2 саме тому, що покажчики перешкоджають оптимізаціям, пов'язаним із збудженням .

Також варто відзначити, що .NET вводить спеціалізацію дженериків у динамічно пов'язаних бібліотеках після посилання, тоді як C ++ не може, оскільки шаблони повинні бути вирішені перед посиланням. І очевидно, що велика перевага дженериків перед шаблонами - це зрозумілі повідомлення про помилки.


0

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


Або якщо ви використовуєте кращий розподільник C ++ та / або пул об’єктів. Це далеко не магія, з точки зору точки зору C ++, і це може звестись до того, що "розподіл купи" стане таким же швидким виділенням стека.
paercebal

Якщо ви завжди розподіляєте все на купі, то .NET і Java можуть навіть працювати краще, ніж C / C ++. Але ви просто не зробите цього в C / C ++.
Фрунсі

0

Що завгодно, що потребує великої швидкості, JVM просто викликає реалізацію C ++, тому питання більше в тому, наскільки хороші їхні зв'язки, ніж наскільки хороший JVM для більшості речей, пов'язаних з ОС. Збір сміття скорочує вашу пам’ять навпіл, але використання деяких більш химерних функцій STL та Boost матиме такий же ефект, але з багатократним потенціалом помилок.

Якщо ви просто використовуєте бібліотеки C ++ та безліч функцій високого рівня у великому проекті з багатьма класами, ви, ймовірно, закінчуватиметься повільніше, ніж використання JVM. За винятком набагато більше схильних до помилок.

Однак перевага C ++ полягає в тому, що він дозволяє оптимізувати себе, інакше ви застрягли в тому, що робить компілятор / jvm. Якщо ви робите власні контейнери, записуйте власне управління пам’яттю, яке вирівняне, використовуйте SIMD та переходите до складання тут і там, ви можете пришвидшити принаймні 2–4 рази, ніж більшість компіляторів C ++ робитимуть самостійно. Для деяких операцій 16x-32x. Це використання одних і тих же алгоритмів, якщо використовувати кращі алгоритми та паралелізувати, збільшення може бути суттєвим, іноді в тисячі разів швидшим, ніж зазвичай використовувані методи.


0

Я дивлюся на це з кількох різних точок.

  1. Враховуючи нескінченний час та ресурси, чи швидше буде керований чи некерований код? Зрозуміло, що відповідь полягає в тому, що некерований код завжди може принаймні прив'язати керований код у цьому аспекті - як і в гіршому випадку, ви просто жорстко вправляєте кероване рішення коду.
  2. Якщо взяти програму однією мовою і безпосередньо перекласти її на іншу, наскільки гірше вона буде виконуватись? Напевно, багато для будь-яких двох мов. Більшість мов вимагають різної оптимізації та мають різні доходи. Мікропродуктивність часто багато чого про знання цих деталей.
  3. З огляду на обмежений час та ресурси, яка з двох мов дасть кращий результат? Це найцікавіше питання, оскільки, хоча керована мова може створювати трохи повільніший код (з урахуванням програми, розумно написаної для цієї мови), ця версія, швидше за все, буде зроблена швидше, що дозволить витратити більше часу на оптимізацію.

0

Дуже коротка відповідь: Враховуючи фіксований бюджет, ви досягнете більш ефективної програми Java, ніж додаток C ++ (міркування щодо ROI). Крім того, платформа Java має більш гідних профілів, які допоможуть вам швидше визначити свої гарячі точки.

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