Я написал реализацию потоков, которая выполняет четыре простых сокращения (+ и <) в строках файла.
Сначала я выполнил четыре потока, но я решил написать свой собственный накопитель и объединитель, чтобы я мог выполнять все четыре сокращения в одном потоке. На небольших наборах данных (10 000 000 строк) это сокращает время выполнения примерно до 1/4, как и ожидалось, и работает через 14 секунд на моем оборудовании.
fileIn = new BufferedReader(new InputStreamReader(
new URL(args[0].trim()).openStream()));
final Results results = fileIn.lines()
.parallel()
.skip(1)
.map(User::parse)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Results::new, Results::accumulate, Results::combine);
Results::accumulate
и Results::combine
корректно объединяют пользователей в результаты и результаты с результатами соответственно, и эта реализация работает
отлично подходит для небольших наборов данных.
Я попытался использовать .reduce()
, а результаты аналогичны, но я попытался .collect()
уменьшить создание короткоживущих объектов.
Проблема в том, что когда я использую данные реального мира с 1 миллиардом строк, я сталкиваюсь с проблемой, которая говорит о том, что потоки Java 8 неспособны выполнить эту задачу. Память кучи наблюдается в JConsole, чтобы подняться до выделенного 12 ГБ примерно линейно, а затем OOM.
У меня создалось впечатление, что сборщик или редуктор обеспечит производительность, сравнимую с итеративным решением, которое должно быть ограничено процессором и IO, но не памятью, потому что шаг восстановления дает результат, который не растет, это сокращение
Когда я беру кучу кучи и помещаю его в jhat, я вижу, что около 7 ГБ заняты строками, и эти строки могут быть четко видны как строки входного файла. Я чувствую, что они не должны быть в памяти, но jhat показывает очень большую связанную с ForkJoin структуру, которая накапливается в памяти:
Static reference from java.util.concurrent.ForkJoinPool.common (from class java.util.concurrent.ForkJoinPool) :
--> [email protected] (76 bytes) (field workQueues:)
--> [Ljava.util.concurrent.ForkJoinPool$WorkQueue;@0x786eda598 (144 bytes) (Element 3 of [Ljava.util.concurrent.ForkJoinPool$WorkQueue;@0x786eda598:)
--> [email protected] (96 bytes) (field currentSteal:)
--> [email protected] (130 bytes) (field completer:)
--> [email protected] (130 bytes) (field completer:)
--> [email protected] (130 bytes) (field leftChild:)
--> [email protected] (130 bytes) (field localResult:)
--> [email protected] (53 bytes) (field spine:)
--> [[Ljava.lang.Object;@0x7b25ffe48 (144 bytes) (Element 12 of [[Ljava.lang.Object;@0x7b25ffe48:)
--> [Ljava.lang.Object;@0x7b37c4f20 (262160 bytes) (Element 19598 of [Ljava.lang.Object;@0x7b37c4f20:)
--> 31ea87ba876505645342b31928394b3c,2013-11-24T23:02:17+00:00,898,22200,1314,700 (28 bytes) (field value:)
--> [[email protected] (170 bytes) // <<<< There are thousands of these
В ApplicationShutdownHooks, локальных ссылках и системных классах есть другие ссылки, но я показываю суть проблемы, и это заставляет память расти O (n), когда
Реализует ли реализация потоков эту O (1) память O (n) памяти, удерживая все строки в классах ForkJoin? Я люблю потоки, и я не хочу, чтобы это было так: (