#c #c 17
#c #c 17
Вопрос:
У меня есть цикл на C , который я хотел бы запустить на несколько секунд. Хотя объем работы на каждой итерации разный, от нескольких микросекунд до секунд, можно останавливаться между итерациями. Это высокопроизводительный код, поэтому я хотел бы избежать вычисления разницы во времени на каждой итерации:
while (!status.has_value())
{
// do something
// this adds extra delays that I would like to avoid
if (duration_cast<seconds>(system_clock::now() - started).count() >= limit)
status = CompletedBy::duration;
}
Я думаю, может быть, есть способ запланировать сигнал, а затем остановить цикл, когда это произойдет, вместо проверки разницы во времени на каждой итерации.
Кстати, цикл может завершиться до сигнала.
Комментарии:
1. Какую ОС вы используете? Такие функции могут зависеть от системы.
2. @MichaelChourdakis вы уверены, что понимаете, как работает сигнализация, скажем, в системах Unix?
3. Это полностью жизнеспособно. Я бы подключил другой поток и перевернул атомарную переменную volatole после некоторого ожидания. Рабочий поток будет проверять переменную перед каждой итерацией. Или действительно запланировать сигнал и перевернуть переменную sig_atomic_t.
4. Вы профилировали свой код и обнаружили, что
duration_cast<seconds>(system_clock::now() - started).count() >= limit)
это узкое место? Потому что эта строка не должна занимать больше микросекунды.5. @MichaelChourdakis Но он упомянул сигналы.
Ответ №1:
Я сделал нечто подобное, но на Java. Общая идея состоит в том, чтобы использовать отдельный поток для управления контрольным значением, придавая вашему циклу вид…
okayToLoop = true;
// code to launch thread that will wait N milliseconds, and then negate okayToLoop
while((!status.hasValue()) AND (okayToLoop)) {
// loop code
}
«Предостерегающее замечание» заключается в том, что многие функции sleep () для потоков используют семантику «sleep по крайней мере», поэтому, если действительно важно, чтобы только спали N миллисекунд, вам нужно будет учесть это в вашей реализации потока. Но это позволяет избежать постоянной проверки продолжительности каждой итерации цикла.
Обратите внимание, что это также позволит завершить текущую итерацию цикла до проверки контрольного значения. Я также реализовал этот подход, при котором «поток управления» фактически прерывает поток, в котором выполняется цикл, прерывая итерацию. Когда я сделал это, я фактически поместил цикл в рабочий поток.
Комментарии:
1. Это потребовало бы, чтобы каждый доступ к okayToLoop вызывал точку синхронизации, что привело бы к отключению многих оптимизаций компилятора, что, вероятно, повредит производительности НАМНОГО больше, чем то, что уже делает OP.
2. Хорошо, я соглашусь с тем, что подход okayToLoop имеет свои проблемы и, как и в большинстве случаев, является компромиссом. Как насчет подхода, основанного на прерываниях? Само прерывание может быть дорогостоящим (я никогда не был в сценарии, где выполнение было критичным для миллисекунды), но этот подход устраняет синхронизированный доступ в okayToLoop (кстати, синхронизация должна быть необходима только в том случае, если есть штраф за дополнительную итерацию цикла, нет?).
3. Подход interupt хорош в чистом C. Но в C для этого потребовались бы таблицы размотки с нулевой стоимостью на уровне инструкций, которые, я думаю, не предоставляет ни один современный компилятор. Если вы готовы сыграть в игру с неопределенным поведением и / или поклясться, что внутри цикла будут задействованы только тривиальные типы, возможно, вам это сойдет с рук. Даже в этом случае компилятор будет иметь полное право все сломать, поскольку вы находитесь на территории UB.
Ответ №2:
Любая форма межпотоковой связи будет намного медленнее, чем простой запрос высокопроизводительных часов.
Теперь steady_clock::now()
цикл может быть слишком медленным.
Используя API, специфичные для ОС, привяжите свой поток к смехотворному приоритету и привязке к определенному процессору. Или используйте rdtsc, приняв во внимание все, что может пойти не так. Рассчитайте, какое значение вы ожидаете получить, если (а) что-то пошло не так или (б) вы превысили пороговое значение по времени.
Когда это произойдет, проверьте steady_clock::now()
, достаточно ли вы близки к завершению, и если да, то завершите. Если нет, вычислите новый целевой тактовый сигнал высокой производительности и повторите цикл.
Комментарии:
1.
system_clock
в любом случае, это неправильная идея, потому что ее можно изменить ; вам нужно как минимумsteady_clock
.2. Любая форма межпотоковой связи будет намного медленнее — существуют формы межпотоковой связи, которые выглядят как
x.load(std::memory_order_relaxed)
. Это всего лишь простаяmov
инструкция для x86_64.3. @MaximEgorushkin Упрощенный порядок памяти не требует, чтобы вы действительно получали значение до того, как солнце превратится в красного гиганта, который я проверял в последний раз. В основном говорится, что если значение установлено равным 7, а затем 10, вы не будете читать 10, а затем 7. Я полагаю, если вы знаете, что используете AMD-64 и знаете, что это реализовано как mov, единственной возможной проблемой является согласованность кэша, и вы знаете, что операция записи очистит кэш другого процессора.
4. @Yakk-AdamNevraumont Верно, но это все равно не делает ваше утверждение в первом предложении правильным.
5. @MaximEgorushkin Я не считаю код, который на самом деле ничего не передает, коммуникационным. Я имею в виду, что он мог в конечном итоге что-то сообщить, но для того, чтобы заставить его действительно что-то сообщить, вам придется сделать что-то еще… В любом случае, напишите ответ! Может быть более полезным для OP.