вторник, 4 октября 2011 г.

Параллельность

Пень-пень-дикуляр.
Евгений М.

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

Сначала несколько слов про последовательный код. Я всего лишь месяц назад узнал, что код (a < 0 ? -a : a) не всегда возвращает положительное число в 32-битных целых. Всю коротко прожитую жизнь был уверен(!) что все хорошо. Но фишка в том, что для -MAXINT возвращается оно же (в с++). В .нете если вызвать Math.Abs(), то получим эксепшн; если же вручную напишем, то можно получить CE.

На днях заказчик прислал excel, с фразой что у вас в приложении числа расходятся. Посмотрел - не нашел расхождений. Спрашиваем. Отвечают, да вот же. Смотрим, расхождение в 10 знаке. Физически, это 0.2 грамма на месторождение! Вот если заправляетесь на бензозаправке, то там нальют с точностью до 30 грамм. А тут 0.2 ГРАММА НА МЕСТОРОЖДЕНИЕ. Очень сильно захотелось послать читать стандарт IEEE754, но там все равно не поймут. Пришлось найти числа, где в приложении считается до 14 знака, в эталоне до 15. Потом зачем-то идут логарифмы и очень длинным путем это вылазит в 10 знак. Ладно, что они сейчас более спокойны, а то было что промежуточные вычисления были в 1e-129, и там сильнее расходилось. А то что масса электрона где-то в 1e-38 (?) их не волнует. Они настолько суровы, что могут делить электроны более чем на 1e81 частей.

А с параллельностью все становится интереснее. Поймать (определить) багу - +5 к опыту, -3 дня к жизни. Потом понять что-где-зачем и ее править. Хотя, с одной стороны все довольно просто, когда знаешь как оно работает. В противном случае, когда что-то не работает - начинаешь говорить в трубку в душе "сошла с ума". На 4-ый день приходит просветление, и находишь мутабельный объект.

Всего нашли пока около 5 багов с параллелизмом. Есть кусок с mapReduce (там все тривиально), и есть кусок, по шаблонам это называется Реестр/Акторы (там не все тривиально, и даже местами все не тривиально). Хотя все решается простыми lock-ами. DeadLock пока в принципе возникнуть нигде не должен, а dataRace-ы вылазят. Если успели понаписать 20Мб кода в однопоточном стиле, и потом воткнули параллельность, то потом обнаружите кучу удивительных вещей.

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

Постепенно, это все находится и правится. Проблема в том, что понять что наличие параллельной ошибки - нетривиально. После того, как понял - ну поставил ты брекпойнт, поймал что-то. И чё? И так 4 дня сидишь, и втыкаешь. А потом - Эврика! В последовательном коде фаза "И чё - Эврика!" длится минут 20 максимум. В противном случае, пишешь еще кучу кода, чтобы понять что же там происходит. А когда что-то пишешь, ты уже меняешь систему, это может и не повториться.

А мутабельность - зло, на уровне с goto, object-type, делегатами и out параметрами. Только ж надо вспомнить, что она еще где-то в коде осталась...

И никакие юнит-тесты не спасают, они почему-то все работают. Хотя когда я писал первые, они падали. Потом зачем-то заработали, хотя ошибка легко вручную воспроизводится. В итоге все тесты работают однопоточно.

Для deadlock-ов в теории есть формализмы и верификаторы и прочий modelChecking, для dataRace - кроме головы я пока ничего не знаю. Надо было идти на лекцию Кости С., но там ПАРАЛЛЕЛЬНО шла другая крутая лекция :) .

Комментариев нет: