#cpp
Мне надо выполнять определенную функцию раз в 20 мс прошедших после выполнения её же(например). Для этого я ставлю отметку после ее выполнения: functime = std::chrono::high_resolution_clock::now(); Затем выполняются еще какие-то действия (проверял, они выполняются за несколько микросекунд). Соответственно, перед очередным вызовом этой функции делаю паузу следующим образом: if (std::chrono::milliseconds(20) > (std::chrono::high_resolution_clock::now() - functime)) { std::this_thread::sleep_for(std::chrono::milliseconds(20) - (std::chrono::high_resolution_clock::now() - functime)); } Но в результате проверки, оказывается, что она выполняется раз в 0.031201 секунды, а не 0.020000. Почему так? P.S. до того, как я внес изменения в код этой программы, но вообще никак не относящийся к данному, все работало отлично. Работаю в VS2013. UPDATE1 Эта функция, которая вызывается, является функцией отправки RTP пакетов, которые должны отправляться каждые 20 мс. К программе подключено несколько "клиентов", которые сами присылают такие же RTP пакеты (прием данных крутится в отдельных потоках в количестве клиентов). отправка данных предполагалась в их родительском потоке, чтобы не грузить ЦП проверкой наличия данных. Соответственно, перед отправкой происходит обработка пришедших пакетов, а затем их отправка обратно всем клиентам также раз в 20 мс(соответственно нужна пауза только перед отправкой первому клиенту, что замораживает следующую обработку данных тоже на 20 мс, что и требуется для нормальной работы). И, хотя обработка данных происходит меньше чем за микросекунду, теперь у меня задержка вместо 20 мс - 31,2. UPDATE2 Выяснил, что тупит компьютер. В тестовом проекте: int main() { using namespace std::chrono; for (int i = 0; i < 100; ++i) { steady_clock::time_point t1 = steady_clock::now(); std::this_thread::sleep_for(std::chrono::milliseconds(20)); steady_clock::time_point t2 = steady_clock::now(); durationtime_span = duration_cast >(t2 - t1); std::cout << " " << time_span.count(); } system("pause"); return 0; } Пишет, что в b-a = 31. Иногда 32... Есть ли какой-то способ сделать тоже самое, но без std::this_thread::sleep_for? Попробовал заменить на boost::this_thread::sleep_for(boost::chrono::milliseconds(20)); результат случаен в диапазоне от 15 до 32. Извращения вида (с различными методами реализации(steady_clock и прочее)): int main() { auto a = GetTickCount(); while ((GetTickCount() - a) < 20) { ; } cout << GetTickCount() -a; system("pause"); return 0; } Тоже выдает результат 31-32... Установка приоритета реального времени только увеличила задержку.
Ответы
Ответ 1
Есть подозрение что Sleep и GetTickCount недостаточно точны для измерения столь малых интервалов времени. Как пример: #include#include #include int main() { LARGE_INTEGER Freq, Time, Current; QueryPerformanceFrequency(&Freq); int64_t Delay = Freq.QuadPart * 0.020; for (int i = 0; i < 20; i++) { int a = GetTickCount(); QueryPerformanceCounter(&Time); do { QueryPerformanceCounter(&Current); } while (Current.QuadPart - Time.QuadPart < Delay); printf("GetTickCount() - %dms, QueryPerformanceCounter() - %fms\n", GetTickCount() - a, (Current.QuadPart - Time.QuadPart) * 1.0 / Freq.QuadPart); } system("pause"); return 0; } Наглядно видно как скачут значения GetTickCount() при постоянном QueryPerformanceCounter(): GetTickCount() - 15ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 32ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 15ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 31ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 16ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 31ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 16ms, QueryPerformanceCounter() - 0.020000ms GetTickCount() - 31ms, QueryPerformanceCounter() - 0.020000ms ... Добавлено: Дальнейшее изучение вопроса выяснило, что Sleep() всё-таки относительно точный (по крайней мере на моей машине), а неточность именно в измерении временных интервалов: int main() { LARGE_INTEGER Freq; LARGE_INTEGER Times[501]; QueryPerformanceFrequency(&Freq); printf("Timer frequency: %lluHz, Resulution: %fmks\n", Freq.QuadPart, (1E6 / Freq.QuadPart)); QueryPerformanceCounter(&Times[0]); for (int i = 1; i <= 500; i++) { Sleep(20); QueryPerformanceCounter(&Times[i]); } int maxDiff = 0; int minDiff = 0x7fffffff; for (int i = 1; i <= 500; i++) { minDiff = std::min(minDiff, int(Times[i].QuadPart - Times[i-1].QuadPart)); maxDiff = std::max(maxDiff, int(Times[i].QuadPart - Times[i-1].QuadPart)); } printf("Sleep(20) accuracy is %f - %f ms\n", (minDiff * 1000) * 1.0 / Freq.QuadPart, (maxDiff * 1000) * 1.0 / Freq.QuadPart); system("pause"); return 0; } Timer frequency: 2929414Hz, Resulution: 0.341365mks Sleep(20) accuracy is 19.218178 - 20.040868 ms Ответ 2
Вы вызываете now() два раза, что не совсем честно. Попробуйте так: auto functime = std::chrono::high_resolution_clock::now(); // ... auto delay = std::chrono::high_resolution_clock::now() - functime; if(std::chrono::milliseconds(20) > delay) { std::this_thread::sleep_for(std::chrono::milliseconds(20) - delay); } По поводу планирования советую понастраивать приоритеты потоков, то есть ваш поток должен получть больший приоритет чем остальные. Еще настоятельно рекомендую не выполнять в критических участках выделение/освобождение ресурсов (память, дескрипторы и т. д.) это довольно таки нестабильные по времени операции.Ответ 3
Как уже выше написали - функции sleep, std::this_thread::sleep_for не гарантируют точности. Почти у всех написано "будет не меньше чем указанно". Но пол беды с этим, допустим, получиться выжать и сделать оправку с заданным интервалом. А ещё есть сеть, на которую сложно влиять. Несколько лет назад я решал подобную задачу и решение получилось следующее. Первоначальный код был такой же как и у Вас - оправили пакет, рассчитали, сколько времени "поспать" и снова отправляем пакет. Но к этому был добавлен код, который подсчитывал, сколько пакетов было отправлено за последние несколько секунд (для ускорения работы был написан простенький класс с буфером типа кольцо, в результате вставка в него выполняется за О(1)). При отправке я смотрел, сколько реально пакетов удалось отправить за последние время. Если пакетов не хватало - то отпавлялся ещё один-два внеочереди. Также уменьшалось немножко время для "сна". Если пакетов отправилось больше, чем нужно наоборот - пропустить один пакет. Главное - не изменять резко, иначе система постоянно будет болтаться, переключаясь в режимах или залипнет в одном. У меня была в начале ошибка и когда клиент сильно тупил, то время уменьшалось до нуля и оправляло десятки пакетов. Исправлено было тем, что таким клиентов просто отключал. Возникает вопрос, а как же клиент? а клиенты держат буфер и складывают туда пришедшие пакеты. А воспроизводят с нужной им скоростью. Если проигрывается музыка, то размер буфера в 10 пакетов (0.2 секунды) абсолютно не мешает. При разговоре (VoIP) уже в 0.1 секунды может быть заметно на слух. Но некоторых даже секундная задержка не пугает - главное, что бы эта задержка не сильно плавала. Этот способ очень хорошо работает даже при плохих сетях, главное научиться правильно выбрасывать пакеты. Просто так выбросить пакет нельзя - на принимающей стороне с большой вероятностью будет "щелкать". Кстати, вместо sleep и подобных функций я использовал то, что у меня клиенты обслуживается в poll и просто корректировал таймаут для сокета. Плюс - не нужен дополнительный тред, минус - в этом треде было много ещё чего. Но оно работало. Да, я не изобретал этот алгоритм - так работают многие стримминговые сервисы.Ответ 4
Вы определитесь, что именно Вам нужно. Или задержка в 20msec между временем завершения функции и следующим ее запуском, или запуск раз в 20msec. В первом случае, при большом числе итераций, неизбежно будут набегать лишние секунды. Во втором случае (его можно назвать "вызов по расписанию") надо планировать время запуска (конечно, на практике вычисляя величину задержки), отталкиваясь от времени первого вызова функции и количества итераций. Т.е. для запланированного интервала между вызовами равным plan_delay, задержка перед n-тым вызовом равна delay = t_start + n * plan_delay - t_now;Ответ 5
Проблема оказалась в системном таймере, а точнее в разрешение аппаратного таймера. Иногда он слетал на значение по умолчанию 15,6мс, что и давало разброс. Решение: добавил первой строчкой в main: SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);//необязательно, но у меня приложение реального времени timeBeginPeriod(1);// устанавливаем разрешение 1мс
Комментариев нет:
Отправить комментарий