Linux-4.4.0 中 PTI=on 时性能下降

performance degrading with PTI=on in Linux-4.4.0

我正在 运行 java 测试 Ubuntu 16.04,我发现 PTI 打开和关闭时的性能差异。
我的主机系统使用 CPU Ivybridge(2 核,4 HT)1.6GHz,16GB 内存。

我尝试用perf来分析差异来自哪里,如下

在 grub.cfg、

中 pti=off
# perf  stat -e bus-cycles,cache-misses,cache-references,L1-dcache-load-misses,dTLB-load-misses,L1-dcache-prefetch-misses,LLC-prefetches ./test.sh 

 Performance counter stats for './test.sh':

       774,986,827      bus-cycles                                                    (59.13%)
        24,044,906      cache-misses              #   12.803 % of all cache refs      (58.17%)
       187,799,652      cache-references                                              (57.51%)
       207,345,039      L1-dcache-load-misses                                         (57.65%)
        13,081,612      dTLB-load-misses                                              (58.85%)
        22,678,453      L1-dcache-prefetch-misses                                     (59.62%)
        24,089,506      LLC-prefetches                                                (59.99%)

       6.210151360 seconds time elapsed

并且,使用 pti=on(Linux 内核中的默认设置),我得到了,

# perf stat -e bus-cycles,cache-misses,cache-references,L1-dcache-load-misses,dTLB-load-misses,L1-dcache-prefetch-misses,LLC-prefetches ./test.sh 

“./test.sh”的性能计数器统计信息:

 1,205,903,578      bus-cycles                                                    (57.92%)
    23,877,107      cache-misses              #   13.167 % of all cache refs      (57.31%)
   181,340,147      cache-references                                              (57.46%)
   206,177,901      L1-dcache-load-misses                                         (58.42%)
    63,285,591      dTLB-load-misses                                              (59.06%)
    24,012,988      L1-dcache-prefetch-misses                                     (58.65%)
    24,928,410      LLC-prefetches                                                (58.23%)

  10.344839116 seconds time elapsed  

test.sh 是要分析的程序,从上面的 perf 输出来看,test.sh 在 pti=on 时比在 pti=off 时花费更多时间,但事件输出不清楚差异在哪里来自.
在这种情况下还有其他性能事件可以提供帮助吗?

更新了更多性能事件。
PTI=关闭

# perf stat --repeat 5 -e cache-references,cache-misses,cpu-cycles,ref-cycles,faults,L1-dcache-loads,L1-dcache-load-misses,L1-icache-load-misses,branches,branch-misses,node-loads,node-load-misses,instructions,cs java mytest

 Performance counter stats for 'java mytest' (5 runs):

         8,711,306      cache-references                                              ( +-  4.13% )  (48.49%)
         1,290,234      cache-misses              #   14.811 % of all cache refs      ( +-  4.04% )  (49.44%)
       709,587,381      cpu-cycles                                                    ( +-  1.44% )  (48.91%)
       671,299,480      ref-cycles                                                    ( +-  1.95% )  (58.09%)
             5,918      faults                                                        ( +-  0.12% )
       185,928,475      L1-dcache-loads                                               ( +-  4.29% )  (35.90%)
         9,249,983      L1-dcache-load-misses     #    4.98% of all L1-dcache hits    ( +-  5.91% )  (27.84%)
         4,718,632      L1-icache-load-misses                                         ( +-  5.47% )  (20.83%)
       106,021,866      branches                                                      ( +-  1.98% )  (31.56%)
         4,487,091      branch-misses             #    4.23% of all branches          ( +-  5.35% )  (40.34%)
           450,170      node-loads                                                    ( +-  9.18% )  (38.32%)
                 0      node-load-misses                                              (40.62%)
       509,344,631      instructions              #    0.72  insns per cycle          ( +-  5.59% )  (49.64%)
               458      cs                                                            ( +-  2.05% )

       0.216794242 seconds time elapsed                                          ( +-  3.44% )   

PTI=开

# perf stat --repeat 5 -e cache-references,cache-misses,cpu-cycles,ref-cycles,faults,L1-dcache-loads,L1-dcache-load-misses,L1-icache-load-misses,branches,branch-misses,node-loads,node-load-misses,instructions,cs java mytest

 Performance counter stats for 'java mytest' (5 runs):

        10,109,469      cache-references                                              ( +-  4.10% )  (44.67%)
         1,360,012      cache-misses              #   13.453 % of all cache refs      ( +-  2.16% )  (45.28%)
     1,199,960,141      cpu-cycles                                                    ( +-  2.44% )  (46.13%)
     1,086,243,141      ref-cycles                                                    ( +-  1.28% )  (54.64%)
             5,923      faults                                                        ( +-  0.24% )
       163,902,394      L1-dcache-loads                                               ( +-  3.46% )  (41.91%)
         8,588,505      L1-dcache-load-misses     #    5.24% of all L1-dcache hits    ( +-  5.59% )  (27.82%)
         5,576,811      L1-icache-load-misses                                         ( +-  3.87% )  (18.41%)
       117,508,300      branches                                                      ( +-  3.98% )  (27.34%)
         4,878,640      branch-misses             #    4.15% of all branches          ( +-  2.28% )  (35.55%)
           585,464      node-loads                                                    ( +-  9.05% )  (34.55%)
                 0      node-load-misses                                              (36.68%)
       614,773,322      instructions              #    0.51  insns per cycle          ( +-  4.11% )  (46.10%)
               476      cs                                                            ( +-  2.75% )

       0.375871969 seconds time elapsed                                          ( +-  0.81% )

不知道 "bus cycles" 实际测量的是什么事件。核心时钟周期通常更相关。

但无论如何,PTI=on 使每个系统调用(以及其他进入内核的入口)都变得更加昂贵,因为它必须修改 x86 CR3 控制寄存器(设置新页面 table 顶级指针).这就是它如何将用户-space 与内核页面-table 的访问隔离开来。

请注意 dTLB-load-misses 的大幅增加。借助进程上下文 ID (PCID) 支持,PTI 可能能够避免在每次进入内核时完全刷新 TLB。但我不知道细节。 (没有 PCID,替换页面 tables 会使整个 TLB 失效。)

您可以使用 strace -c 为系统调用计时。

使用perf record(有足够的权限),你可以记录包含内核代码的样本,这样你就可以看到内核中哪些指令实际上花费了很长时间。 (转移到 CR3 也需要时间,Spectre 缓解也需要时间,它与 Meltdown 缓解 (PTI) 是分开的。但我认为 大部分 Meltdown 缓解成本是 TLB 遗漏内核中的时间和返回用户 space 后的时间,而不是来自实际的交换页面 tables。)