Создание дружественного времени цикла занятости для гиперпотока

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

Основной поток будет выполнять важную работу IPC, связанную с ЦП. Вспомогательный поток не должен ничего, кроме как периодически обновлять общее значение временной метки, которое будет периодически читать основной поток. Частота обновления настраивается, но может достигать 100 МГц или более. Такие быстрые обновления более или менее исключают подход, основанный на спячке, поскольку блокировка сна слишком медленная, чтобы спать/просыпаться в течение 10 наносекунд (100 МГц).

Итак, я хочу оживленное ожидание. Тем не менее, ожидание ожидания должно быть максимально дружественным к основному потоку: используйте как можно меньше ресурсов выполнения и, таким образом, добавьте как можно меньше накладных расходов в основной поток.

Я предполагаю, что эта идея будет командой с длинной задержкой, которая не использует много ресурсов, например pause, а также имеет фиксированную и известную задержку. Это позволило бы нам откалибровать период "сна", поэтому никакое считывание часов даже не требуется (если вы хотите обновить с периодом P, мы просто выдаем P/L этих инструкций для откалиброванного занятого сна. Well pause удовлетворяют этому последнему критерию, так как его латентность сильно варьируется 1.

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

Любые лучшие варианты?


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

Ответ 1

Некоторые случайные размышления по этому вопросу.

Итак, вы хотите иметь отметку времени на частоте 100 МГц, а это значит, что на процессоре 4 ГГц у вас есть 40 циклов между каждым вызовом.

Поток таймера интенсивно считывает часы реального времени (RTDSC???), но не может использовать метод сохранения с cpuid, поскольку это занимает 100 циклов. Старые часы реального времени имеют латентность около 25 (и пропускную способность 1/25), может быть немного более новая, немного более точная, с чуть большим таймером задержки (32 цикла).

  start:
  read time (25 cycles)
  tmp = time - last (1 cycle)
  if tmp < sample length goto start
  last += cycles between samples
  sample = time
  goto start

В идеальном мире предсказатель ветвей будет угадывать каждый раз, в действительности он будет ошибочно предсказать случайное добавление 5-14 циклов к циклам 26 циклов из-за отклонения в циклах чтения.

Когда образец записывается, другой поток будет отменять свои инструкции от первых спекулятивных нагрузок из этой строки кэша (не забудьте выровнять до 64 байт для позиции образца, чтобы другие данные не пострадали). И загрузка временной метки образца начинается с задержки ~ 5-14 циклов в зависимости от того, откуда берутся инструкции, буфера цикла, кэша микроопераций или I-кеша.

Таким образом, минимальная производительность 5 > 14 циклов /40 циклов будет потеряна, в дополнение к половине процессора, используемой другим потоком.

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

~ 1/4, латентность, скорее всего, будет покрыта другими инструкциями. Но тогда вы не можете изменять частоту. Длительная латентность в 25 циклов может быть проблемой, если перед ней не предшествуют другие команды с большим временем ожидания.

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