исчезнувшая масштабируемость - PullRequest
0 голосов
/ 16 февраля 2012

Я сделал игрушечную программу для проверки производительности параллелизма Java.Я положил это здесь: https://docs.google.com/open?id=0B4e6u_s5iHT6MTNkZGM5ODQtNjZmYi00NTMwLWJlMjUtYzViOWZlMDM5NGVi

Он принимает целое число в качестве аргумента, который указывает, сколько потоков использовать.Программа просто вычисляет простые числа из диапазона.Обобщенную версию получают, комментируя строки 44 ~ 53, и она генерирует почти идеальную масштабируемость.

Однако, когда я раскомментирую строку 44 ~ 53, которая выполняет простые вычисления локально, и настраивает переменную s наДостаточно большое значение, масштабируемость может исчезнуть.

Мой вопрос заключается в том, использует ли моя игрушечная программа общие данные, что может привести к снижению производительности параллелизма.И как объяснить исчезнувшую масштабируемость (я думаю, что это связано с низким уровнем издержек, например сборкой мусора)?Любое решение может решить такие проблемы, как этот случай?

1 Ответ

2 голосов
/ 16 февраля 2012

Код, о котором идет речь:

int s = 32343;
ArrayList<Integer> al = new ArrayList<Integer>(s);
for (int c = 0; c < s; c++) {
    al.add(c);
}
Iterator<Integer> it = al.iterator();
if (it.hasNext()) {
    int c = it.next();
    c = c++;
}

Конечно, это снизит производительность, если вы увеличите значение s, так как s контролирует, сколько вещей вы поместите в список. Но это очень мало связано с параллелизмом или масштабируемостью. Если вы пишете код, говорящий компьютеру тратить время на тысячи или миллионы одноразовых вычислений, то, конечно, ваша производительность будет ухудшаться.

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

С точки зрения того, почему это может уменьшить преимущества параллелизма, рассматривали ли вы последствия для памяти, когда s становится больше? Например, вы уверены, что куча Java достаточно велика, чтобы хранить все в памяти, не выгружая ее на диск? И даже если ничего не выгружается, увеличивая длину ArrayList, вы даете сборщику мусора больше работы во время его работы (и, возможно, увеличиваете частоту, с которой он работает). Обратите внимание, что в зависимости от реализации сборщик мусора может приостанавливать все ваши потоки при каждом запуске.

Интересно, если вы выделите один экземпляр ArrayList для каждого потока во время создания потока, а затем повторно используете это при вызове isPrime() вместо того, чтобы каждый раз создавать новый список, это улучшает ситуацию?

Редактировать: Вот исправленная версия: http://pastebin.com/6vR7Uhez

Это дает следующий вывод на моей машине:

------------------start------------------
1 threads' runtimes:
1       3766.0
maximum:    3766.0
main time:  3766.0
------------------end------------------
------------------start------------------
2 threads' runtimes:
1       897.0
2       2483.0
maximum:    2483.0
main time:  2483.0
------------------end------------------
------------------start------------------
4 threads' runtimes:
1       576.0
2       1473.0
3       568.0
4       1569.0
maximum:    1569.0
main time:  1569.0
------------------end------------------
------------------start------------------
8 threads' runtimes:
1       389.0
2       965.0
3       396.0
4       956.0
5       398.0
6       976.0
7       386.0
8       933.0
maximum:    976.0
main time:  978.0
------------------end------------------

... который показывает почти линейное масштабирование при увеличении количества потоков. Проблемы, которые я исправил, были комбинацией пунктов, поднятых выше и в ответе Джона Винта (теперь удаленном), а также неправильное / ненужное использование структур ConcurrentLinkedQueue и некоторая сомнительная логика синхронизации.

Если мы включим ведение журнала GC и профилируем обе версии, мы увидим, что оригинальная версия тратит в 10 раз больше времени на сборку мусора, чем модифицированная версия:

Original:  [ParNew: 17401K->750K(19136K), 0.0040010 secs] 38915K->22264K(172188K), 0.0040227 secs]
Modified:  [ParNew: 17024K->0K(19136K),   0.0002879 secs] 28180K->11156K(83008K),  0.0003094 secs]

Что означает для меня, что между постоянными распределениями списков и Integer автоматической коробкой исходная реализация просто перетаскивала слишком много объектов, что накладывает слишком большую нагрузку на ГХ, что снижает производительность ваших потоков до такой степени, что не было никакой выгоды (или даже негативной выгоды) для создания большего количества потоков.

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

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