zoukankan      html  css  js  c++  java
  • static-keys.txt 翻译

    静态键
    -----------

    作者:Jason Baron <jbaron@redhat.com>

    0) 摘要

    静态键允许通过 GCC 功能和代码修补技术在对性能敏感的快速路径内核代码中包含很少使用的功能。 一个简单的例子:

    struct static_key key = STATIC_KEY_INIT_FALSE;
    ...
    if (static_key_false(&key))
        do unlikely code
    else
        do likely code
    ...
    static_key_slow_inc();
    ...
    static_key_slow_inc();
    ...

    static_key_false() 分支将生成到代码中,对可能的代码路径的影响尽可能小。


    1) 动机

    目前,跟踪点是使用条件分支实现的。 条件检查需要检查每个跟踪点的全局变量。 虽然这个检查的开销很小,但是当内存缓存受到压力时它会增加(这些全局变量的内存缓存行可能与其他内存访问共享)。
    随着我们增加内核中跟踪点的数量,这种开销可能会成为一个更大的问题。 此外,跟踪点通常处于休眠状态(禁用)并且不提供直接的内核功能。 因此,非常希望尽可能减少它们的影响。 尽管跟踪点是这
    项工作的最初动机,但其他内核代码路径应该能够利用静态键设施。


    2) 解决方案

    gcc (v4.5) 添加了一个新的“asm goto”语句,允许分支到标签:http://gcc.gnu.org/ml/gcc-patches/2009-07/msg01556.html

    使用“asm goto”,我们可以创建默认情况下采用或不采用的分支,而无需检查内存。 然后,在运行时,我们可以修补分支站点以改变分支方向。

    例如,如果我们有一个默认禁用的简单分支:

    if (static_key_false(&key))
        printk("I am the true branch
    ");

    因此,默认情况下不会发出“printk”。 生成的代码将在直线代码路径中包含单个原子“无操作(no-op)”指令(x86 上为 5 个字节)。 当分支被“翻转(flipped)”时,我们将使用“跳转(jump)”指令将直线代码路径
    中的“无操作(no-op)”修补到线外真实分支。 因此,改变分支方向是昂贵的,但分支选择基本上是“免费的”。 这是这种优化的基本权衡。

    这种低级修补机制称为“跳转标签修补”,它为静态键设施提供了基础。


    3) 静态键标签API、用法及示例:

    为了利用这种优化,您必须首先定义一个键:

    struct static_key key;

    初始化为:

    struct static_key key = STATIC_KEY_INIT_TRUE;

    或者:

    struct static_key key = STATIC_KEY_INIT_FALSE;

    如果键未初始化,则默认为 false。 'struct static_key'必须是全局变量。 也就是说,它不能在堆栈上分配或在运行时动态分配。

    然后在代码中使用键为:

    if (static_key_false(&key))
        do unlikely code
    else
        do likely code

    或者:

    if (static_key_true(&key))
        do likely code
    else
        do unlikely code

    通过“STATIC_KEY_INIT_FALSE”初始化的键必须在“static_key_false()”构造中使用。 同样,通过“STATIC_KEY_INIT_TRUE”初始化的键必须用在“static_key_true()”构造中使用。 一个键可以在许多分支中使用,但所有
    分支必须与键的初始化方式相匹配。

    然后可以通过以下方式切换分支:

    static_key_slow_inc(&key);
    ...
    static_key_slow_dec(&key);

    因此,“static_key_slow_inc()”意味着“使分支为真”,并且'static_key_slow_dec()' 表示使用适当的引用计数'使分支为假'。 例如,如果键被初始化为真,static_key_slow_dec() 会将分支切换为假。 随后的 
    static_key_slow_inc() 会将分支更改回 true。 同样,如果键被初始化为 false,则“static_key_slow_inc()”会将分支更改为 true。 然后'static_key_slow_dec()',将再次使分支为假。

    内核中的示例用法是跟踪点的实现:

    static inline void trace_##name(proto)                   
    {                                                        
        if (static_key_false(&__tracepoint_##name.key))        
                __DO_TRACE(&__tracepoint_##name,               
                        TP_PROTO(data_proto),                  
                        TP_ARGS(data_args),                 
                        TP_CONDITION(cond));                   
    }

    跟踪点默认是禁用的,可以放置在内核的性能关键部分。 因此,通过使用静态键,跟踪点在不使用时可以产生绝对最小的影响。


    4) 架构级代码打补丁接口,‘跳转标签(jump labels)’


    为了利用这种优化,架构必须实现一些功能和宏。 如果没有架构支持,我们就简单地退回到传统的加载、测试和跳转序列。

    * 选择 HAVE_ARCH_JUMP_LABEL,参见:arch/x86/Kconfig

    * #define JUMP_LABEL_NOP_SIZE,见:arch/x86/include/asm/jump_label.h

    * __always_inline bool arch_static_branch(struct static_key *key),见:arch/x86/include/asm/jump_label.h

    * void arch_jump_label_transform(struct jump_entry *entry, enum jump_label_type type),参见:arch/x86/kernel/jump_label.c

    * __init_or_module void arch_jump_label_transform_static(struct jump_entry *entry, enum jump_label_type type), 参见:arch/x86/kernel/jump_label.c

    * struct jump_entry,参见:arch/x86/include/asm/jump_label.h


    5)静态键/跳转标签分析,结果(x86_64):

    例如,让我们将以下分支添加到“getppid()”,这样系统调用现在看起来像:

    SYSCALL_DEFINE0(getppid)
    {
            int pid;
    
    +       if (static_key_false(&key))
    +               printk("I am the true branch
    ");
    
            rcu_read_lock();
            pid = task_tgid_vnr(rcu_dereference(current->real_parent));
            rcu_read_unlock();
    
            return pid;
    }

    GCC生成的带有跳转标签的指令是:

    ffffffff81044290 <sys_getppid>:
    ffffffff81044290:       55                      push   %rbp
    ffffffff81044291:       48 89 e5                mov    %rsp,%rbp
    ffffffff81044294:       e9 00 00 00 00          jmpq   ffffffff81044299 <sys_getppid+0x9>
    ffffffff81044299:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
    ffffffff810442a0:       00 00
    ffffffff810442a2:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
    ffffffff810442a9:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
    ffffffff810442b0:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
    ffffffff810442b7:       e8 f4 d9 00 00          callq  ffffffff81051cb0 <pid_vnr>
    ffffffff810442bc:       5d                      pop    %rbp
    ffffffff810442bd:       48 98                   cltq
    ffffffff810442bf:       c3                      retq
    ffffffff810442c0:       48 c7 c7 e3 54 98 81    mov    $0xffffffff819854e3,%rdi
    ffffffff810442c7:       31 c0                   xor    %eax,%eax
    ffffffff810442c9:       e8 71 13 6d 00          callq  ffffffff8171563f <printk>
    ffffffff810442ce:       eb c9                   jmp    ffffffff81044299 <sys_getppid+0x9>

    如果没有跳转标签优化,它看起来像:

    ffffffff810441f0 <sys_getppid>:
    ffffffff810441f0:       8b 05 8a 52 d8 00       mov    0xd8528a(%rip),%eax        #
    ffffffff81dc9480 <key>
    ffffffff810441f6:       55                      push   %rbp
    ffffffff810441f7:       48 89 e5                mov    %rsp,%rbp
    ffffffff810441fa:       85 c0                   test   %eax,%eax
    ffffffff810441fc:       75 27                   jne    ffffffff81044225 <sys_getppid+0x35>
    ffffffff810441fe:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
    ffffffff81044205:       00 00
    ffffffff81044207:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
    ffffffff8104420e:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
    ffffffff81044215:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
    ffffffff8104421c:       e8 2f da 00 00          callq  ffffffff81051c50 <pid_vnr>
    ffffffff81044221:       5d                      pop    %rbp
    ffffffff81044222:       48 98                   cltq
    ffffffff81044224:       c3                      retq
    ffffffff81044225:       48 c7 c7 13 53 98 81    mov    $0xffffffff81985313,%rdi
    ffffffff8104422c:       31 c0                   xor    %eax,%eax
    ffffffff8104422e:       e8 60 0f 6d 00          callq  ffffffff81715193 <printk>
    ffffffff81044233:       eb c9                   jmp    ffffffff810441fe <sys_getppid+0xe>
    ffffffff81044235:       66 66 2e 0f 1f 84 00    data32 nopw %cs:0x0(%rax,%rax,1)
    ffffffff8104423c:       00 00 00 00

    因此,禁用跳转标签案例添加了“mov”、“test”和“jne”指令,而跳转标签案例只有“无操作(no-op)”或“jmp 0”。 (jmp 0 在启动时被修补为 5 字节的原子无操作指令。)因此,禁用跳转标签的情况添加:

    6 (mov) + 2 (test) + 2 (jne) = 10 - 5 (5 byte jump 0) = 5 个加法字节。

    如果我们随后包含填充字节,则跳转标签代码将为这个小功能节省总共 16 个字节的指令存储器。在这种情况下,非跳转标签函数的长度为 80 个字节。因此,我们节省了 20% 的指令占用空间。事实上,我们可以
    进一步改进这一点,因为 5 字节无操作实际上可以是 2 字节无操作,因为我们可以使用 2 字节 jmp 到达分支。但是,我们还没有实现最佳的无操作大小(它们目前是硬编码的)。

    由于调度程序路径中使用了许多静态键 API,因此可以使用“pipe-test”(也称为“perf bench sched pipe”)来显示性能改进。在 3.3.0-rc2 上完成的测试:

    跳转标签禁用:

    'bash -c /tmp/pipe-test' 的性能计数器统计信息(50 次运行):

    855.700314 task-clock # 0.534 CPUs utilized ( +- 0.11% )
    200,003 context-switches # 0.234 M/sec ( +- 0.00% )
    0 CPU-migrations # 0.000 M/sec ( +- 39.58% )
    487 page-faults # 0.001 M/sec ( +- 0.02% )
    1,474,374,262 cycles # 1.723 GHz ( +- 0.17% )
    <not supported> stalled-cycles-frontend
    <not supported> stalled-cycles-backend
    1,178,049,567 instructions # 0.80 insns per cycle ( +- 0.06% )
    208,368,926 branches # 243.507 M/sec ( +- 0.06% )
    5,569,188 branch-misses # 2.67% of all branches ( +- 0.54% )

    1.601607384 seconds time elapsed ( +- 0.07% )


    启用跳转标签:

    'bash -c /tmp/pipe-test' 的性能计数器统计信息(50 次运行):

    841.043185 task-clock # 0.533 CPUs utilized ( +- 0.12% )
    200,004 context-switches # 0.238 M/sec ( +- 0.00% )
    0 CPU-migrations # 0.000 M/sec ( +- 40.87% )
    487 page-faults # 0.001 M/sec ( +- 0.05% )
    1,432,559,428 cycles # 1.703 GHz ( +- 0.18% )
    <not supported> stalled-cycles-frontend
    <not supported> stalled-cycles-backend
    1,175,363,994 instructions # 0.82 insns per cycle ( +- 0.04% )
    206,859,359 branches # 245.956 M/sec ( +- 0.04% )
    4,884,119 branch-misses # 2.36% of all branches ( +- 0.85% )

    1.579384366 seconds time elapsed

    已保存分支的百分比为 0.7%,我们在“分支未命中”上节省了 12%。 这是我们期望获得最多节省的地方,因为这种优化是关于减少分支数量。 此外,我们还节省了 0.2% 的指令、2.8% 的周期和 1.4% 的运行时间。

  • 相关阅读:
    理解 Delphi 的类(十) 深入方法[15] 调用其他单元的函数
    理解 Delphi 的类(十) 深入方法[13] 在 interface 区声明的方法
    理解 Delphi 的类(十) 深入方法[12] implementation 区中的方法
    理解 Delphi 的类(十) 深入方法[6] Result
    什么是B*树倒排索引技术 已解决 搜搜问问
    caoruntao的博客 数据结构及算法分类文章列表 ITeye技术网站
    PForDelta索引压缩算法的实现
    计算机词汇(融合了搜狗所有的计算机词库)_搜狗输入法词库
    一种由B+树实现的倒排索引《电脑知识与技术》2011年08期
    海量数据处理专题(九)——外排序
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15026568.html
Copyright © 2011-2022 走看看