LINUX.ORG.RU

timer

 ,


3

6

Есть у кого под рукой готовая реализация?

Я накорябал на колене, что то бажненько получилось, охота посмотреть как большие дядьки делают.

Целевое назначение: шедулить задачи в рамках одного потока с микросекундным разрешением(можно порядка 100us), ну и ессно, что бы оно спало, пока задач нет.

Мой цикл выглядит так:

void run() {
    std::unique_lock<std::mutex> lock(lock_);
    auto wait_condition = [this](){return !tasks_.empty();};
    while(started_){
        event_.wait(lock, wait_condition);

        DeadlineT deadline;
        while (started_ && process_tasks(deadline)) {
            event_.wait_until(lock, deadline, wait_condition);
        }
    }
}

//process_tasks - выбирает таски с дэдлайном <= now, выполняет их,   возвращает минимальный дэдлайн оставшихся и не пуста ли очередь задач
★★★★★
Ответ на: комментарий от eao197

Спасибо.

А православные хостинги кода не угодили - чем? Мне конечно проще было бы через веб морду глянуть где там что как, хотя это конечно субьективно, как и православность.

Как товарищ опытный - скажи, чем плох мой цикл?

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от eao197

А спонтанные пробуждения ты осознанно не обрабатываешь?

pon4ik ★★★★★
() автор топика

В wait_contition не хватает флага quit - что бы можно было разбудить поток перед выходом для его завершения.

andreyu ★★★★★
()
Ответ на: комментарий от pon4ik

А православные хостинги кода не угодили - чем?

Я старый уже, мне привычки сложно менять.

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

https://sourceforge.net/p/sobjectizer/repo/HEAD/tree/tags/timertt/1.2.1/dev/t...

А спонтанные пробуждения ты осознанно не обрабатываешь?

А там это вроде как частью логики является. Для timer_wheel-а при пробуждении проверяется, на какой шаг попали. Для остальных механизмов делается сравнение с текущим временем.

Как товарищ опытный - скажи, чем плох мой цикл?

Думаю, что как только потребуется отменять таймеры или переносить на другое время, ситуация станет посложнее. Ну а так-то везде механизм будет практически такой же.

Тут нужно еще учитывать, на какое минимальное время может уснуть wait_until. Если таймеры с микросекундной точностью, то спать на системном вызове может быть слишком дорого. Впрочем, у нас в timertt по другому сейчас все равно нельзя.

eao197 ★★★★★
()
Ответ на: комментарий от andreyu

О! Точно. Спасибо.

Но бага не в этом, пока до корректного выхода всей остальной поделки дело не доходит.

Бага в том, что некоторые таски не выполняются никогда.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Бага в том, что некоторые таски не выполняются никогда.

Так не видя полного кода нельзя понять что к чему.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Думаю, что как только потребуется отменять таймеры или переносить на другое время, ситуация станет посложнее. Ну а так-то везде механизм будет практически такой же.

Практика показывает, что проще в колбэке проверить инвариант, что событие должно быть пропущено.

Тут нужно еще учитывать, на какое минимальное время может уснуть wait_until.

У меня ситуация в вакууме: rt ядро и поток таймера на отдельной голове сидит. Т.е. спит оно чётко +/- 500нс. Меня интересует есть ли в этом цикле ситуация, когда оно уснёт без таймаута при добавлении события?

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от eao197
bool process_tasks(DeadlineT& when) {
  auto now = std::chrono::high_resolution_clock::now();
  auto minimum = now;

  auto erase = std::remove_if(tasks_.begin(), tasks_.end(),
                              [&now, &minimum](auto& task) -> bool {
                                if (task.deadline_ <= now) {
                                  auto reschedule_to = task.trigger_();
                                  if (reschedule_to == MicrosecondsT(0)) {
                                    return true;
                                  } else {
                                    task.deadline_ = now + reschedule_to;
                                  }
                                }
                                minimum = std::min(task.deadline_, minimum);
                                return false;
                              });

  tasks_.erase(erase, tasks_.end());

  when = minimum;
  return !tasks_.empty();
pon4ik ★★★★★
() автор топика
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Но это же только код обработки событий, которые уже стоят в очереди. Как вы их туда добавляете?

Ну вот такая ситуация: в очереди событие на (t+10s), где t — это текущее время. Вы добавляете заявку на (t+2s). Если никто в этот момент не передернет event_, то проснетесь вы не через 2s, а через 10s. Т.к. предыдущий дедлайн, на котором вы спите в wait_until, — это 10s.

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 2)
Ответ на: комментарий от eao197

Добавляются задачи так:

void Trigger::add_trigger(Trigger::DeadlineT deadline,
                                Trigger::Trigger trigger) {
  std::unique_lock<std::mutex> lock(lock_);
  tasks_.emplace_back(deadline, trigger);
  event_.notify_all();
}

в очереди событие на (t+10s), где t — это текущее время. Вы добавляете заявку на (t+2s). Если никто в этот момент не передернет event_, то проснетесь вы не через 2s, а через 10s. Т.к. предыдущий дедлайн, на котором вы спите в wait_until, — это 10s.

Но ведь wait_condition == true и внутренний цикл пойдёт на новый виток. После чего - новый дедлайн будет минимальный дедлайн среди всех тасок которые не выполнились.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Но ведь wait_condition == true и внутренний цикл пойдёт на новый виток. После чего - новый дедлайн будет минимальный дедлайн среди всех тасок которые не выполнились.

Может я уже туплю после рабочего дня, но. Допустим у вас в списке тасок лежит две заявки: на 19:00:45 и на 19:00:55. Сейчас 18:59:00. Вы засыпаете во внутреннем while на wait_until. В 18:59:30 приходит заявка, которая должна сработать в 19:00:15. Вы ведь из wait_util не выйдете, т.к. предикат вам вернет false. И вы продолжите спать до 19:00:45. А когда проснетесь, то обработаете сразу две заявки: которая на 19:00:15, и которая на 19:00:45.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Но ведь очередь задач(заявок) будет не пуста, инвариант кондиционной переменной сработает, поток проснётся, пробежится по всем таскам и выберет заново минимальный дэдлайн попутно исполнив протухшие задачи если таковые имеются.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Повторюсь, я уже могу тупить и ошибаться.

eao197 ★★★★★
()
void how2timer(int ms) {
    int CLOCKS_PER_MSEC = CLOCKS_PER_SEC / 1000;   /// новая константа
       /* выведем константы для наглядности */
    cout << "CLOCKS_PER_SEC: " << CLOCKS_PER_SEC << endl;
    cout << "CLOCKS_PER_MSEC: " << CLOCKS_PER_MSEC << endl;
    clock_t end_time = clock() + ms * CLOCKS_PER_MSEC ;  // время завершения
    while (clock() < end_time) {}  // цикл ожидания времени
    cout << "Время вышло!!!\n";  // сообщение о конце работы функции
}
Deathstalker ★★★★★
()

как-то довольно давно я проводила сравнительные тесты. так вот все эти ивенты тормозят просто дико. нет смысла говорить о микросекундах, потому что сама реализация тормозит. помню, что по моим тестам самым быстрым способом «пробуждения» потока были условные переменные. может, сейчас что-то и изменилось, но вряд ли. просто засеки время от ивента до реального начала работы потока - увидишь, что смысла в точных таймерах нет, если реализация не подходит под быстрые задачи.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Аффинити в своих тестах выставляла? Preemtant ядрышко ставила? Планировщик правильный был?

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

куча лапшекода поверх вектора

Идеологически все так, но есть нюансы:

  • обычно в таких ситуациях используют priority_queue, ибо min/max за O(1) и удаление/вставка за O(log(N))
  • шедулинг по wall clock — серьезно?
  • не слушай наркомана с clock ниже по тексту. Только clock_gettime(CLOCK_MONOTONIC, ...).
  • Выбрось убогие крестовые condvar/mutex и перейди на pthreads. Сможешь невозбранно использовать CLOCK_MONOTONIC в том числе и для pthread_cond_timedwait, правда придется осилить man pthread_condattr_setclock.

По коду ниже (event_.notify_all()):

  • зачем notify_all? Семантически шедулер-то явно в единственном экземпляре.
  • есть мнение, что делать condvar.notify() при отпущенном мьютексе может быть чуточку эффективнее, поскольку не будет «простоя» при перезахвате мьютекса в condvar.wait()
kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Этот лапшекод с правильно подобранным аллокатором не будет стоит в рантайме почти ничего а пишется на колене за 3 минуты.

Учитывая специфику задачи (5 задач зашедуленных одновременно это из области фантастики) всякие O(1) идут лесом в угоду простоте кода.

notify_all() на случай если железных ядер завезут и можно будет ещё один поток на этот триггер пустить, поправив тупо строчку в конфиге. Хотя в принципе пока можно захардкодить, не уверен что это даст какой то выигрыш.

шедулинг по wall clock — серьезно?

А в чём проблема(я не в курсе, серьёзно - проясни)? Мне важно не точное время, мне важно точное относительное время, притом таймаут это попугаи эмпирически подбираемые.

Выбрось убогие крестовые condvar/mutex и перейди на

Бла бла бла, ещё мильён преждевременных оптимизаций.

И вообще сиё спит +/- 10мкс на предвыделенной очереди без полезной нагрузки, с различными таймаутами (20;300)мкс в 99 процентов случаев, меня такое разрешение на данном этапе устраивает полностью.

Пока не вижу проблемы в крестовых примитивах синхронизации.

Вижу по результатам, что есть баг в самом алгоритме, либо в коде который его вызывает.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от eao197

У меня код который шедулит постоянно таймеры с 0 таймаутом, дает всего 20к шотов в секунду на любой машине. Чем это вызвано? Моя реализация дает около 2 лямов шотов на рабочем и около полутора на моем.

кстати в венде (на crt который 14) есть бага у конд_варианбла. Если время назад переводить перестает работать.

anonymous
()

что то бажненько получилось

Естественно бажненько, у тебя же по какой-то неведомой причине два ожидания на condvar внутри одного цикла. Правильно будет

std::unique_lock<std::mutex> lock(lock_);
// also wake up when started is set to false
auto wait_condition = [this](){return !started_ || !tasks_.empty();};
while (started_) {
  if (process_tasks(deadline))
    // we have smth scheduled
    event_.wait_until(lock, deadline, wait_condition);
  else
    // just wait until we're awaken by some manipulation with task queue or started_ flag change
    event_.wait(lock, wait_condition);
}

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Во, круто, спасибо.

А теперь поясни кейс когда первый вариант не сработает, а второй сработает, если не сложно.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Т.е. этот вариант явно не такой наркоманский как у меня, но интересует конкретный кейс.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от kawaii_neko

Тьфу ты. Я понял. Ещё раз спасибо!

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

А в чём проблема(я не в курсе, серьёзно - проясни)?

В том, что календарное время может переводиться, например при синхронизации часов со внешним источником. Например, в старых версиях xscreensaver после синхронизации можно было получить немедленное отключение монитора.

Конечно, весь мир использует gettimeofday и не парится, но такие нюансы, как правило, отличают любителя от профессионала.

Бла бла бла, ещё мильён преждевременных оптимизаций.

Это не оптимизации. Я себе слабо представляю, как wait_until будет работать со steady_clock, потому что в платформенном API тип «часов», по которым работает timedwait задается как атрибут condvar-а.

kawaii_neko ★★★★
()

Если шедулинг только в начале процесса, то норм. Если предполагается добавление таймеров по ходу - решение нерабочее.

anonymous
()
Ответ на: комментарий от kawaii_neko

Про календарное время - понял, мне просто банально не критично, это не api общего назначения а наколенный лясик именно под короткие относительные задержки на заданном окружении, если ntpd полезет править время, то врят ли триггер сработает сильно позднее чем при monotonic, будет обидно, но переписывать весь код на api на уровень ниже из-за таких нюансов пока нет желания.

Про steady_clock(используется high_resolution но вроде в gcc реализации это одно и тоже) - эмпирически распределение пока устраивает, но спасибо буду иметь ввиду для общих случаев.

потому что в платформенном API тип «часов», по которым работает timedwait задается как атрибут condvar-а.

Что-то мне подсказывает, что там CLOCK_REALTIME ибо

весь мир использует gettimeofday и не парится

видимо надо будет сгонять в реализацию, прежде чем выпиливать прототип на std14 с заменой на нормальное api, но выпиливать конечно же придётся, если вдруг будет не хватать скоростей конечно же.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

всё нормально было. ивенты сами по себе тормозные. нет смысла говорить про точные таймеры там, где нет соответствующей скорости реакции на них.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

Интересно, как же они устроенны, эти загадочные тормозные ивенты, которые в подмётки не годятся условным переменным :)

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

почитай код, если хочеть этот вопрос выяснить.

Iron_Bug ★★★★★
()
Ответ на: комментарий от vromanov

тем, что иногда нужны просто некоторые отложенные таски и не нужны либы. Ну а если они уже есть в проекте, то смысла нет велосипедить, это да.

anonymous
()
Ответ на: комментарий от anonymous

Там есть все решено. Например, в libev есть таймеры, а есть преиодики. Они как раз по разному себя ведут при переводе чесов.

vromanov ★★★
()
Ответ на: комментарий от vromanov

Например - я знаю, что у меня до 5 активных задач в один момент времени, поэтому, всякие timer wheel будут скорее менее эффективны, чем тупо предвыделенный вектор с задачами.

Или я хочу поменять имя потоку таймера, и так же выставить ему аффинити, приоритет и задать планировщик. Или мне хочется интрузивный подход к отмене или перепланированию задач. Таких или, когда к таймеру есть жесткие нефункциональные требования накапливается мильён.

В реализации общего назначения для каких то из них могут быть обходные пути(аффинити и прочее можно из таймер таски настроить например), а могут и не быть. И одна фигня скорее всего придется лезть в сорцы этих реализаций что бы понять, что можно а что нет.

Второй момент - это не таскать лишние зависимости, пока они не нужны позарез.

pon4ik ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Есть тут один могильничек. Раз уж все выкладывают... https://github.com/newenclave/etool/tree/master/include/etool/queues/delayed

Можно директорию просто скопировать, там нет зависимостей от проекта. Автор, вроде, местный регистрант.

Юзать просто.

#include "etool/queues/delayed/simple.h"
...
using timerqueue = etool::queues::delayed::simple;
timerqueue tq;
auto task1 = tq.post_delayed_task(1000ms, [](){std::cout << "hello!\n";});
auto task2 = tq.post_delayed_task(1000ms, [](bool cancelled){std::cout << cancelled << "\n";});
...
task2.cancel();
...
tq.run();
...
/// hello!
/// 1

можно постить обычные задания, задания можно отменять (task.cancel()). очередь можно запускать (вызов run) на скольки хочешь потоках.

там есть еще traits. на сколь помню делалось, для напейсания обетки над CONDITION_VARIABLE из winapi бо реализация conditional_variable в msvcp140.dll говно полное и будет исправлено только в следующем мажоре.

anonymous
()
Ответ на: комментарий от andreyu

Почему тогда не CLOCK_MONOTONIC_RAW?

Есть две причины:

  • CLOCK_MONOTONIC (как и CLOCK_REALTIME, gettimeofday и просто time()) выполняются через специальный механизм (как я понимаю, это отмапленная страница памяти ядра) и не являются syscall-ом, соответственно работают очень быстро. CLOCK_MONOTONIC_RAW — честный syscall
  • pthread_condattr_setclock не принимает CLOCK_MONOTONIC_RAW, соответственно, по monotonic-raw часам невозможно выполнить pthread_cond_timedwait
kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

А в man говорится, что


CLOCK_MONOTONIC_RAW
clock that increments monotonically, tracking the time since an arbitrary point like
CLOCK_MONOTONIC. However, this clock is unaffected by frequency or time adjustments.
It should not be compared to other system time sources.


Про сискол ни слова.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

А в man говорится, что...

...есть большая разница между любителем и профессионалом. Советую для начала увидеть своими глазами «невидимый в strace gettimeofday», а потом читнуть man vdso. Никогда что ли не было любопытно, что за linux-vdso.so.1 отсвечивает в ldd?

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

CLOCK_MONOTONIC (как и CLOCK_REALTIME, gettimeofday и просто time()) выполняются через специальный механизм (как я понимаю, это отмапленная страница памяти ядра) и не являются syscall-ом, соответственно работают очень быстро. CLOCK_MONOTONIC_RAW — честный syscall

На самом деле ѣто все сильно относительно. По моемꙋ опытꙋ: CLOCK_REALTIME & CLOCK_MONOTONIC_RAW всегда обращаютсѧ к ѧдрꙋ одинаково. Может ты пꙋтаешь с CLOCK_MONOTONIC_COARSE и CLOCK_REALTIME_COARSE? Ѣти - да, декларируются как безсискольные.

cvv ★★★★★
()
Ответ на: комментарий от kawaii_neko

Чет пишут что глубоко пофиг какой именно тип часов использовать как я понял:

man 7 vdso

One frequently used system call is gettimeofday(2). This system call
is called both directly by user-space applications as well as indirectly by the C library. Think timestamps or timing loops or polling—all of these frequently need to know what time it is right now. This information is also not secret—any application in any privilege mode (root or any unprivileged user) will get the same answer. Thus the kernel arranges for the information required to answer this question to be placed in memory the process can access. Now a call to gettimeofday(2) changes from a system call to a normal function call and a few memory accesses.

Для остального надо читать сорцы конкретного ядра видимо.

pon4ik ★★★★★
() автор топика
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Чет пишут что глубоко пофиг какой именно тип часов использовать как я понял:

Вы неправильно поняли.

cvv ★★★★★
()
Ответ на: комментарий от kawaii_neko

А в man говорится, что...

...есть большая разница между любителем и профессионалом.

Нет, в man об этом речи не идет. Это вы что-то придумали.

Советую для начала увидеть своими глазами «невидимый в strace gettimeofday»,

Что значит «невидимый в strace gettimeofday»?

а потом читнуть man vdso.

$ man vdso
No manual entry for vdso

Никогда что ли не было любопытно, что за linux-vdso.so.1 отсвечивает в ldd?

Нет. Кроме того, его нет на macos. Почему вы вообще решили привязаться жестко к linux?

andreyu ★★★★★
()
Последнее исправление: andreyu (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.