Могут ли разные процессы запускать RDTSC одновременно? - PullRequest
3 голосов
/ 04 июня 2019

Могут ли разные процессы запускать RDTSC одновременно?Или это ресурс, на котором одновременно может работать только одно ядро?TSC есть в каждом ядре (по крайней мере, вы можете настроить его отдельно для каждого ядра), поэтому это должно быть возможно.Но как насчет Hyper Treading?

Как я могу это проверить?

1 Ответ

4 голосов
/ 04 июня 2019

Каждое физическое ядро ​​имеет свой собственный TSC; микрокод не должен выходить из ядра, поэтому нет общего ресурса, за который они конкурируют. Отключение ядра вообще сделало бы это намного медленнее и сделало бы реализацию более сложной. Наличие физического счетчика внутри каждого ядра является более простой реализацией, просто считая такты сигнала тактовой частоты, который распространяется на все ядра.

С HyperThreading логические ядра, разделяющие физическое, всегда конкурируют за ресурсы выполнения. Из таблиц инструкций Agner Fog мы знаем, что RDTSC на Skylake составляет 20 моп для внешнего интерфейса и имеет пропускную способность 1 на 25 циклов. При скорости менее 1 мегапикселя за такт при выполнении только инструкций RDTSC конкуренция за интерфейс, вероятно, не является проблемой.

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

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

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

Мне стало любопытно, и я сделал это на Linux с perf на Skylake i7-6700k, с taskset -c 3 и taskset -c 7 (то, как Linux перечисляет ядра на этом процессоре, эти числа являются логическими ядрами Четвертое физическое ядро. Вы можете проверить / proc / cpuinfo, чтобы выяснить это в вашей системе.)

Чтобы избежать чередования выходных строк, если они обе заканчиваются почти одновременно, я использовал замену процесса bash с помощью cat <(cmd1) <(cmd2), чтобы запустить их обе одновременно и получить выходные данные в фиксированном порядке. Команды были taskset -c 3 perf stat -etask-clock:u,context-switches,cpu-migrations,page-faults,cycles:u,instructions:u,branches:u,branch-misses:u,uops_issued.any:u,uops_executed.thread:u,cpu_clk_thread_unhalted.one_thread_active:u -r2 ./testloop для подсчета тактовых циклов ядра (не эталонных циклов, поэтому мне не нужно быть параноиком по поводу тактовых / холостых тактовых частот).

testloop - это статический исполняемый файл с рукописным циклом asm, содержащий times 20 rdtsc (оператор повтора NASM) и dec ebp / jnz, с вершиной цикла, выровненной на 64, в случае, если это когда-либо имеет значение. Перед циклом mov ebp, 10000000 инициализирует счетчик. (См. Может ли MOV x86 действительно быть "свободным"? Почему я вообще не могу воспроизвести это? для получения подробных сведений о том, как я делаю микробенчмарки таким образом. Или Понимание влияния lfence на цикл с две длинные цепочки зависимостей, для увеличения длины еще один пример простой программы NASM с циклом, использующим times для повторения инструкций.)

 Performance counter stats for './testloop' (2 runs):

          1,278.19 msec task-clock:u              #    1.000 CPUs utilized            ( +-  0.19% )
                 4      context-switches          #    0.004 K/sec                    ( +- 11.11% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.002 K/sec                  
     5,243,270,118      cycles:u                  #    4.102 GHz                      ( +-  0.01% )  (71.37%)
       219,949,542      instructions:u            #    0.04  insn per cycle           ( +-  0.01% )  (85.68%)
        10,000,692      branches:u                #    7.824 M/sec                    ( +-  0.03% )  (85.68%)
                32      branch-misses:u           #    0.00% of all branches          ( +- 93.65% )  (85.68%)
     4,010,798,914      uops_issued.any:u         # 3137.885 M/sec                    ( +-  0.01% )  (85.68%)
     4,010,969,168      uops_executed.thread:u    # 3138.018 M/sec                    ( +-  0.00% )  (85.78%)
                 0      cpu_clk_thread_unhalted.one_thread_active:u #    0.000 K/sec                    (57.17%)

           1.27854 +- 0.00256 seconds time elapsed  ( +-  0.20% )


 Performance counter stats for './testloop' (2 runs):

          1,278.26 msec task-clock:u              #    1.000 CPUs utilized            ( +-  0.18% )
                 6      context-switches          #    0.004 K/sec                    ( +-  9.09% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.002 K/sec                    ( +- 20.00% )
     5,245,894,686      cycles:u                  #    4.104 GHz                      ( +-  0.02% )  (71.27%)
       220,011,812      instructions:u            #    0.04  insn per cycle           ( +-  0.02% )  (85.68%)
         9,998,783      branches:u                #    7.822 M/sec                    ( +-  0.01% )  (85.68%)
                23      branch-misses:u           #    0.00% of all branches          ( +- 91.30% )  (85.69%)
     4,010,860,476      uops_issued.any:u         # 3137.746 M/sec                    ( +-  0.01% )  (85.68%)
     4,012,085,938      uops_executed.thread:u    # 3138.704 M/sec                    ( +-  0.02% )  (85.79%)
             4,174      cpu_clk_thread_unhalted.one_thread_active:u #    0.003 M/sec                    ( +-  9.91% )  (57.15%)

           1.27876 +- 0.00265 seconds time elapsed  ( +-  0.21% )

против. работает один:

 Performance counter stats for './testloop' (2 runs):

          1,223.55 msec task-clock:u              #    1.000 CPUs utilized            ( +-  0.52% )
                 4      context-switches          #    0.004 K/sec                    ( +- 11.11% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.002 K/sec                  
     5,003,825,966      cycles:u                  #    4.090 GHz                      ( +-  0.00% )  (71.31%)
       219,905,884      instructions:u            #    0.04  insn per cycle           ( +-  0.04% )  (85.66%)
        10,001,852      branches:u                #    8.174 M/sec                    ( +-  0.04% )  (85.66%)
                17      branch-misses:u           #    0.00% of all branches          ( +- 52.94% )  (85.78%)
     4,012,165,560      uops_issued.any:u         # 3279.113 M/sec                    ( +-  0.03% )  (85.78%)
     4,010,429,819      uops_executed.thread:u    # 3277.694 M/sec                    ( +-  0.01% )  (85.78%)
        28,452,608      cpu_clk_thread_unhalted.one_thread_active:u #   23.254 M/sec                    ( +-  0.20% )  (57.01%)

           1.22396 +- 0.00660 seconds time elapsed  ( +-  0.54% )

(Счетчик для cpu_clk_thread_unhalted.one_thread_active:u считает только с некоторой медленной скоростью; система была довольно простаивает во время этого теста, поэтому она должна была иметь ядро ​​в себе все время. То есть, что ~ 23,2 М / сек действительно представляет одно режим потока.)

против. отсчеты 0 и почти 0 для совместной работы показывают, что мне удалось выполнить эти задачи одновременно на одном и том же ядре с гиперпоточностью, в основном, в течение всего времени (~ 1,2 секунды повторяются дважды или 2,4 секунды).

Таким образом, 5,0038G циклов / 10M итеров / 20 rdtsc / iter = 25,019 циклов на однопоточную RDTSC, в значительной степени то, что измерял Agner Fog.

Усреднение по обоим процессам для теста HT составляет около 5,244G циклов / 10M iter / 20 rdtsc / iter = 26,22 циклов в среднем.

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

Если другое ядро ​​занято выполнением кода с высокой пропускной способностью (который может выдерживать 4 мопа за такт, если у него есть ядро), это, вероятно, повредит потоку RDTSC * на 1055 * больше , чем другой поток, который также просто выполняет RDTSC,Возможно, мы могли бы даже выяснить, существует ли один конкретный порт, который нужен RDTSC больше, чем другие, например, порт 1 легко насыщается, потому что это единственный порт, который может выполнять команды целочисленного умножения.

...