Являются ли 16 миллисекунд необычно большим временем ожидания выполнения незаблокированного потока, работающего в Windows?

Недавно я выполнял некоторые глубокие проверки времени в приложении DirectShow, которое у меня есть в Delphi 6, используя компоненты DSPACK. В рамках моей диагностики я создал класс Critical Section, который добавляет функцию тайм-аута к обычному объекту Critical Section, присутствующему в большинстве языков программирования Windows. Если время между первой Acquire() и последней совпадающей Release() превышает X миллисекунд, генерируется исключение.

Первоначально я установил тайм-аут на 10 миллисекунд. Код, который я завернул в Critical Sections, работает довольно быстро, используя в основном перемещение и заполнение памяти для большинства операций, содержащихся в защищенных областях. К моему большому удивлению, я получил довольно частые тайм-ауты в, казалось бы, случайных частях кода. Иногда это происходило в блоке кода, который итерирует список буферов и последовательно выполняет определенные быстрые операции, иногда в крошечных участках защищенного кода, который выполнял только очистку флага между вызовами Acquire() и Release(). Единственная закономерность, которую я заметил, заключается в том, что длительность, обнаруженная в момент тайм-аута, была сосредоточена на среднем значении около 16 миллисекунд. Очевидно, что это огромное количество времени для установки флага в последнем примере события, о котором я упоминал выше.

Итак, мои вопросы:

1) Возможно ли, чтобы код управления потоками Windows довольно часто (примерно раз в несколько секунд) отключал незаблокированный поток и не возвращался к нему в течение 16 миллисекунд или дольше?

2) Если это разумный сценарий, какие шаги я могу предпринять, чтобы уменьшить количество таких случаев, и следует ли мне подумать о повышении приоритетов потоков?

3) Если это не разумный сценарий, что еще я должен рассмотреть или попробовать в качестве метода анализа, чтобы диагностировать реальную проблему?

Примечание. Я использую Windows XP на четырехъядерном процессоре Intel i5 с 3 ГБ памяти. Кроме того, причина, по которой мне нужно быть быстрым в этом коде, связана с размером буфера в миллисекундах, который я выбрал в своих графиках фильтра DirectShow. Чтобы свести задержку к минимуму, аудиобуферы на моем графике доставляются каждые 50 миллисекунд. Поэтому любая операция, которая занимает значительный процент этого времени, вызывает беспокойство.


person Robert Oschler    schedule 30.11.2011    source источник
comment
Как вы измеряете время?   -  person Jon Skeet    schedule 30.11.2011
comment
Я проверяю системные часы. В Delphi это функция Now.   -  person Robert Oschler    schedule 30.11.2011
comment
Это проблема для начала - это, вероятно, гранулярность системных часов.   -  person Jon Skeet    schedule 30.11.2011
comment
Разве тогда я не буду получать тайм-аут всегда, а не иногда? Или сумма ошибки плавает? В любом случае, что еще я мог бы использовать? Счетчики производительности кажутся настоящей змеиной ямой из-за степпинга скорости процессора, так что же еще?   -  person Robert Oschler    schedule 30.11.2011
comment
Честно говоря, я не уверен, что вы можете эффективно написать тайм-аут менее 10 мс, используя системные часы.   -  person Jon Skeet    schedule 30.11.2011
comment
Означает ли это также, что указание времени ожидания менее 16 миллисекунд в вызовах объектов синхронизации (WaitFor) также вызывает подозрения? Или в Windows есть более точное разрешение вызовов WaitFor*()? Тот же вопрос о функции Sleep(), если вы не возражаете.   -  person Robert Oschler    schedule 30.11.2011
comment
Не уверен, если честно. Это вполне может зависеть от версии Windows. Я почти уверен, что Sleep будет иметь минимальное полезное значение 15 мс.   -  person Jon Skeet    schedule 30.11.2011


Ответы (3)


Приоритеты потоков определяют, когда запускаются готовые потоки. Однако есть механизм предотвращения голодания. Существует так называемый Balance Set Manager, который просыпается каждую секунду и ищет готовые потоки, которые не запускались в течение примерно 3 или 4 секунд, и если есть, он повышает свой приоритет до 15 и дает ему двойное значение. нормальный квант. Он делает это не более чем для 10 потоков одновременно (в секунду) и одновременно сканирует не более 16 потоков на каждом уровне приоритета. В конце такта повышенный приоритет падает до базового значения. Вы можете узнать больше в книге(ах) Windows Internals.

Таким образом, это вполне нормальное поведение, которое вы наблюдаете, потоки могут не запускаться в течение нескольких секунд.

Возможно, вам придется повысить приоритеты или иным образом рассмотреть другие потоки, конкурирующие за процессорное время.

person Alexey Frunze    schedule 30.11.2011
comment
Кроме того, я хотел бы добавить, что случайные блоки памяти могут быть выгружены на диск, поэтому итерация списка буферов может вызвать ошибку страницы, и поток ожидает чтения памяти с диска. - person Ruslan Yushchenko; 01.12.2011

звучит как обычное поведение Windows в отношении разрешения таймера, если вы явно не используете некоторые высокоточные таймеры. Некоторые подробности в этой ссылке msdn

person Matt    schedule 30.11.2011
comment
См. мой ответ Джону о том, как еще измерить время, пожалуйста. Мне нужны ваши комментарии. - person Robert Oschler; 30.11.2011

Прежде всего, я не уверен, что Delphi Now является хорошим выбором для измерений с миллисекундной точностью. GetTickCount и QueryPerformanceCoutner API были бы лучшим выбором.

Когда при блокировке критической секции нет коллизий, все выполняется довольно быстро, однако, если вы пытаетесь войти в критическую секцию, которая в данный момент заблокирована в другом потоке, в конечном итоге вы сталкиваетесь с операцией ожидания на внутреннем объекте ядра (мьютексе или событии), что включает передачу управления потоком и ожидание, пока планировщик вернет управление позже.

«Позднее» выше будет зависеть от нескольких вещей, включая упомянутые выше приоритеты, и есть одна важная вещь, которую вы пропустили в своем тесте — какова общая загрузка ЦП во время вашего тестирования. Чем больше нагрузка, тем меньше шансов, что поток вскоре продолжит выполнение. Время в 16 мс, возможно, все еще находится в пределах разумного допуска, и в целом это может зависеть от вашей фактической реализации.

person Roman R.    schedule 01.12.2011
comment
В веб-документах, которые я читал, указано, что надежное обнаружение изменения скорости процессора, которое сделает недействительным измерение QueryPerformanceCounter(), непростая задача. Знаете ли вы что-то простое в реализации, которое будет работать? Я посмотрю на GetTickCount, спасибо. - person Robert Oschler; 01.12.2011
comment
GetTickCount прост и надежен, QueryPerformanceCounter имеет гораздо большую точность, но может быть неточным для долгосрочных измерений, см. stackoverflow.com/questions/7583074/ - person Roman R.; 01.12.2011
comment
Now() в Delphi использует в своей реализации GetLocalTime(), что дает ту же точность, что и GetTickCount(). GetSystemTimeAsFileTime() ничем не лучше, хотя возвращает значение, кратное 1E-7 секундам. QueryPerformanceCounter() единственный, который может помочь, несмотря на все проблемы с ним. - person mghie; 03.12.2011