ByteBuffer.allocate () проти ByteBuffer.allocateDirect ()


144

До allocate()або до allocateDirect()цього, це питання.

Вже кілька років я просто притримуюсь думки, що оскільки DirectByteBuffers - це пряме відображення пам'яті на рівні ОС, воно буде виконувати швидше з викликами get / put, ніж HeapByteBuffers. Мені ніколи не було цікаво дізнаватися точні подробиці щодо ситуації, що склалася до цих пір. Я хочу знати, який із двох типів ByteBuffers швидший та на яких умовах.


Щоб дати конкретну відповідь, потрібно конкретно сказати, що ви робите з ними. Якщо один завжди був швидшим за інший, то чому б було два варіанти. Можливо, ви можете розширити питання про те, чому ви зараз "дійсно зацікавлені в тому, щоб дізнатися точні деталі". BTW: Чи читали ви код, особливо для DirectByteBuffer?
Пітер Лорі

Вони будуть використовуватися для читання та запису до SocketChannels, які налаштовані на неблокування. Тож щодо сказаного @bmargulies, DirectByteBuffers працюватиме швидше для каналів.

@Gnarly Принаймні, поточна версія моєї відповіді говорить про те, що очікується, що канали принесуть користь.
bmargulies

Відповіді:


150

Рон Хітч у своїй чудовій книзі Java NIO, здається, пропонує хорошу відповідь на ваше запитання:

Операційні системи виконують операції вводу / виводу на ділянках пам'яті. Ці області пам'яті, що стосується операційної системи, є суміжними послідовностями байтів. Тож не дивно, що в операціях вводу / виводу можуть брати участь лише байтові буфери. Також пам’ятайте, що операційна система буде безпосередньо отримувати доступ до адресного простору процесу, в даному випадку - процесу JVM, для передачі даних. Це означає, що області пам’яті, які є цілями переходів вводу / виводу, повинні бути суміжними послідовностями байтів. У JVM масив байтів може не зберігатися постійно в пам'яті, або колектор сміття може перемістити його в будь-який час. Масиви - це об'єкти в Java, і спосіб зберігання даних всередині цього об'єкта може змінюватись від однієї реалізації JVM до іншої.

З цієї причини було введено поняття прямого буфера. Прямі буфери призначені для взаємодії з каналами та нативними підпрограми вводу / виводу. Вони докладають максимум зусиль для зберігання елементів байта в області пам’яті, яку канал може використовувати для прямого або сировинного доступу, використовуючи нативний код, щоб повідомити операційній системі безпосередньо злити або заповнити область пам’яті.

Буфери прямого байту зазвичай є найкращим вибором для операцій вводу / виводу. За своєю конструкцією вони підтримують найефективніший механізм вводу / виводу, доступний JVM. Ненаправлені байтові буфери можуть передаватися каналам, але це може призвести до штрафу за продуктивність. Зазвичай неможливо, щоб непрямий буфер був ціллю нативного вводу / виводу. Якщо ви передаєте непрямий об'єкт ByteBuffer каналу для запису, канал може неявно робити наступне під час кожного виклику:

  1. Створіть тимчасовий прямий об'єкт ByteBuffer.
  2. Скопіюйте вміст непрямого буфера у тимчасовий буфер.
  3. Виконайте операцію вводу / виводу низького рівня, використовуючи тимчасовий буфер.
  4. Тимчасовий буферний об’єкт виходить за межі сфери і з часом збирається сміття.

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

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

Компроміс продуктивності використання прямих та непрямих буферів може сильно відрізнятися залежно від проекту JVM, операційної системи та коду. Виділяючи пам'ять поза грою, ви можете піддавати вашій заяві додаткові сили, про які JVM не знає. Залучаючи в рух додаткові рухомі частини, переконайтеся, що ви досягаєте бажаного ефекту. Я рекомендую максимум старого програмного забезпечення: спочатку змусьте його працювати, а потім зробити його швидким. Не надто турбуйтеся про оптимізацію передньої частини; концентруйтеся спочатку на правильності. Реалізація JVM може виконати кешування буфера або інші оптимізації, що дасть вам необхідну продуктивність без великих зайвих зусиль.


9
Мені ця цитата не подобається, бо вона містить занадто багато здогадок. Крім того, JVM, безумовно, не потрібно виділяти прямий ByteBuffer при виконанні IO для непрямого ByteBuffer: достатньо, щоб розмістити послідовність байтів на купі, зробити IO, скопіювати з байтів у ByteBuffer і звільнити байти. Ці області навіть можна кешувати. Але виділяти для цього об’єкт Java абсолютно не потрібно. Реальні відповіді будуть отримані лише шляхом вимірювання. Минулого разу, коли я робив вимірювання, суттєвої різниці не було. Мені доведеться повторно тестувати, щоб придумати всі конкретні деталі.
Роберт Клемме

4
Сумнівно, чи може книга, яка описує NIO (і власні операції), певна. Зрештою, різні JVM та операційні системи керують речами по-різному, тому автора не можна звинувачувати у тому, що він не може гарантувати певну поведінку.
Мартін Тускевічус

@RobertKlemme, +1, ми всі ненавидимо здогадки, однак оцінити продуктивність для всіх основних ОС може бути неможливо, оскільки там надто багато основних ОС. Ще одна публікація спробувала це, але ми можемо побачити безліч проблем з його еталоном, починаючи з "результати коливаються в широких межах залежно від ОС". Крім того, що робити, якщо є чорні вівці, які роблять жахливі речі, такі як копіювання буфера на кожен I / O? Тоді через цих овець ми можемо змусити не допустити написання коду, який ми б інакше використовували, лише щоб уникнути цих найгірших випадків.
Pacerier

@RobertKlemme Я згоден. Тут є занадто багато здогадок. Наприклад, JVM навряд чи може виділяти байтові масиви рідко.
Маркіз Лорн

@ Едвін Далорцо: Навіщо нам потрібен такий байт-буфер в реальному світі? Вони придумані як хак, щоб поділитись пам’яттю між процесом? Скажімо, наприклад, що JVM працює над процесом, і це був би інший процес, який запускається на рівні мережі або каналу зв'язку - який відповідає за передачу даних - ці байтові буфери виділяються для обміну пам'яттю між цими процесами? Будь ласка, виправте мене, якщо я помиляюся ..
Том Тейлор

25

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


Справді. Наприклад, коли потрібно робити IO в Scala / Java і викликати вбудовані Python / native libs з великими в пам'яті даними для алгоритмічної обробки або подачі даних безпосередньо в GPU в Tensorflow.
SemanticBeeng

21

оскільки DirectByteBuffers - це пряме відображення пам'яті на рівні ОС

Вони ні. Вони є просто звичайною пам'яттю процесу додатків, але не підлягають переміщенню під час Java GC, що значно спрощує речі всередині шару JNI. Те, що ви описуєте, стосуєтьсяMappedByteBuffer .

що він буде виконувати швидше з дзвінками get / put

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


7
Це хороший і важливий момент: на шляху ІО вам в якийсь момент потрібно перетнути кордон Ява - JNI . Прямі та не прямі байтові буфери переміщують лише межу: за допомогою прямого буфера всі операції, поставлені з землі Java, повинні перетинатися, тоді як з непрямим буфером всі операції вводу-виводу повинні перетинатися. Що швидше, залежить від програми.
Роберт Клемме

@RobertKlemme Ваше резюме невірно. З усіма буферами будь-які дані, що надходять на Яву і з неї, повинні перетинати межу JNI. Суть прямих буферів полягає в тому, що якщо ви просто копіюєте дані з одного каналу на інший, наприклад, завантажуючи файл, вам взагалі не доведеться вносити його в Java, що набагато швидше.
Маркіз Лорн

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

@RobertKlemme Ваше твердження про те, що "з прямим буфером [лише] всі операції, поставлені з землі Java повинні перетинатися" є невірним. І обидва, і ставки мають перетинатися.
Маркіз Лорн

EJP, ти, мабуть, все ще не вистачає наміченого розрізнення, яке робив @RobertKlemme, вибираючи в одній фразі слово "поставити операції" та використовуючи слова "операції з виводу операцій" у контрастній фразі речення. В останній фразі його намір полягав у тому, щоб посилатися на операції між буфером та певним пристроєм, передбаченим ОС.
naki

18

Найкраще робити власні вимірювання. Швидка відповідь здається, що надсилання з allocateDirect()буфера займає 25% до 75% менше часу, ніж allocate()варіант (тестується як копіювання файлу в / dev / null), залежно від розміру, але сам розподіл може бути суттєво повільнішим (навіть коефіцієнт 100х).

Джерела:


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