Едемський простір
Тож моє запитання - чи може щось із цього насправді бути правдивим, і якщо так, то чому виділення купи Java відбувається набагато швидше.
Я трохи вивчав, як працює Java GC, оскільки мені це дуже цікаво. Я завжди намагаюся розширити свою колекцію стратегій розподілу пам’яті на C і C ++ (зацікавлений у спробі реалізувати щось подібне в C), і це дуже, дуже швидкий спосіб розподілити безліч об'єктів у вибуховий спосіб практична перспектива, але в першу чергу завдяки багатопотоковості.
Спосіб розподілу Java GC полягає у використанні надзвичайно дешевої стратегії розподілу для початкового розподілу об'єктів у просторі "Еден". Як я можу сказати, це використання послідовного розподільника пулу.
Це набагато швидше саме з точки зору алгоритму та зменшення обов'язкових помилок сторінки, ніж загального призначення malloc
на C або за замовчуванням, викидання operator new
C ++.
Але послідовні розподільники мають очевидну слабкість: вони можуть виділяти шматки змінного розміру, але вони не можуть звільнити жодних окремих шматочків. Вони просто виділяють прямим послідовним способом з накладкою для вирівнювання і можуть лише очистити всю пам'ять, яку вони виділили одразу. Вони зазвичай корисні для C та C ++ для побудови структур даних, для яких потрібні лише вставки та не видалення елементів, як дерево пошуку, яке потрібно створити лише один раз, коли програма запускається, а потім повторно шукається або додаються лише нові ключі ( клавіші не видалено)
Вони також можуть бути використані навіть для структур даних, які дозволяють видаляти елементи, але ці елементи насправді не будуть звільнені від пам'яті, оскільки ми не можемо розібрати їх окремо. Така структура, що використовує послідовний розподільник, просто споживає більше і більше пам'яті, якщо б вона не мала відкладеного проходу, де дані були скопійовані у свіжу компактну копію за допомогою окремого послідовного розподільника (і це іноді дуже ефективна техніка, якщо фіксований алокатор виграє я не буду чомусь - просто прямо вгору послідовно виділіть нову копію структури даних і скиньте всю пам'ять старої).
Колекція
Як і в наведеному вище прикладі структури даних / послідовного пулу, це було б величезною проблемою, якби Java GC виділяла лише цей спосіб, хоча це дуже швидко для швидкого розподілу багатьох окремих фрагментів. Він не зможе нічого вивільнити, доки програмне забезпечення не вимкнеться, і тоді він зможе звільнити (очистити) всі пулі пам'яті відразу.
Таким чином, замість цього після одного циклу GC робиться пропуск через існуючі об'єкти в просторі "Еден" (послідовно виділяються), а ті, на які все ще посилаються, потім отримують виділення за допомогою більш загального призначення, здатного звільнити окремі шматки. Оні, на які більше не посилаються, будуть просто розміщені в процесі очищення. Тому в основному це "копіювання об'єктів з простору Едена, якщо на них все ще посилаються, а потім очищення".
Це зазвичай коштує досить дорого, тому це робиться в окремій фоновій нитці, щоб уникнути значного затримки потоку, який спочатку виділив усю пам'ять.
Після того, як пам'ять скопійована з простору Едена і розподілена за допомогою цієї більш дорогої схеми, яка може звільнити окремі фрагменти після початкового циклу GC, об'єкти переміщуються до більш стійкої області пам'яті. Ці окремі шматки потім звільняються в наступних циклах GC, якщо вони перестають ставати посиланням.
Швидкість
Отже, грубо кажучи, причина Java GC може дуже добре перевершити C або C ++ при прямому розподілі купи, тому що вона використовує найдешевшу, повністю вироджену стратегію розподілу в потоці з проханням виділити пам'ять. Тоді це економить більш дорогу роботу, яку нам зазвичай потрібно робити, використовуючи більш загальний розподільник, як прямий вгору malloc
для іншого потоку.
Таким чином, концептуально GC насправді має робити більше загальної роботи, але він розподіляє це по потоках, щоб повна вартість не була сплачена заздалегідь одним потоком. Це дозволяє потоку, що виділяє пам'ять, робити це дуже дешево, а потім відкладає справжні витрати, необхідні для того, щоб робити все належним чином, щоб окремі об'єкти могли бути фактично звільнені в інший потік. У C або C ++, коли ми malloc
або дзвонимо operator new
, ми повинні сплатити повну вартість заздалегідь у межах однієї нитки.
Це головна відмінність, і чому Java може дуже добре перевершити C або C ++, використовуючи лише наївні дзвінки malloc
або operator new
виділяти купу маленьких шматок окремо. Звичайно, як правило, будуть якісь атомні операції та потенційна блокування, коли починається цикл GC, але це, мабуть, оптимізовано зовсім небагато.
В основному, просте пояснення зводиться до оплати більш високої вартості в одному потоці ( malloc
) порівняно з оплатою більш дешевої вартості в одному потоці, а потім оплати більш високої вартості в іншій, яка може працювати паралельно ( GC
). З огляду на те, що цей спосіб полягає в тому, що вам потрібні дві непрямості, щоб дістати посилання на об'єкт до об'єкта, як це потрібно, щоб дозволити алокатору копіювати / переміщувати пам’ять без недійсних існуючих посилань на об'єкт, а також ви можете втратити просторову локальність, коли пам'ять об'єкта є перемістилися з простору "Едем".
І останнє, але не менш важливе, порівняння трохи несправедливе, оскільки код C ++, як правило, не розподіляє човно об'ємних предметів окремо на купі. Достойний код C ++ має тенденцію виділяти пам'ять для багатьох елементів у суміжних блоках або на стеку. Якщо він виділяє човновий набір крихітних предметів один на один у вільному магазині, код є shite.