оптимизация производительности

 
 
 
Сообщения:10
Добрый день, уважаемые гуру многопоточного программирование. Если Вас не затруднит, ответьте пожалуйста на несколько вопросов:

1 К примеру у меня есть какой-то объект типа Object многопоточное чтение которого происходит гораздо чаще, чем модификация и есть два варианта реализации:

a) обычная переменная Object obj и ReadWriteLock соответственно на методы чтения и модификации

б) volatile Object obj, на метод модификации - простой Lock, создание нового объекта и замены ссылки obj. Метод чтения без синхронизации(не считая записи-чтения volatile переменной)

Какой из вариантов предпочтительнее и будет работать быстрее и почему?

2 Если я решил делать вариант 1.б и точно знаю, что мое приложение будет запускаться на сервере с несколькими процессорами архитектуры X86-64, то мог ли я вообще убрать volatile и нарушив тем самым JMM, но полагаясь на протоколы когерентности кешей процессоров? прочитает ли ядро 2го процессора измененые данные в ядре первого и даст ли это хоть какой-то прирост производительности?
 
 
Сообщения:244
1. В простом случае read-write lock подойдет. С ним единственная типичная проблема - deadlock при upgrade, например, захватил read и потом хочешь еще на write захватить.

2. Не совсем понятно какая семантика нужна. Потому что тут получится, что все читают объект, потом кто-то решил его поменять, захватил Lock (можно просто synchronized метод использовать в принципе), создал новый объект и собирается его записать в поле, но читатели в этом время же могут читать старый объект. Если это устраивает, то может быть не нужен Lock? Просто кто последний в поле записал, тот и прав (если не требуется гарантировать модифкацию объекта в один поток).

Я думаю что второй вариант перспективнее с точки зрения понятности кода. Просто volatile поле и immutable объект, который надо пересоздавать и записывать. Можно заменить volatile на AtomicReference и менять значение через compareAndSet, как вариант, если нужна чуть другая семантика.

Про перформанс тут говорить крайне сложно, потому что синхронизация в JVM и JIT очень сложная штука и надо проверять, причем не микробенчмарками, а именно на реальных тестах. Скорее всего volatile и compare and set будут быстрее работы с Lock и synchronized (потому что те обычно тоже используют CAS внутри), но зависит очень от многих факторов.

Когерентность кешей действительно существует, но в процессорах сейчас есть еще и store buffer'ы (можно считать кешом 0 уровня), которые не когерентны, поэтому без синхронизации написать корректно нельзя.
 
 
Сообщения:2361
slippery:
a) обычная переменная Object obj и ReadWriteLock соответственно на методы чтения и модификации

Если как вы говорите чтения существенно превосходят записи, то на месте варианта a) просто обязан быть StampedLock вместо ReadWriteLock, конечно он в теории всё равно должен быть медленнее чем CopyOnWrite, так как в StampedLock два чтения volatile на горячем участке, а в CopyOnWrite что Вы сделаете будет только одно, но в любом случае он будет быстрее чем ReadWriteLock так как при чтении никуда не пишет, а ReadWriteLock пишет, да еще через CAS. Но я бы прежде всего беспокоился не о лишнем чтении из volatile а о корректности(вернее требованиям к ней), если согласно вашим бизнесс требованиям всё ещё допустимо оперировать данными которые уже поменяли то CopyOnWrite вам в руки, а если нужно незамедлительно прекратить использование данных которые изменены то используйте StampedLock.

slippery:

2 Если я решил делать вариант 1.б и точно знаю, что мое приложение будет запускаться на сервере с несколькими процессорами архитектуры X86-64, то мог ли я вообще убрать volatile и нарушив тем самым JMM, но полагаясь на протоколы когерентности кешей процессоров? прочитает ли ядро 2го процессора измененые данные в ядре первого и даст ли это хоть какой-то прирост производительности?

Напишите benchmark на JMH и запустите его на соответсвующем процессоре. В теории конечно все знают, что чтение volatile на X86 не дороже обычного чтения, но хз как там benchmark покажет как оно есть на практике. А вот плевать на JMM ради производительности я бы не стал, это даже больше не я советую, а умный дядька Gil Tene в этом посте, он CTO в Azul как никак и автор просто адовых библиотек в области concurrency, плохого не посоветует. То что Вы заранее знаете какое железо будет это хорошо, но Вы же наверняка не знаете как JIT будет переколбашивать ваш код, а убрав volatile вы ещё больше развязываете JIT-у руки, и производя различные инлайнинги методов и по разному их компонуя, а потом оптимизируя, он Вам такое в итоге может скомпилить, что рады не будете, допустим находясь внутри большого(ещё хуже бесконечного) цикла положит значение в регистр и не будет перечитывать, а вот в какой конкретный ассемблер JIT скомпилит заранее знать никому невозможно, так как это завист от профиля нагрузки в момент выполнения и погоде на марсе.
Изменен:17 авг 2016 22:59
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет