Как Java управляет многопоточным доступом к элементам массивов? - PullRequest
5 голосов
/ 24 февраля 2011

Здравствуйте, коллеги-программисты.Я уже задавал один вопрос , но, несмотря на действительно хорошие ответы, которые я получил, я не смог решить мою проблему.Затем я потратил время на рефакторинг своего кода таким образом, чтобы улучшить его потенциал распараллеливания (имея меньше пакетов вычислений с большим количеством вычислительных обязанностей в каждой).Но все же я не могу иметь лучшую производительность, чем последовательная обработка.

Я подозреваю, что эта медленная параллельная обработка происходит из-за переключения контекста .Или может быть из-за «автоматической» синхронизации общих объектов.Я думаю, что вы можете помочь мне понять, что происходит.

Позвольте мне изложить мой случай: я делаю программу для научных расчетов.Это не зависит от внешних вещей, только от входных значений, которые я даю ему при его запуске.Размер этой проблемы можно измерить с помощью Ns (это имя я использую).Это можно рассматривать как «разрешение» решения, оно является одним из пользовательских вводов и обычно имеет порядок 100.

Таким образом, в моем основном классе есть несколько двойных массивов, напримеркак двойной ys[Ns][N] или phiS[Ns][Nord][N], где N и Nord - другие фиксированные величины программы.В моей программе я должен рассчитать несколько вещей для каждой из Ns точек, и здесь идет распараллеливание.Каждое вычисление точек является независимым, поэтому я могу разделить их на разные потоки и надеяться, что оно станет быстрее.

Таким образом, вместо цикла for (int i=0; i<Ns; <i++) я разделил эту вычислительную обязанность на Runnable партии, каждая из которых находится в пределахменьший интервал: for (int i=start; i<end; i++), где начало и конец всегда между 0 и Ns.Например, если я работаю на двухъядерном компьютере, я делаю две партии, одну с start = 0 и end = Ns/2, другую с start = Ns/2 и end = Ns.Если я работаю на четырехъядерном процессоре, вторая партия будет иметь start = Ns/4 до end = Ns/2 и т. Д. (При условии, что деление является точным в каждом случае).

Каждая партия, как класс, реализующий Runnable, сохраняется в ArrayList<Batch> и присваивается FixedThreadPool с размером, равным количеству ядер.Он выполняет партии и ожидает их завершения, используя простую схему CountDown.

Каждый из этих пакетов должен иметь доступ к данным этих массивов из основного класса программы, но их доступ таков, что каждый пакет читает только от yS[start][] до yS[end][], и поэтому две партии никогда не будутпопробуйте прочитать тот же элемент массива.Интересно, Java все еще блокирует yS, даже если каждый пакет не пытается получить доступ к тем же элементам, что и другие.

Интересно также, связана ли моя проблема с издержками из-за переключения контекста, поскольку каждый пакетНужно иметь дело с тысячами двойников, и если способ, которым построена программа, может повлиять на это.

Может быть, мне нужно найти способ передать каждой партии только те элементы массивов, которые имеют к ней отношение, но я не знаю, как к этому подойти.Если бы были указатели, я мог бы иметь новые массивы только нужных элементов с простыми операциями с указателями и без перераспределения чего-либо.Есть ли способ сделать такую ​​вещь в Java?

Ну, наконец, просто упомянуть: есть одна часть кода, которую нужно синхронизировать (она имеет дело с другими массивами), и она уже работаетхорошо.Эти расчетные обязанности, которые я описал выше, не единственные, что делает моя программа.Они находятся внутри цикла, чередуясь с последовательными частями обработки, но они действительно важны как общее время выполнения.

Итак, подведем итог: вопрос в том, почему я не получаю многопоточность, когда я ожидалto?

Я просто пару раз запускал здесь простой последовательный порт и многопоточную программу и получил 14500 мс для последовательного и 15651 мс для многопоточного.Оба на одном Dual Core.Другой момент, на который следует обратить внимание: при серийном запуске каждая расчетная нагрузка (от 0 до Ns) занимает от 1,1 до 4,5 мс.При двойной резьбе на каждую партию (нс / 2 точки) уходит от 0,5 до 3 мс;(измеряется сверху вниз для метода run (). Каждый раз, когда расчетная пошлина отличается своей числовой сходимостью)

Большое спасибо за внимание.

Ответы [ 4 ]

2 голосов
/ 24 февраля 2011
 I wonder if Java still locks up yS, even that each batch isn't trying to access
 the same elements as others.

В Java нет автоматической синхронизации или блокировки. Вы должны явно кодировать это.

I wonder also if my problem is related to the overhead due to context switching..

Переключатели контекста имеют накладные расходы. Если все ваши потоки работают над одной и той же задачей, которая требует много ресурсов процессора, то количество потоков должно быть равно количеству доступных ядер процессора.

If there were pointers, I could have new arrays of just the desired elements with
simple pointer operations and without reallocating anything.

Все объекты в Java передаются по ссылке (например, когда вы передаете их методу). И в основном все ссылки являются указателями (с той разницей, что вы не можете разыменовать их). Таким образом, никакие объекты не копируются в Java, кроме случаев, когда это явно запрашивается вашим кодом.

При этом вы должны знать о другом: если вы добавляете много элементов в Коллекции (списки, HashMaps и т. Д.), Чем эти Коллекции должны расти. Внутренне все Коллекции используют массивы для хранения элементов, и когда элементы добавляются, размеры массивов необходимо изменять. Поскольку в Java нет способа изменить размер массива, необходимо создать новый массив, а все ссылки на старые объекты скопировать в новый массив. Или, если вы используете примитивные типы, все данные должны быть скопированы. Таким образом, при создании коллекций вы должны подбирать их по размеру, чтобы их не нужно было изменять.

Вы также можете прочитать Сколько потоков я должен использовать в моей Java-программе?

2 голосов
/ 24 февраля 2011

Один из возможных вариантов: потоки, перебирающие строки кэша.Если разные потоки быстро записывают данные в местоположения в одной и той же строке кэша (например, закрывают в одном и том же массиве), то аппаратные средства имеют высокие накладные расходы на связь, гарантирующие, что данные остаются согласованными.

1 голос
/ 24 февраля 2011

Исходя из того, что вы упомянули до сих пор, я бы попробовал следующее:

  1. Сравните результаты между последовательной и параллельной версиями для увеличения размеров ваших массивов.Разница в производительности действительно может быть незначительной для размера вашей проблемы и может проявиться только после того, как размер увеличится, т.е. размер массивов

  2. Дайте каждому исполняемому экземпляру собственную копию массива.С точки зрения производительности, способ размещения массива в памяти и способ доступа к ним может повлиять на производительность.Несмотря на то, что у вас может быть 2D-массив, он будет выложен в виде списка последовательных массивов в памяти.Следовательно, если вы разделяете этот массив между исполняемыми файлами, он может стать неэффективным для некоторых из них.

0 голосов
/ 24 февраля 2011

достаточно ли у вас памяти для создания нескольких коллекций и передачи уникальной коллекции работ каждому потоку, чтобы вы могли полностью исключить конфликт нескольких потоков, обращающихся к одной и той же памяти?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...