В исходном коде, который вы разместили, я не вижу очевидного узкого места.Когда параллельный код выполняется медленнее, наиболее распространенными объяснениями являются либо издержки из-за синхронизации, либо выполнение дополнительной работы.
Когда речь идет о синхронизации, высокая загруженность может привести к тому, что параллельный код будет выполняться очень медленно.Это может означать, что потоки (или процессы) борются за ограниченные ресурсы (например, ожидают блокировки), но это также может быть более тонким, чем доступ к той же памяти с использованием атомарных операций, что может быть довольно дорогостоящим.В вашем примере я не видел ничего подобного.Похоже, единственной операцией синхронизации в конце являются защелки обратного отсчета, что не должно быть значительным.Неравные рабочие нагрузки также могут повредить масштабируемости, но в вашем примере это маловероятно.
Выполнение дополнительной работы может быть проблемой.Может быть, вы копируете больше данных в параллельной версии, чем в последовательной?Это может объяснить некоторые накладные расходы.Другое предположение состоит в том, что в параллельной версии локальность кэша была затронута отрицательно.Обратите внимание, что эффект кэша является значительным (как правило, доступ к памяти может стать в 50-100 раз медленнее, когда ваша рабочая нагрузка больше не помещается в кэш).
Как найти узкое место?В общем, это называется профилированием.Существуют специализированные инструменты, например, VisualVM - это бесплатный инструмент для Java, который можно использовать в качестве профилировщика.Другой, даже более простой, но часто очень эффективный первый подход - это запустить вашу программу и получить несколько случайных дампов потоков.Если у вас есть очевидное узкое место, вполне вероятно, что вы увидите его в трассировке стека.
Технику часто называют профилировщиком бедняка, но я нашел ее очень эффективной (см. этот ответ Больше подробностей).Кроме того, вы также можете безопасно применять его в производственной среде, так что это хитрый трюк, когда вам нужно оптимизировать код, который вы не можете запустить на локальном компьютере.
IDE (например, Eclipse или IntelliJ) имеют поддержкуПоток дампов, но вы также можете запустить его непосредственно из командной строки, если вы знаете идентификатор процесса:
kill -3 JAVA_PID
Программа (или JVM, которая ее запускает) затем распечатает текущую трассировку стека всех текущих потоков,Если вы повторите это пару раз, вы должны понять, на что ваша программа тратит большую часть своего времени.
Вы также можете сравнить ее с вашей последовательной версией.Возможно, вы заметили какой-то паттерн, объясняющий издержки параллельной версии.
Надеюсь, это немного помогло начать работу.