Доброго времени суток, господа Ось-Девелоперы:)
; Я у вас тут новенький. Вот тоже интересуюсь разного рода разработками. ; ...в основном теоритическими)) Уже пол года борюсь с миниксом, ; и всё бы ничего, да вот написан он для наглядности, а не для работы, ; а основной удар пришёлся какрас по производительности... ; Ясно, что это связано и с его микроядерной архитектурой. ; Все попытки найти информацию по оптимизации и "заточке" оси ; приводят либо к дорогим и тяжёлым стандартам, либо к оптимизации ; на уровне языка. ; Здесь я хотел-бы поднять тему производительности современных систем, ; которая касается не только микроядер, но и всех систем вцелом. ; В качестве старта хочу рассмотреть тему многопоточности, которую поднял ; сегодня утром, после чего решил зарегистрироваться у вас. ; Просьба сильно не бить камнями. Таки вот...
* * * * * * * * * * * * * * * * * * * * * * *
Допустим, что потоки, занятые решением одной задачи обмениваются данными через общий блок данных (разделяемый объект), и доступ к нему синхронизирован двоичным семафором (мутексом). Если не учитывать потери времени на реализацию IPC и синхронизацию, и принять, что один независимый поток решает задачу за условное время (t), то минимальное время (t.min), за которое потоки решат задачу равно:
t.min = n/s;
Где: n - общее число потоков, разделяющих процессор. s - число потоков, занятых решением одной задачи.
Но если учесть особенность механизма синхронизации, которая заключается в блокировании потока, пытающегося захватить занятый мутекс, то возникает совсем другая картина.
Представим обычную ситуацию взаимоисключения. Для простоты представления возьмём очередь из 6-ти потоков, из которых два (А,В) заняты решением одной общей задачи и имеют один общий разделяемый объект (для связи).
Допустим, «А» обращается к объекту и захватывает мутекс. В этот момент происходит аппаратное прерывание (таймер), и диспетчер передает управление следующему по списку очереди потоку (например «В»). «В», в свою очередь, желая обратиться к объекту, пытается захватить занятый мутекс, и оказывается заблокирован. Если А для работы с объектом требуется время равное или меньшее предоставленного ему кванту времени, то «В» выйдет из состояния блокировки не раньше, чем пройдёт очередь следующих 4-х потоков, и управление перейдет к «А», который освободит мутекс.
tb = n*р;
Здесь: tb - время, в течении которого В заблокирован. р - целое число квантов времени, необходимое для работы с объектом.
А т.к. скорость работы системы (в данном случае решения задачи) ограничена скоростью самого медленного из её элементов (В), получаем:
t.max = t.min + tb*q;
q - это показатель того (%), насколько часто один из потоков оказывается в состоянии блокировки, по отношению к общему времени решения задачи. При взаимной блокировке q равно бесконечности.
Для примера я использовал простую кольцевую очередь (карусель) с небольшим числом потоков. При приоритетном планировании вероятность блокировок не увеличивается, но если происходит взаимоисключение потока высокого приоритета, потоком с приоритетом пониже, возникает необходимость в изменении очерёдности их исполнения (инверсия приоритета), а это дополнительные расходы времени. Напротив, с увеличением числа потоков растёт время "стояния в очереди", и увеличивается вероятность взаимоисключений.
В отличии от "простой модели" реальное поведение системы с полноценной приоритетной многозадачностью и временами нахождения в критических секциях в интервале от долей микросекунд (разделяемая память), до нескольких секунд (устройства ввода-вывода), сложно представить "на бумаге", но очевидно, что потери производительности не просто значительные.
Как легко догадаться, для уменьшения потерь вызванных последствиями синхронизации, необходимо сводить вероятность взаимоисключений к нулю. Этого можно достигнуть уменьшая общее количество входов в критические секции, и время нахождения в них.
Но если поток может гарантировать гораздо меньшее время нахождения в КС, чем предоставленный ему квант времени, а промежутки между входами намного большие этого кванта, то взаимоисключения становятся невозможны.
* * * * * *
При работе с буферами обмена возникает ситуация, известная как "проблема потребителя и производителя". Суть её заключается в том, что если один поток "производитель" записывает информацию в буфер быстрей, чем другой читает, возникает момент, когда буфер переполняется, и "производитель", в следствии, блокируется системой до тех пор, пока "потребитель" ни опустошит буфер. Далее блокируется "потребитель". Как видно из контекста выше, блокировки приводят к огромным потерям производительности. Но эту ситуацию нельзя разрешить редкими и быстрыми операциями чтения и записи в буфер. Работа таких потоков выглядит "скачкообразно", а скорость передачи данных через буфер (без учёта простоя в блокировках!) равна скорости самого медленного потока. А если потоки, при этом, имеют разные приоритеты, то блокировка потока с большим приоритетом затягивается на неопределённый срок. На практике ситуация разрешается инверсией приоритетов, притом тогда, когда уже имеет место блокировка. Можно попробовать разрешить ситуацию следующим образом: условно разделить буфер на три части. Нижнюю и верхнюю области назовём "горячими", а центральную, соответственно, "холодной". Каждый раз когда производитель обращается к буферу, он проверяет уровень "наполненности" буфера, после чего записывает в него данные. Если уровень выше некоторого значения (горячая область), то "производитель" обращается к системе с просьбой понизить его приоритет, а приоритет "потребителя" повысить. Аналогично поступает и "потребитель", если видит, что уровень упал. Изменение приоритетов возможно производить до некоторого установленного предела, или до тех пор, пока уровень наполненности буфера не вернётся в "холодную область". При таком подходе исключены блокировки, по причине переполнения или опустошения буфера, а скорость передачи данных становится равной средней скорости потоков, т.к. их скорости "выравниваются".
|