Java и опашки: проблеми с насищането при многонишков I/O

Този въпрос се отнася до най-новата версия на Java.

30 нишки на производителя избутват низове към абстрактна опашка. Една записваща нишка изскача от същата опашка и записва низа във файл, който се намира на 5400 rpm HDD RAID масив. Данните се изпращат със скорост от приблизително 111 MBps и се изваждат/записват със скорост от приблизително 80MBps. Програмата живее 5600 секунди, достатъчно, за да се натрупат около 176 GB данни в опашката. От друга страна, аз съм ограничен до общо 64 GB основна памет.

Въпросът ми е: Какъв тип опашка да използвам?

Ето какво съм пробвал досега.

1) ArrayBlockingQueue. Проблемът с тази ограничена опашка е, че независимо от първоначалния размер на масива, винаги се оказвам с проблеми с жизнеността веднага щом се запълни. Всъщност, няколко секунди след стартирането на програмата, top отчита само една активна нишка. Профилирането разкрива, че средно нишките производители прекарват по-голямата част от времето си в чакане опашката да се освободи. Това е независимо от това дали използвам или не политиката за справедлив достъп (с втория аргумент в конструктора, зададен на true).

2) ConcurrentLinkedQueue. Що се отнася до жизнеността, тази неограничена опашка се представя по-добре. Докато не ми свърши паметта, след около седемстотин секунди, всичките тридесет нишки на производителя са активни. След като премина лимита от 64 GB обаче, нещата стават невероятно бавни. Предполагам, че това се дължи на проблеми с страниците, въпреки че не съм правил никакви експерименти, за да докажа това.

Предвиждам два изхода от моята ситуация.

1) Купете SSD. Надяваме се, че увеличаването на I/O скоростта ще помогне.

2) Компресирайте изходящия поток преди запис във файл.

Има ли алтернатива? Пропускам ли нещо в начина, по който някоя от горните опашки е конструирана/използвана? Има ли по-умен начин да ги използвате? Книгата Java Concurrency in Practice предлага редица политики за насищане (Раздел 8.3.3) в случай, че ограничените опашки се запълват по-бързо, отколкото могат да бъдат изчерпани, но за съжаление нито една от тях --- прекъсва, извикващият се изпълнява и двете отхвърлят политики --- се прилагат в моя сценарий.


person user2650947    schedule 05.10.2013    source източник
comment
Както осъзнавате, че дискът е вашето тясно място и без значение как играете със софтуера, производителността на вашия диск е това, което трябва да заобиколите. Това означава компресиране на данните и/или закупуване на по-бърз диск. Имайте предвид, че можете да получите 400 MB/s SSD за около $100.   -  person Peter Lawrey    schedule 05.10.2013
comment
Ако вашите производители произвеждат по-бързо от вашия потребител, вие трябва да направите своя потребител по-бърз или вашите производители по-бавни.   -  person Raedwald    schedule 05.10.2013
comment
Изглеждате изненадани, че опитът да съхраните 176GB обекти на 64GB компютър прави вашата програма бавна.   -  person Raedwald    schedule 05.10.2013


Отговори (5)


Потърсете тясното място. Вие произвеждате повече, отколкото консумирате, ограничената опашка има абсолютно смисъл, тъй като не искате да останете без памет.

Опитайте се да направите своя потребител по-бърз. Профилирайте и вижте къде се прекарва най-много време. Тъй като пишете на диск, ето някои мисли:

  • Бихте ли използвали NIO за вашия проблем? (може би FileChannel#transferTo())
  • Промивайте само когато е необходимо.
  • Ако имате достатъчно процесорни резерви, компресирайте потока? (както вече спомена)
  • оптимизирайте вашите дискове за скорост (рейд кеш и т.н.)
  • по-бързи дискове

Както вече каза @Flavio, за модела производител-потребител не виждам проблем там и трябва да е така, както е сега. В крайна сметка най-бавната страна контролира скоростта.

person Ortwin Angermeier    schedule 05.10.2013
comment
Използването на ограничена опашка на практика забавя производителите, докато общата им скорост съвпадне със скоростта на потребителя. - person Raedwald; 05.10.2013
comment
Приемам този отговор поради споменаването на NIO, за което не знаех. Благодаря. - person user2650947; 05.10.2013

Не виждам проблема тук. В ситуация производител-потребител системата винаги ще върви със скоростта на по-бавната страна. Ако производителят е по-бърз от потребителя, той ще бъде забавен до скоростта на потребителя, когато опашката се запълни.

Ако вашето ограничение е, че не можете да забавите производителя, ще трябва да намерите начин да ускорите потребителя. Направете профил на потребителя (не започвайте прекалено изискано, няколко System.nanoTime() обаждания често дават достатъчно информация), проверете къде прекарва по-голямата част от времето си и започнете да оптимизирате оттам. Ако имате затруднение в процесора, можете да подобрите алгоритъма си, да добавите още нишки и т.н. Ако имате затруднение в диска, опитайте да пишете по-малко (компресията е добра идея), вземете по-бърз диск, пишете на два диска вместо на един...

person Flavio    schedule 05.10.2013

Според java "Реализация на опашка" има други класове, които трябва да е точно за теб:

  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • DelayQueue
  • Синхронна опашка
  • LinkedTransferQueue
  • TransferQueue

Не знам ефективността на тези класове или използването на паметта, но можете да опитате сами.

Надявам се, че това ви помага.

person Valerio Emanuele    schedule 05.10.2013

Защо имате 30 производители. Този номер фиксиран ли е от проблемния домейн, или е просто номер, който сте избрали? Ако е последното, трябва да намалите броя на производителите, докато те произвеждат с обща скорост, която е по-голяма от потреблението само с малко количество, и да използвате опашка за блокиране (както други предложиха). Тогава ще държите вашия потребител зает, което е частта, ограничаваща производителността, като същевременно минимизирате използването на други ресурси (памет, нишки).

person Raedwald    schedule 05.10.2013

имате само 2 изхода: направете доставчиците по-бавни или потребителите по-бързи. По-бавните производители могат да бъдат направени по много начини, по-специално чрез използване на ограничени опашки. За да направите потребителя по-бърз, опитайте https://www.google.ru/search?q=java+memory-mapped+file . Вижте https://github.com/peter-lawrey/Java-Chronicle.

Друг начин е да освободите нишката за писане от работата по подготовката на буфери за запис от низове. Нека нишките на производителя излъчват готови буфери, а не низове. Използвайте ограничен брой буфери, да речем 2*номер на нишка=60. Разпределете всички буфери в началото и след това ги използвайте повторно. Използвайте опашка за празни буфери. Произвеждащата нишка взема буфер от тази опашка, запълва го и го поставя в опашка за запис. Записващата нишка взема буфери от записващата нишка, записва на диска и поставя в празната опашка за буфери.

Още един подход е да използвате асинхронен I/O. Производителите сами започват операцията по писане, без специална нишка за писане. Манипулаторът за завършване връща използвания буфер в празни буфери опашка.

person Alexei Kaigorodov    schedule 05.10.2013