Comments 65
long t = timer.Microsec();
while((timer.Microsec()-t)<500){}
Всё равно же за это время операционка потоки не успеет перераспределить и реально «ждать» необязательно
Как это не успеет? Напишите простое пинг-понг приложение и посчитайте, сколько раз будет передано управление из одного потока в другой. Увидите от 10 до 100 тысяч переключений в секунду.
Причём результаты будут одинаковы что при использовании сокетов, что при использовании событий. Планировщик не будет ждать очередного кванта времени, допуская простой процессора, если выполнение потока можно продолжить вотпрямщас.
А нельзя параллельно разжимать кадры? Один поток разжимает один кадр, второй поток разжимает другой кадр… Вы же говорили про 200 видеопотоков.
Смотрите, очевидно, что со слипом в 1 миллисекунду, за 1 секунду можно разжать 1000 кадров. Это на одном ядре. При среднем fps равным 25, это всего 40 потоков. Т.е. на 4-х ядерном проце получается всего 160 потоков (4000 кадров в секунду). А цель: 200 потоков, т.е. 200 * 25 = 5000 кадров в секунду.
А вы не пробовали просто запустить параллельно 200 потоков, а не 4?
Да, вы сами не можете проснуться когда преобразование кадра закончилось. Но драйвер-то наверняка это знает! А значит, пока один поток спит свои 16 милисекунд, другие потоки на том же самом ядре смогут делать свою работу.
Ну и пусть синхронизируются. Важно лишь, что там ниже у них не будет нижнего ограничения на время сна.
Да и вообще 25 fps — это 1 кадр в 40 миллисекунд. Т.е. нам надо, чтобы раз в 40 миллисекунд винда нам выделяла хоть немного времени, чтобы мы успели выгребсти результат предыдущего декодирования, вернули его наверх, нам чтобы спустили новую порцию данных, которые мы бы также запихнули в декомпрессор. Предположим, что винда переключает потоки раз в 15 миллисекунд, т.е. за это время на 4-х ядерном проце успее поработать всего 12 потоков. Пусть мы реально быстро делаем подобные операции (выгребсти ...., запихнуть), скажем 1 миллисекунду, и тут же вызываем yield, чтобы остаток кванта отдать другому потоку. (Ну либо точность таймера 1 миллисекунда). В таком случае, успеет поработать 40*4 = 160 потоков. Хм. что-то всё равно не сходится, надо подумать. Вроде как вариант с 200-та потоками работал (но плохо).
Вы переоцениваете сложность копирования. Если у вас 4 потока успевали все копировать за отведенное время — то и 200 потоков ту же самую задачу должны успеть выполнить. Суммарный объем-то не поменялся!
И даже частые переключения потоков тут не должны стать проблемой, потому что системные вызовы вы в 4 потока делаете даже чаще чем в 200, а кеши процессора все равно бесполезны в деле копирования больших объемов данных.
Проблему при таком подходе я ожидаю в стабильности. Если в четырех-поточном варианте при мгновенном перегрузе один поток задержится на лишнюю половину миллисекунды — то в варианте с 200 потоками куча потоков задержатся на лишние 16 миллисекунд.
Кроме того, многое зависит от реализации на стороне драйвера. Там внутри тоже может слип на 16 миллисекунд стоять :)
Потому и было интересно пробовали ли и что получилось.
Поясните. С помощью QueryPerformanceCounter и QueryPerformanceFrequency можно точно замерять интервалы — это уже давно всем известно. А вот заставить операционную систему напрямую использовать HPET для вызова кода по таймеру все равно не получится, здесь придётся писать свою операционную систему.
Ну да, Stopwatch — обёртка над функциями QueryPerformanceCounter и QueryPerformanceFrequency из API.
Но как это поможет нам сделать Sleep на полсекунды, мне непонятно.
Разве что busy wait с периодической проверкой таймера. Можно вычисление биткоинов запихать, чтоб процессор совсем вхолостую не работал.
Почему же? В посте речь идёт именно о полсекунды. Почему полсекунды — да потому что это минимальной возможный интервал системного таймера в Windows. А загвоздка в том, что sleep принимает целое число в миллисекундах, и поэтому приходится использовать платформозависимый API,
Ожидать событие в цикле со слипом можно только в некритических приложениях. Для целей обработки видео и аудио — это как-то очень не аккуратно.
Вы же сами пишите "Т.е. что-либо утверждать или гарантировать нельзя!". Значит нужно искать решения, где алгоритм будет гарантировать передачу блоков данных точно в нужное время. Наверняка АПИ предполагает какие-то колбэки или события которые можно ждать не в слипах, а скажем в waitforsingleobject(..) или подобных функциях.
Автор так и написал:
Собственно было 2 варианта: либо переделывать всё на асинхронную работу с аппаратным декомпрессором, либо уменьшать время Sleep'а.
Видимо, костыль в виде второго варианта оказался проще.
Нет, статья о том, как сделать Sleep на полсекунды в Windows. А то, что применяется как костыль — на то воля программиста.
У меня не было необходимости делать такой точный Sleep, хотя и писал свой планировщик задач для асинхронного выполнения, но, тем не менее, мне это было интересно.
for (;;) {
if (frame_is_ready())
break;
SwitchToThread();
}
Честно говоря, сам такого никогда не пробовал, не обессудьте если совет в молоко.
Нет гарантии, что другой поток достаточно быстро вернет управление обратно. Поток же, который ожидает таймера, при пробуждении получает повышенный приоритет (на клиентских осях).
Также потоку можно явно поставить повышенный или высокий приоритет. В таком случае таймер будет будить его сразу же при срабатывании, а вот SwitchToThread() просто перестанет работать как задумывалось.
при пробуждении получает повышенный приоритет (на клиентских осях).
Ой, я был уверен, что на серверных всё так же с динамическим повышением приоритетов, как и в клиентских осях :-(. Надо будет себе на заметку взять провести тесты на эту тему.
На серверных точно знаю что приоритет процесса, отвечающего за активное окно, не повышается. Про другие эвристики когда-то знал, да забыл. Может, они продолжают работать, а может и нет.
Использовать Sleep(0) не пробовали? Там выполнение передается потоку по приоритету и может обратно вернуться без ожидания если более приоритетных потоков нет.
Та же проблема что и с SwitchToThread() (см. выше) — нет гарантии что управление вернется обратно быстро, равно как и нет гарантии что оно не вернется сразу же.
Выполнение перейдет в другой поток декодирования, и как только у них не станет работы вернется в ваш, и не станет ждать 20 мс. Т.е. оно начнет загружать потоки по полной и нормально распределять приоритеты, так как потоки не будут находиться в очереди таймера, а только в нормальной очереди. И только если система будет перегружена работой, тогда управление будет возвращаться реже, но это и при ожидании таймера будет происходить. Т.е. 0 задержка это передача выполнения наиболее приоритетному потоку с точки зрения ОС.
Есть некий квант времени, выделяемый потоку на выполнение (да, да, те самые 20 мс)
Насколько я понял (сам в своё время этим же занимался), там не 20мс, а 1/64 с. (т.е. 15-16 мс.)
Call IMFTrackedSample::SetAllocator and provide a pointer to the IMFAsyncCallback interface. (The software decoder must implement this interface.) When the video renderer releases the sample, the callback will be invoked. Use this callback to keep track of which samples are currently available and which are in use.
1. Почему вместо «минимально возможной паузы», Sleep(1) и прочего не использоавать std::this_thread::yield в комбинации с назначением максимального приоритета процессу?
2. Почему для декодирования 60 видеопотоков вы используете Windows?
2) А что не так с Windows? И задача была не 60, а 200 видеопотоков расжимать. Кстати, сейчас под линуксом тоже делаем аппаратный декомпрессор, пока его не удалось заставить более 16 потоков расжимать. Правда при этом задача, что нельзя ни драйвера никакие ставить дополнительные, ни чужой софт.
std::this_thread::yield в комбинации с назначением максимального приоритета процессу приведут к тому, что два таких процесса полностью сожрут одно ядро!
Не могу сказать, что он бы помог именно в этой теме, но для полноты картины, имхо, очень даже ок
Использование каких либо таймеров – это всегда костыль.
Windows: Sleep(0.5)