Чи можуть сучасні мови OO конкурувати з продуктивністю магазину C ++?


40

Я щойно помітив, що кожна сучасна мова програмування OO, якою я, принаймні, дещо знайома (що в основному є лише Java, C # і D), дозволяє коваріантні масиви. Тобто рядковий масив - це об'єктний масив:

Object[] arr = new String[2];   // Java, C# and D allow this

Коваріантні масиви - це дірка в системі статичного типу. Вони роблять можливими помилки типу, які неможливо виявити під час компіляції, тому кожне записування до масиву повинно перевірятися під час виконання:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Це здається страшним ударом, якщо я маю багато магазинів.

У C ++ немає коваріантних масивів, тому не потрібно проводити таку перевірку виконання, що означає, що не існує штрафу за продуктивність.

Чи робиться якийсь аналіз, щоб зменшити кількість перевірок виконання? Наприклад, якщо я скажу:

arr[1] = arr[0];

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

Чи справді сучасні компілятори роблять такі оптимізації, чи мені доводиться жити з тим фактом, що, наприклад, Quicksort завжди робить непотрібні перевірки часу O (n log n)?

Чи можуть сучасні мови OO уникати накладних витрат, створених підтримкою ко-варіантних масивів?


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

17
@eco: C ++ швидше з доступом до масиву, оскільки він не підтримує ко-варіант масиви. Фред хоче знати, чи можливо, щоб "сучасні мови OO" схилили накладні масиви ко-варіантів, щоб наблизитись до швидкостей C ++.
Mooing Duck

13
"Код, який складається, але викидає винятки під час виконання, - погана новина"

4
@Jesse І мільйони людей пишуть надійний, масштабований код динамічними мовами. Imo: Якщо ви не пишете тестові випадки для свого коду, мені все одно, чи є помилки компілятора чи ні, я все одно не буду довіряти цьому.
Во

7
@Jesse І коли б ви ще очікували винятків, але під час виконання? Проблема не в коді, який викидає винятки під час виконання - є маса випадків, коли це має сенс - проблема полягає в коді, який гарантовано помиляється, який не потрапляє статично компілятором, а натомість призводить до виключення на час виконання.
Джонатан М Девіс

Відповіді:


33

D не має коваріантних масивів. Це дозволило їм до останнього випуску ( dmd 2.057 ), але ця помилка була виправлена.

Масив у D фактично є лише структурою з покажчиком та довжиною:

struct A(T)
{
    T* ptr;
    size_t length;
}

Перевірка меж проводиться зазвичай під час індексації масиву, але вона видаляється при компіляції -release. Отже, в режимі випуску немає реальної різниці в продуктивності між масивами в C / C ++ і тими в D.


Постає не - d-programming-language.org/arrays.html говорить «Статичний масив T[dim]може бути неявно перетворений в один з наступних способів : ... U[]... Масив динамічний T[]може бути неявно перетворений в один з наступних способів : U[]. .. де Uбазовий клас " T.
Бен Войгт

2
@BenVoigt Тоді онлайн-документи потрібно оновити. На жаль, вони не завжди є актуальними на 100%. Перетворення буде працювати const U[], оскільки тоді ви не можете призначити неправильний тип елементам масиву, але, T[]безумовно, не конвертується до U[]тих пір, поки U[]є змінним. Допускати коваріантні масиви, як це було раніше, було серйозною вадою дизайну, яка тепер була виправлена.
Джонатан М Девіс

@JonathanMDavis: Коваріантні масиви хитрі, але добре працюють для конкретного сценарію коду, який запише лише до елементів масиву , прочитаних з того самого масиву . Як D дозволив би написати метод, який міг би сортувати масиви довільного типу?
supercat

@supercat Якщо ви хочете написати функцію, яка сортує довільні типи, то шаблонізуйте її. наприклад, dlang.org/phobos/std_algorithm.html#sort
Джонатан М Девіс

21

Так, одна важлива оптимізація полягає в наступному:

sealed class Foo
{
}

У C # цей клас не може бути супертипом для будь-якого типу, тому ви можете уникнути перевірки на масив типів Foo.

А до другого питання, у ко-варіантних масивах F # заборонено (але, мабуть, перевірка залишиться в CLR, якщо вона не виявиться непотрібною в оптимізаціях під час виконання)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Дещо пов'язана проблема - перевірка обмеженості масиву. Це може бути цікавим (але старим) читанням про оптимізації, зроблені в CLR (коваріація також згадується 1 місце): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds -чек-усунення-в-clr.aspx


2
Скала також запобігає цій конструкції: val a = Array("st"); val b: Array[Any] = aє незаконною. (Однак масиви в Scala - це ... особлива магія ... завдяки використаному базовому JVM.)
pst

13

Відповідь Java:

Я вважаю, що ви насправді не порівняли код, чи не так? Загалом 90% усіх динамічних ролей на Java безкоштовні, оскільки JIT може їх ухилити (quicksort повинен бути хорошим прикладом для цього), а решта - одна ld/cmp/brпослідовність, яка абсолютно передбачувана (якщо ні, ну чому, до біса, ваш код кидає всі ті винятки з динамічної ролі?)

Ми робимо навантаження набагато раніше, ніж фактичне порівняння, гілка вірно прогнозується в 99,9999% (складена статистика!) Усіх випадків, тому ми не затримуємо конвеєр (якщо припустити, що ми не потрапимо в пам'ять вантажем, якщо не добре, що буде помітно, але тоді навантаження все одно потрібно. Отже, вартість становить 1 тактовий цикл, якщо СП не може уникнути перевірки взагалі.

Якісь накладні? Звичайно, але я сумніваюся, ви коли-небудь це помітите ..


Щоб підтримати мою відповідь, будь ласка, перегляньте цей доктор Кліфф Клацніть блог, де обговорюється продуктивність Java проти C.


10

D не дозволяє коваріантні масиви.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Як ви кажете, це було б діркою в системі типів, щоб це допустити.

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

Доступ до масиву в D так само швидко, як і в C або C ++.


Відповідно до d-programming-language.org/arrays.html "Статичний масив T [dim] може бути неявно перетворений в одне з наступних: ... U [] ... Динамічний масив T [] може бути неявно перетворений до одного з наступних: U [] ... де U - базовий клас Т. "
Бен Войгт

1
@BenVoigt: застарілі документи.
BCS

1
@BenVoigt: Я створив запит на поновлення, щоб оновити документацію. Сподіваємось, це скоро буде вирішено. Дякуємо, що вказали на це.
Петро Олександр

5

Від тесту, який я робив на дешевому ноутбуці, різниця між використанням int[]та Integer[]становить приблизно 1,0 нс. Різниця, ймовірно, буде пов’язана з додатковою перевіркою на тип.

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

Дзвінки для порівнянняТо, ймовірно, будуть значно дорожчими, особливо якщо ви використовуєте такий складний об'єкт, як String.


2

Ви запитували про інші сучасні мови ОО? Що ж, Delphi цілком уникає цієї проблеми, будучи stringпримітивом, а не об'єктом. Отже, масив рядків - це саме масив рядків і нічого іншого, і будь-які операції над ними проходять так швидко, як і власний код, без перевірки накладних типів.

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

Що стосується інших об'єктів взагалі, то проблеми не існує, оскільки в Delphi, як і в C ++, масиви не є коваріантними, щоб запобігти виду системних отворів, описаних тут.


1

чи мені доводиться жити з тим, що, наприклад, Quicksort завжди робить O (n log n) зайві перевірки часу виконання?

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

Причина такої поведінки полягає в тому, що обмежена перевірка модифікує вирівнювання коду в пам'яті, буде змінювати частоту звернень кешу тощо.

Так, так, живіть з тим, що Quicksort завжди робить O (n log n) помилковими перевірями та оптимізує їх після профілювання.


1

Scala - мова ОО, яка має інваріантні, а не коваріантні масиви. Він націлений на JVM, тому там немає виграшів у продуктивності, але це дозволяє уникнути невластивості, спільної як для Java, так і для C #, яка ставить під загрозу безпеку їх типу з міркувань зворотної сумісності.

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