phantom-84 писал(а):
SII писал(а):
А зачем передавать управление другому потоку пользователя, если для первого в данный момент выполняется код ядра? Такое переключение просто должно быть отложено, и всё.
При таком подходе слишком неравномерно будет распределяться время между потоками, даже если штрафовать потоки, работающие дольше других в режиме ядра.
Если ядро сделано настолько плохо, что по полчаса обрабатывает существенную часть запросов, то этому случаю уже ничего не поможет. Обсуждать такое положение вещей смысла нет, потому будем считать, что ядро достаточно вменяемо спроектировано и реализовано, а посему не тратит на обработку запроса потока очень много времени, а впустую его вообще не расходует. Замечу, что абсолютное время обработки конкретного запроса, выраженное, например, в миллисекундах, может быть довольно большим (предположим, что обработку пришлось приостановить, поскольку потребовалось загрузить страницу памяти с диска), однако время, затраченное процессором на обработку запроса, в любом случае невелико, ну а пока обработка запроса приостановлена, процессор предоставлен другому потоку, а не работает вхолостую.
Поскольку мы предположили, что само по себе ядро достаточно быстрое, можно считать, что типичный квант времени, выделяемый потоку, будет существенно больше типичного времени, необходимого ядру на обработку запроса. В такой ситуации "отъеданием" времени чужого потока можно попросту пренебречь, поскольку: а) потери будут невелики (обработка занимает лишь малую часть времени, выделенного потоку); б) случаться такая ситуация будет довольно редко (ведь для этого нужно, чтобы запрос от потока поступил "под занавес" его кванта, а не в начале или середине).
Не соблюдаться предположение о соотношении величины кванта и времени обработки может лишь в двух случаях: либо система дубовая (что мы отвергаем), либо квант слишком мал. Однако сильное его уменьшение нецелесообразно, поскольку резко увеличивает накладные расходы на постоянное переключение потоков, ну а попытки справедливо делить оставшееся от переключений время лишь ещё больше его уменьшат, и в итоге производительность системы сильно снизится.
Наконец, ничто не мешает легко и непринуждённо штрафовать "провинившийся" поток. Для этого достаточно хранить для него две величины кванта времени: расчётную ("по умолчанию") и ту, которая будет ему выделена при следующей постановке на процессор. Последняя устанавливается равной расчётной, если поток снят с процессора в результате истечения его кванта, и уменьшается, если истечение кванта наступило в момент, когда ядро обрабатывало запрос этого потока, причём величина уменьшения зависит от того, сколько лишних тиков израсходовало ядро. Таким образом, следующий поток не потеряет своего законного времени, он лишь будет поставлен на процессор чуть позже, чем при простом переключении по таймеру, однако эта задержка с лихвой компенсируется исключением времени, необходимого на лишние "телодвижения" системы для сохранения состояния обработки запроса от одного потока, потом для возобновления этой обработки и т.д. и т.п. Попросту говоря, упрощение логики обработки, в том числе и учёта времени, позволит уменьшить расход времени на работу самой системы, а значит, больше времени останется на работу самих потоков, что, вообще говоря, и является самым важным.
В общем, проблема представляется мне надуманной.
Цитата:
К тому же сам поток может уйти на ожидание или даже добровольно отдать управление в режиме ядра.
"Сам поток" не может "уйти на ожидание" в ядре, поскольку в этот момент выполняется не код потока, а код ядра. Как ядро делит своё время -- это его дело, но поток на это никак повлиять не может. Если же "уходит" ядро, обрабатывающее запрос потока, и управление получает другой поток, то перезагружаются и счётчики времени, поэтому один поток не будет в сколько-нибудь заметных дозах пожирать чужое время.
Цитата:
Я имел в виду сложную обработку запросов, последовательно выполняемую разными компонентами ядра и требующую достаточной глубины стека.
Честное слово, я не вижу ни одного случая, где реально может потребоваться такая сложная глубоко вложенная обработка.
Цитата:
Я сейчас статически выделяю буфер для сохранения контекста FPU/SSE - это 0,5 кб, плюс "основные поля", плюс управление рабочим набором, плюс управление обработкой исключений в потоке.
Что такое рабочий набор, мне не очень понятно; управление обработкой исключений в потоке требует, ИМХО, мизерного объёма (правда, это сильно зависит от того, как именно она организована). Но в пределах полукилобайта расход действительно оправдан, раз уже сохраняется контекст FPU/SSE: понятно, что его не соптимизируешь.
Цитата:
Про структуру процесса я вообще не говорю, хотя она и разделена на несколько частей, большинство из которых находится в области локальных данных ядра.
Где находятся, в данном случае не шибко важно, роль играет именно общий расход памяти. И дело не в том, что её мало (на ПК её хоть залейся, это вот попробуй на АРМе обойтись лишь встроенной памятью -- от 16 до пары сотен килобайт ОЗУ и от нескольких десятков до нескольких сотен килобайт флэш-памяти). Нужно помнить, что "раздувание" структур данных ведёт не только к расходу ОЗУ, но и к постоянной пересылке данных между ОЗУ и кэшем, что пагубно влияет на производительность. Собственно, грамотная структура данных в любой задаче -- непременное условие получения хорошего результата...