首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是什么原因导致了-O0而不是-O3在Cortex-A72上的简单紧循环周期的如此高的变异性?

是什么原因导致了-O0而不是-O3在Cortex-A72上的简单紧循环周期的如此高的变异性?
EN

Stack Overflow用户
提问于 2019-12-16 00:26:18
回答 2查看 277关注 0票数 9

我正在为一段代码进行一些实验,以获得高度一致的运行时。我目前正在计时的代码是一个相当任意的CPU绑定工作负载:

代码语言:javascript
复制
int cpu_workload_external_O3(){
    int x = 0;
    for(int ind = 0; ind < 12349560; ind++){
        x = ((x ^ 0x123) + x * 3) % 123456;
    }
    return x;
}

我编写了一个内核模块,该模块禁用中断,然后运行上述函数的10次试用,每次测试的计时方法是将时钟周期计数器中前后的差异计算出来。其他要注意的事项:

  • 这台机器是一个ARM Cortex-A72,有4个插座,每个插座有4个核心(每个都有自己的L1缓存)。
  • 时钟频率缩放关闭
  • 不支持超线程。
  • 除了一些基本的系统进程外,这台机器几乎什么也不运行。

换句话说,我相信系统可变性的大部分/所有来源都被考虑进去了,特别是当作为一个内核模块运行时,通过spin_lock_irqsave()禁用中断时,代码应该实现几乎相同的性能运行到运行(当某些指令第一次被拖入缓存时,可能是第一次运行时的一小部分性能问题,但仅此而已)。

实际上,当使用-O3编译基准代码时,我看到平均135,845,192个周期中最多有200个周期,大多数试验所用的时间完全相同。然而,当使用-O0编译时,这个范围从262,710,916周期上升到了158,386个周期。所谓范围,我指的是最长和最短的跑步时间之间的差别。此外,对于-O0代码来说,哪种试验是最慢/最快的并不是很一致的--相反,有一次最快的是第一次,最慢的是紧接着的一次!

所以:是什么导致了-O0代码的可变性上限?从程序集来看,-O3代码似乎存储了所有的东西(?)在寄存器中,而-O0代码有很多对sp的引用,因此它似乎是在访问内存。但即使如此,我也希望所有东西都能进入L1缓存,并以相当确定的访问时间坐在那里。

代码

正在进行基准测试的代码位于上面的代码段中。程序集在下面。这两个文件都是用gcc 7.4.0编译的,除了-O0-O3之外,没有任何标志。

-O0

代码语言:javascript
复制
0000000000000000 <cpu_workload_external_O0>:
   0:   d10043ff        sub     sp, sp, #0x10
   4:   b9000bff        str     wzr, [sp, #8]
   8:   b9000fff        str     wzr, [sp, #12]
   c:   14000018        b       6c <cpu_workload_external_O0+0x6c>
  10:   b9400be1        ldr     w1, [sp, #8]
  14:   52802460        mov     w0, #0x123                      // #291
  18:   4a000022        eor     w2, w1, w0
  1c:   b9400be1        ldr     w1, [sp, #8]
  20:   2a0103e0        mov     w0, w1
  24:   531f7800        lsl     w0, w0, #1
  28:   0b010000        add     w0, w0, w1
  2c:   0b000040        add     w0, w2, w0
  30:   528aea61        mov     w1, #0x5753                     // #22355
  34:   72a10fc1        movk    w1, #0x87e, lsl #16
  38:   9b217c01        smull   x1, w0, w1
  3c:   d360fc21        lsr     x1, x1, #32
  40:   130c7c22        asr     w2, w1, #12
  44:   131f7c01        asr     w1, w0, #31
  48:   4b010042        sub     w2, w2, w1
  4c:   529c4801        mov     w1, #0xe240                     // #57920
  50:   72a00021        movk    w1, #0x1, lsl #16
  54:   1b017c41        mul     w1, w2, w1
  58:   4b010000        sub     w0, w0, w1
  5c:   b9000be0        str     w0, [sp, #8]
  60:   b9400fe0        ldr     w0, [sp, #12]
  64:   11000400        add     w0, w0, #0x1
  68:   b9000fe0        str     w0, [sp, #12]
  6c:   b9400fe1        ldr     w1, [sp, #12]
  70:   528e0ee0        mov     w0, #0x7077                     // #28791
  74:   72a01780        movk    w0, #0xbc, lsl #16
  78:   6b00003f        cmp     w1, w0
  7c:   54fffcad        b.le    10 <cpu_workload_external_O0+0x10>
  80:   b9400be0        ldr     w0, [sp, #8]
  84:   910043ff        add     sp, sp, #0x10
  88:   d65f03c0        ret

-O3

代码语言:javascript
复制
0000000000000000 <cpu_workload_external_O3>:
   0:   528e0f02        mov     w2, #0x7078                     // #28792
   4:   5292baa4        mov     w4, #0x95d5                     // #38357
   8:   529c4803        mov     w3, #0xe240                     // #57920
   c:   72a01782        movk    w2, #0xbc, lsl #16
  10:   52800000        mov     w0, #0x0                        // #0
  14:   52802465        mov     w5, #0x123                      // #291
  18:   72a043e4        movk    w4, #0x21f, lsl #16
  1c:   72a00023        movk    w3, #0x1, lsl #16
  20:   4a050001        eor     w1, w0, w5
  24:   0b000400        add     w0, w0, w0, lsl #1
  28:   0b000021        add     w1, w1, w0
  2c:   71000442        subs    w2, w2, #0x1
  30:   53067c20        lsr     w0, w1, #6
  34:   9ba47c00        umull   x0, w0, w4
  38:   d364fc00        lsr     x0, x0, #36
  3c:   1b038400        msub    w0, w0, w3, w1
  40:   54ffff01        b.ne    20 <cpu_workload_external_O3+0x20>  // b.any
  44:   d65f03c0        ret

核模块

运行测试的代码如下所示。它在每次迭代之前/之后读取PMCCNTR_EL0,将差异存储在数组中,并在所有试验中打印出最后的min/max时间。函数cpu_workload_external_O0cpu_workload_external_O3位于外部对象文件中,这些文件分别编译,然后链接到。

代码语言:javascript
复制
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include "cpu.h"

static DEFINE_SPINLOCK(lock);

void runBenchmark(int (*benchmarkFunc)(void)){
    // Enable perf counters.
    u32 pmcr;
    asm volatile("mrs %0, pmcr_el0" : "=r" (pmcr));
    asm volatile("msr pmcr_el0, %0" : : "r" (pmcr|(1)));

    // Run trials, storing the time of each in `clockDiffs`.
    u32 result = 0;
    #define numtrials 10
    u32 clockDiffs[numtrials] = {0};
    u32 clockStart, clockEnd;
    for(int trial = 0; trial < numtrials; trial++){
        asm volatile("isb; mrs %0, PMCCNTR_EL0" : "=r" (clockStart));
        result += benchmarkFunc();
        asm volatile("isb; mrs %0, PMCCNTR_EL0" : "=r" (clockEnd));

        // Reset PMCCNTR_EL0.
        asm volatile("mrs %0, pmcr_el0" : "=r" (pmcr));
        asm volatile("msr pmcr_el0, %0" : : "r" (pmcr|(((uint32_t)1) << 2)));

        clockDiffs[trial] = clockEnd - clockStart;
    }

    // Compute the min and max times across all trials.
    u32 minTime = clockDiffs[0];
    u32 maxTime = clockDiffs[0];
    for(int ind = 1; ind < numtrials; ind++){
        u32 time = clockDiffs[ind];
        if(time < minTime){
            minTime = time;
        } else if(time > maxTime){
            maxTime = time;
        }
    }

    // Print the result so the benchmark function doesn't get optimized out.
    printk("result: %d\n", result);

    printk("diff: max %d - min %d = %d cycles\n", maxTime, minTime, maxTime - minTime);
}

int init_module(void) {
    printk("enter\n");
    unsigned long flags;
    spin_lock_irqsave(&lock, flags);

    printk("-O0\n");
    runBenchmark(cpu_workload_external_O0);

    printk("-O3\n");
    runBenchmark(cpu_workload_external_O3);

    spin_unlock_irqrestore(&lock, flags);
    return 0;
}

void cleanup_module(void) {
    printk("exit\n");
}

硬件

代码语言:javascript
复制
$ lscpu
Architecture:        aarch64
Byte Order:          Little Endian
CPU(s):              16
On-line CPU(s) list: 0-15
Thread(s) per core:  1
Core(s) per socket:  4
Socket(s):           4
NUMA node(s):        1
Vendor ID:           ARM
Model:               3
Model name:          Cortex-A72
Stepping:            r0p3
BogoMIPS:            166.66
L1d cache:           32K
L1i cache:           48K
L2 cache:            2048K
NUMA node0 CPU(s):   0-15
Flags:               fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
代码语言:javascript
复制
$ lscpu --extended
CPU NODE SOCKET CORE L1d:L1i:L2 ONLINE
0   0    0      0    0:0:0      yes
1   0    0      1    1:1:0      yes
2   0    0      2    2:2:0      yes
3   0    0      3    3:3:0      yes
4   0    1      4    4:4:1      yes
5   0    1      5    5:5:1      yes
6   0    1      6    6:6:1      yes
7   0    1      7    7:7:1      yes
8   0    2      8    8:8:2      yes
9   0    2      9    9:9:2      yes
10  0    2      10   10:10:2    yes
11  0    2      11   11:11:2    yes
12  0    3      12   12:12:3    yes
13  0    3      13   13:13:3    yes
14  0    3      14   14:14:3    yes
15  0    3      15   15:15:3    yes
代码语言:javascript
复制
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 32159 MB
node 0 free: 30661 MB
node distances:
node   0
  0:  10

样品测量

下面是内核模块一次执行的一些输出:

代码语言:javascript
复制
[902574.112692] kernel-module: running on cpu 15                                                                                                                                      
[902576.403537] kernel-module: trial 00: 309983568 74097394 98796602 <-- max
[902576.403539] kernel-module: trial 01: 309983562 74097397 98796597                                                                                                                  
[902576.403540] kernel-module: trial 02: 309983562 74097397 98796597                                                                                                                  
[902576.403541] kernel-module: trial 03: 309983562 74097397 98796597
[902576.403543] kernel-module: trial 04: 309983562 74097397 98796597
[902576.403544] kernel-module: trial 05: 309983562 74097397 98796597                                                                                                                  
[902576.403545] kernel-module: trial 06: 309983562 74097397 98796597
[902576.403547] kernel-module: trial 07: 309983562 74097397 98796597
[902576.403548] kernel-module: trial 08: 309983562 74097397 98796597
[902576.403550] kernel-module: trial 09: 309983562 74097397 98796597                                                                                                                  
[902576.403551] kernel-module: trial 10: 309983562 74097397 98796597
[902576.403552] kernel-module: trial 11: 309983562 74097397 98796597
[902576.403554] kernel-module: trial 12: 309983562 74097397 98796597                                                                                                                  
[902576.403555] kernel-module: trial 13: 309849076 74097403 98796630 <-- min
[902576.403557] kernel-module: trial 14: 309983562 74097397 98796597                                                                                                                  
[902576.403558] kernel-module: min time: 309849076
[902576.403559] kernel-module: max time: 309983568                                                                                                                                    
[902576.403560] kernel-module: diff: 134492

对于每一次试验,报告的值是:循环# (0x11),L1D访问# (0x04),L1I访问# (0x14)。我使用的是本臂PMU参考的11.8节)。

EN

回答 2

Stack Overflow用户

发布于 2019-12-18 17:41:08

在最近的Linux内核中,自动NUMA页面迁移机制周期性地关闭TLB条目,以便监视NUMA的本地性。TLB重新加载会减慢O0代码的速度,即使数据保留在L1DCache中。

不应在内核页上激活页迁移机制。

检查是否启用了自动NUMA页面迁移。

代码语言:javascript
复制
$ cat /proc/sys/kernel/numa_balancing

你可以用

代码语言:javascript
复制
$ echo 0 > /proc/sys/kernel/numa_balancing
票数 4
EN

Stack Overflow用户

发布于 2019-12-23 23:41:32

你的方差是6*10^-4。虽然令人震惊的是,超过1.3*10^-6,一旦您的程序与缓存,它涉及到许多同步操作。同步总是意味着浪费时间。

有趣的是,您的-O0,-O3比较如何模仿L1缓存命中大约2倍于寄存器引用的一般规则。平均O3运行时间是O0运行时间的51.70%。当你应用较低/更高的方差,我们有(O3-200)/(O0+158386),我们看到一个提高到51.67%。

简而言之,是的,缓存永远不会是确定性的;并且您看到的低方差与与较慢的设备同步应该达到的要求是一致的。与更确定的寄存器专用机器相比,这只是一个很大的差异。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59349267

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档