如何在 x86_64 上准确地衡量未对齐访问速度?

2024-04-14

In 一个答案 https://stackoverflow.com/questions/45116212/are-packed-structs-portable/45116730#45116730,我已经说过,在很长一段时间内,未对齐访问的速度几乎与对齐访问相同(在 x86/x86_64 上)。我没有任何数据来支持这一说法,因此我为其创建了一个基准。

您认为这个基准测试有什么缺陷吗?你能改进它吗(我的意思是,增加 GB/秒,这样它可以更好地反映事实)?

#include <sys/time.h>
#include <stdio.h>

template <int N>
__attribute__((noinline))
void loop32(const char *v) {
    for (int i=0; i<N; i+=160) {
        __asm__ ("mov     (%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x04(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x08(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x0c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x10(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x14(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x18(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x1c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x20(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x24(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x28(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x2c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x30(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x34(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x38(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x3c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x40(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x44(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x48(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x4c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x50(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x54(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x58(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x5c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x60(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x64(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x68(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x6c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x70(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x74(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x78(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x7c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x80(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x84(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x88(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x8c(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x90(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x94(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x98(%0), %%eax" : : "r"(v) :"eax");
        __asm__ ("mov 0x9c(%0), %%eax" : : "r"(v) :"eax");
        v += 160;
    }
}

template <int N>
__attribute__((noinline))
void loop64(const char *v) {
    for (int i=0; i<N; i+=160) {
        __asm__ ("mov     (%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x08(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x10(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x18(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x20(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x28(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x30(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x38(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x40(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x48(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x50(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x58(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x60(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x68(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x70(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x78(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x80(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x88(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x90(%0), %%rax" : : "r"(v) :"rax");
        __asm__ ("mov 0x98(%0), %%rax" : : "r"(v) :"rax");
        v += 160;
    }
}

template <int N>
__attribute__((noinline))
void loop128a(const char *v) {
    for (int i=0; i<N; i+=160) {
        __asm__ ("movaps     (%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x10(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x20(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x30(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x40(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x50(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x60(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x70(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x80(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movaps 0x90(%0), %%xmm0" : : "r"(v) :"xmm0");
        v += 160;
    }
}

template <int N>
__attribute__((noinline))
void loop128u(const char *v) {
    for (int i=0; i<N; i+=160) {
        __asm__ ("movups     (%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x10(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x20(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x30(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x40(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x50(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x60(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x70(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x80(%0), %%xmm0" : : "r"(v) :"xmm0");
        __asm__ ("movups 0x90(%0), %%xmm0" : : "r"(v) :"xmm0");
        v += 160;
    }
}

long long int t() {
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (long long int)tv.tv_sec*1000000 + tv.tv_usec;
}

int main() {
    const int ITER = 10;
    const int N = 1600000000;

    char *data = reinterpret_cast<char *>(((reinterpret_cast<unsigned long long>(new char[N+32])+15)&~15));
    for (int i=0; i<N+16; i++) data[i] = 0;

    {
        long long int t0 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop32<N/100000>(data);
        }
        long long int t1 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop32<N/100000>(data+1);
        }
        long long int t2 = t();
        for (int i=0; i<ITER; i++) {
            loop32<N>(data);
        }
        long long int t3 = t();
        for (int i=0; i<ITER; i++) {
            loop32<N>(data+1);
        }
        long long int t4 = t();

        printf(" 32-bit, cache: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t1-t0)/1000, (double)N*ITER/(t2-t1)/1000, 100.0*(t2-t1)/(t1-t0)-100.0f);
        printf(" 32-bit,   mem: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t3-t2)/1000, (double)N*ITER/(t4-t3)/1000, 100.0*(t4-t3)/(t3-t2)-100.0f);
    }

    {
        long long int t0 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop64<N/100000>(data);
        }
        long long int t1 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop64<N/100000>(data+1);
        }
        long long int t2 = t();
        for (int i=0; i<ITER; i++) {
            loop64<N>(data);
        }
        long long int t3 = t();
        for (int i=0; i<ITER; i++) {
            loop64<N>(data+1);
        }
        long long int t4 = t();

        printf(" 64-bit, cache: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t1-t0)/1000, (double)N*ITER/(t2-t1)/1000, 100.0*(t2-t1)/(t1-t0)-100.0f);
        printf(" 64-bit,   mem: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t3-t2)/1000, (double)N*ITER/(t4-t3)/1000, 100.0*(t4-t3)/(t3-t2)-100.0f);
    }

    {
        long long int t0 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop128a<N/100000>(data);
        }
        long long int t1 = t();
        for (int i=0; i<ITER*100000; i++) {
            loop128u<N/100000>(data+1);
        }
        long long int t2 = t();
        for (int i=0; i<ITER; i++) {
            loop128a<N>(data);
        }
        long long int t3 = t();
        for (int i=0; i<ITER; i++) {
            loop128u<N>(data+1);
        }
        long long int t4 = t();

        printf("128-bit, cache: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t1-t0)/1000, (double)N*ITER/(t2-t1)/1000, 100.0*(t2-t1)/(t1-t0)-100.0f);
        printf("128-bit,   mem: aligned: %8.4f GB/sec unaligned: %8.4f GB/sec, difference: %0.3f%%\n", (double)N*ITER/(t3-t2)/1000, (double)N*ITER/(t4-t3)/1000, 100.0*(t4-t3)/(t3-t2)-100.0f);
    }
}

计时方式。我可能会设置它,以便通过命令行参数选择测试,这样我就可以用perf stat ./unaligned-test,并获取性能计数器结果,而不仅仅是每个测试的挂钟时间。这样,我就不必关心涡轮/节能,因为我可以在核心时钟周期中进行测量。 (与gettimeofday / rdtsc参考周期,除非您禁用涡轮增压和其他频率变化。)


您仅测试吞吐量,而不测试延迟,因为没有任何负载是相关的。

您的缓存数量将比您的内存数量差,但您可能不会意识到这是因为您的缓存数量可能是由于缓存数量的瓶颈造成的分割加载寄存器 https://stackoverflow.com/questions/46480015/vipt-cache-connection-between-tlb-cache/46480702?noredirect=1#comment96369512_46480702处理跨越缓存行边界的加载/存储。对于顺序读取,高速缓存的外层仍然总是会看到对整个高速缓存行的一系列请求。只有从 L1D 获取数据的执行单元才需要关心对齐。要测试非缓存情况下的未对齐情况,您可以进行分散负载,因此缓存行拆分需要将两个缓存行带入 L1。

Cache lines are 64 bytes wide1, so you're always testing a mix of cache-line splits and within-a-cache-line accesses. Testing always-split loads would bottleneck harder on the split-load microarchitectural resources. (Actually, depending on your CPU, the . Recent Intel CPUs can fetch any unaligned chunk from inside a cache line, but that's because they have special hardware to make that fast. Other CPUs may only be at their fastest when fetching within a naturally-aligned 16 byte chunk or something. @BeeOnRope says that AMD CPUs may care about 16 byte and 32 byte boundaries https://stackoverflow.com/questions/45128763/unaligned-access-speed-on-x86-64/45129784?noredirect=1#comment77232619_45129784.)

你不是在测试存储→负载转发根本不。对于现有测试以及可视化不同比对结果的好方法,请参阅此 stuffdcow.net 博客文章:x86 处理器中的存储到加载转发和内存消歧 http://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/.

通过内存传递数据是一个重要的用例,未对齐 + 缓存行分割可能会干扰某些 CPU 上的存储转发。要正确测试这一点,请确保测试不同的错位,而不仅仅是 1:15(向量)或 1:3(整数)。 (您当前仅测试相对于 16B 对齐的 +1 偏移量)。

我忘记了它是否只是用于存储转发,或者用于常规加载,但是当负载均匀地跨缓存行边界(8:8 向量,也可能是 4:4 或 2:2)分割时,惩罚可能会更少整数分割)。你应该测试一下这个。 (我可能会想到P4lddqu或核心2movqdu)

Intel优化手册 https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf具有大的错位表与从宽存储转发到完全包含在其中的窄重新加载的存储转发。在某些 CPU 上,当宽存储自然对齐时,这在更多情况下有效,即使它不跨越任何缓存行边界。 (也许在 SnB/IvB 上,因为它们使用具有 16B 存储体的存储 L1 缓存,并且这些存储体之间的分割可能会影响存储转发。

我没有重新检查手册,但如果你真的想通过实验来测试它,那么你应该寻找它。)


这提醒我,未对齐的负载更有可能引发 SnB/IvB 上的缓存存储体冲突(因为一个负载可能会触及两个存储体)。但是您不会看到来自单个流的加载,因为访问相同的银行same一个周期内连线两次就可以了。它只访问同一个银行不同的不可能在同一周期内发生的线路。 (例如,当两次内存访问间隔为 128 字节的倍数时。)

您不会尝试测试 4k 页面分割。它们比常规缓存行分割慢,因为它们还需要两个TLB https://en.wikipedia.org/wiki/Translation_lookaside_buffer检查。 (不过,Skylake 将其从约 100 个周期的惩罚改进为超出正常加载使用延迟的约 5 个周期的惩罚)

你测试失败movups在对齐的地址上,所以你不会检测到movups慢于movaps在 Core 2 及更早版本上,即使内存在运行时对齐也是如此。 (我觉得不统一mov即使在 Core 2 中,加载最多 8 个字节也可以,只要它们不跨越缓存行边界即可。我不知道您需要查看多旧的 CPU 才能发现缓存行中非矢量加载的问题。它只是 32 位 CPU,但您仍然可以使用 MMX 或 SSE,甚至 x87 测试 8 字节负载。P5奔腾 https://en.wikipedia.org/wiki/P5_%28microarchitecture%29然后保证对齐的 8 字节加载/存储是原子的,但是P6 https://en.wikipedia.org/wiki/P6_(microarchitecture)更新的保证只要不跨越缓存行边界,缓存的 8 字节加载/存储都是原子的。与 AMD 不同的是,即使在可缓存内存中,8 字节边界对于原子性保证也很重要。为什么自然对齐变量的整数赋值在 x86 上是原子的? https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86)

去看看阿格纳·雾 http://agner.org/optimize/的内容,以了解更多有关未对齐负载如何变慢的信息​​,并编写测试来练习这些情况。事实上,Agner 可能不是最好的资源,因为他的微体系结构指南主要侧重于通过管道获得微指令。只是简单提及缓存行分割的成本,没有深入讨论吞吐量与延迟。

也可以看看:缓存线分割,取两个 http://web.archive.org/web/20120417184641/http://x264dev.multimedia.cx/archives/96,来自 Dark Shikari 的博客(x264 首席开发人员),谈论 Core2 上的未对齐加载策略:检查对齐情况并为块使用不同的策略是值得的。


脚注1如今,64B 缓存行是一个安全的假设。 Pentium 3 及更早版本有 32B 线。 P4 有 64B 行,但它们经常以 128B 对齐的方式传输。 http://www.osronline.com/article.cfm?article=273我想我记得读过 P4 实际上在 L2 或 L3 中有 128B 行,但也许这只是成对传输的 64B 行的扭曲。7-CPU 明确表示 P4 130nm 的两级缓存都有 64B 行 http://www.7-cpu.com/cpu/P4-130.html.

现代 Intel CPU 具有相邻行 L2“空间”预取,同样倾向于拉入 128 字节对齐对的另一半,这在某些情况下可能会增加错误共享。x86-64 的缓存填充大小应该为 128 字节吗? https://stackoverflow.com/questions/72126606/should-the-cache-padding-size-of-x86-64-be-128-bytes展示了一个实验来证明这一点。


也可以看看uarch-长凳 https://github.com/travisdowns/uarch-bench结果。显然有人已经编写了一个测试程序来检查相对于缓存行边界的每个可能的未对齐情况。


我在 Skylake 桌面 (i7-6700k) 上的测试

寻址模式会影响加载使用延迟,正如英特尔在其优化手册中所描述的那样。我用整数测试mov rax, [rax+...], 与movzx/sx(在这种情况下,使用加载的值作为索引,因为它太窄而不能成为指针)。

;;;  Linux x86-64 NASM/YASM source.  Assemble into a static binary
;; public domain, originally written by [email protected] /cdn-cgi/l/email-protection.
;; Share and enjoy.  If it breaks, you get to keep both pieces.

;;; This kind of grew while I was testing and thinking of things to test
;;; I left in some of the comments, but took out most of them and summarized the results outside this code block
;;; When I thought of something new to test, I'd edit, save, and up-arrow my assemble-and-run shell command
;;; Then edit the result into a comment in the source.

section .bss

ALIGN   2 * 1<<20   ; 2MB = 4096*512.  Uses hugepages in .bss but not in .data.  I checked in /proc/<pid>/smaps
buf:    resb 16 * 1<<20

section .text
global _start
_start:
    mov     esi, 128

;   mov             edx, 64*123 + 8
;   mov             edx, 64*123 + 0
;   mov             edx, 64*64 + 0
    xor             edx,edx
   ;; RAX points into buf, 16B into the last 4k page of a 2M hugepage

    mov             eax, buf + (2<<20)*0 + 4096*511 + 64*0 + 16
    mov             ecx, 25000000

%define ADDR(x)  x                     ; SKL: 4c
;%define ADDR(x)  x + rdx              ; SKL: 5c
;%define ADDR(x)  128+60 + x + rdx*2   ; SKL: 11c cache-line split
;%define ADDR(x)  x-8                 ; SKL: 5c
;%define ADDR(x)  x-7                 ; SKL: 12c for 4k-split (even if it's in the middle of a hugepage)
; ... many more things and a block of other result-recording comments taken out

%define dst rax



        mov             [ADDR(rax)], dst
align 32
.loop:
        mov             dst, [ADDR(rax)]
        mov             dst, [ADDR(rax)]
        mov             dst, [ADDR(rax)]
        mov             dst, [ADDR(rax)]
    dec         ecx
    jnz .loop

        xor edi,edi
        mov eax,231
    syscall

然后运行

asm-link load-use-latency.asm && disas load-use-latency && 
    perf stat -etask-clock,cycles,L1-dcache-loads,instructions,branches -r4 ./load-use-latency

+ yasm -felf64 -Worphan-labels -gdwarf2 load-use-latency.asm
+ ld -o load-use-latency load-use-latency.o
 (disassembly output so my terminal history has the asm with the perf results)

 Performance counter stats for './load-use-latency' (4 runs):

     91.422838      task-clock:u (msec)       #    0.990 CPUs utilized            ( +-  0.09% )
   400,105,802      cycles:u                  #    4.376 GHz                      ( +-  0.00% )
   100,000,013      L1-dcache-loads:u         # 1093.819 M/sec                    ( +-  0.00% )
   150,000,039      instructions:u            #    0.37  insn per cycle           ( +-  0.00% )
    25,000,031      branches:u                #  273.455 M/sec                    ( +-  0.00% )

   0.092365514 seconds time elapsed                                          ( +-  0.52% )

在这种情况下,我正在测试mov rax, [rax],自然对齐,因此周期 = 4*L1-dcache-loads。 4c 延迟。我没有禁用涡轮增压或类似的东西。由于内核没有发生任何事情,因此内核时钟周期是最好的测量方法。

  • [base + 0..2047]:4c 加载使用延迟、11c 缓存行分割、11c 4k 页分割(即使在同一大页内)。看当基址+偏移量与基址位于不同页面时是否会受到惩罚? https://stackoverflow.com/questions/52351397/is-there-a-penalty-when-baseoffset-is-in-a-different-page-than-the-base欲了解更多详细信息:如果base+disp结果位于不同的页面base,必须重放加载 uop。
  • 任何其他寻址模式:5c 延迟、11c 缓存行分割、12c 4k 分割(甚至在大页内)。这包括[rax - 16]。造成差异的不是 disp8 与 disp32。

因此:大页面无助于避免页面分割惩罚(至少当 TLB 中的两个页面都很热时)。高速缓存行分割使寻址模式变得无关紧要,但“快速”寻址模式对于正常加载和页分割加载的延迟要低 1c。

4k 分割处理比以前好得多,请参阅 @harold 的数据,其中 Haswell 的 4k 分割延迟约为 32c。 (较旧的 CPU 可能比这更糟糕。我认为 SKL 之前应该有约 100 个周期的惩罚。)

吞吐量(无论寻址模式如何),通过使用除rax所以负载是独立的:

  • 无分裂:0.5c。
  • CL 分裂:1c。
  • 4k 分割:~3.8 至 3.9c(much比 Skylake 之前的 CPU 更好)

相同的吞吐量/延迟movzx/movsx(包括 WORD 分割),正如预期的那样,因为它们是在加载端口中处理的(与某些 AMD CPU 不同,其中还有 ALU uop)。

依赖于缓存行分割加载的 Uop 从 RS(保留站)重播。计数器uops_dispatched_port.port_2 + port_3= 2x 数量mov rdi, [rdi],在另一个测试中使用基本相同的循环。 (这是一种依赖负载的情况,不受吞吐量限制。)在 AGU 生成线性地址之前,CPU 无法检测到拆分负载。

我之前认为拆分加载本身会重播,但这是基于指针追逐测试,其中每个加载都依赖于先前的加载。如果我们放一个imul rdi, rdi, 1在循环中,我们会得到额外的端口 1 ALU 计数,因为它会被重播,而不是负载。

分割加载只需分派一次,但我不确定它后来是否借用同一加载端口中的一个周期来访问另一个缓存行(并将其与保存在该加载端口内的分割寄存器中的第一部分结合起来。 ) 或者启动另一条线路的需求负载(如果 L1d 中不存在该线路)。

无论细节如何,即使避免负载重播,缓存行拆分负载的吞吐量也会低于非拆分负载。 (无论如何,我们没有测试指针追逐。)

也可以看看IvyBridge 上指针追逐循环中附近的依赖存储对性能产生奇怪的影响。添加额外的负载会加快速度吗? https://stackoverflow.com/questions/54084992/weird-performance-effects-from-nearby-dependent-stores-in-a-pointer-chasing-loop有关 uop 重播的更多信息。 (但请注意,这是针对 uops依赖于负载,而不是负载 uop 本身。在该问答中,相关的微指令也主要是负载。)

缓存未命中加载不会itself需要重播以在准备好时“接受”传入数据,仅依赖于 uops。查看聊天讨论加载操作在调度、完成或其他时间时是否从 RS 中释放? https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th. This https://godbolt.org/z/HJF3BN https://godbolt.org/z/HJF3BNi7-6700k 上的 NASM 测试用例显示,无论 L1d 命中还是 L3 命中,所调度的负载 uops 数量都是相同的。但调度的 ALU uops 数量(不包括循环开销)从每个负载 1 个增加到每个负载约 8.75 个。当加载数据可能从 L2 缓存到达时,调度程序会积极地安排消耗数据的微指令在周期中分派(然后看起来非常积极),而不是等待一个额外的周期来查看它是否到达。

我们还没有测试当有其他独立但较年轻的工作可以在输入肯定已准备好的同一端口上完成时,重播有多积极。


SKL有两个硬件page-walk单元,这可能与4k split性能的大幅提升有关。即使没有 TLB 未命中,较旧的 CPU 也必须考虑到可能存在的情况。

有趣的是,4k 分割吞吐量是非整数。我认为我的测量有足够的精度和可重复性来说明这一点。请记住这是与everyload 是 4k 分割,并且没有其他工作正在进行(除了在一个小的 dec/jnz 循环内)。如果您在实际代码中遇到过这种情况,那么您就做错了。

我对为什么它可能是非整数没有任何可靠的猜测,但显然对于 4k 分割,微架构上必须发生很多事情。它仍然是缓存行分割,并且必须检查 TLB 两次。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 x86_64 上准确地衡量未对齐访问速度? 的相关文章

随机推荐

  • 获取二维数组的长度

    如果我不知道数组的第二个维度 如何获取它 array length仅给出第一个维度 例如 在 public class B public static void main String main int nir new int 2 3 Sy
  • LAST_INSERT_ID() MySQL

    我有一个 MySQL 问题 我认为这一定很简单 当我运行以下 MySql 查询时 我需要从 table1 返回最后插入的 ID INSERT INTO table1 title userid VALUES test 1 INSERT INT
  • 将 lambda 传递到函数模板中

    我正在学习 C 并且正在尝试实现一个二分搜索函数 该函数查找谓词所适用的第一个元素 该函数的第一个参数是一个向量 第二个参数是一个计算给定元素的谓词的函数 二分查找函数如下所示 template
  • 如何忽略 IntelliJ 中的“无法解析查询参数”错误

    我有一个疑问 Date dDateFrom String sql select a from tblA where timestamp gt ps this connection prepareStatement sql ps setTim
  • 如何测量用 PHP 编写的代码的速度? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我怎么能说哪一类 都做同样的工作 执行得更快呢 有没有软件可以测量它 你有 至少 两种解决方案 相当
  • 如何在 Visual Studio 中关闭构建定义的缓存

    在项目文件中我导入我自己的目标文件
  • Python:格式化使用“%”和“{”作为字符的正则表达式字符串

    我有以下正则表达式 它允许我使用 re split 解析 20 20 或 20 50 等百分比 0 9 1 3 我想使用字符串格式将系列标识符 即 作为 config py 的参数传递 SERIES 我尝试过的两种方法都产生了错误 新式格式
  • 如果“target”属性与某个字符串通过 jQuery 匹配,则获取该属性的值

    假设我有一些链接 a href target self a a href target self a a href target self a 如何获取以 UniqueString 开头的目标属性的值 元素的ID未知 我们需要通过 targ
  • 显示软键盘时向上移动布局?

    我在RelativeView中有一些元素设置了对齐底部属性 当软键盘出现时 这些元素被软键盘隐藏 我希望它们向上移动 以便如果有足够的屏幕空间 它们会显示在键盘上方 或者使键盘上方的部分可滚动 以便用户仍然可以看到元素 关于如何解决这个问题
  • .NET MAUI 每天运行几次的循环后台作业

    我希望后台工作每天运行 2 3 次 但更少也没关系 这只是对我的服务器的快速 api 调用 不需要更新 UI 事实上我更喜欢它在应用程序不在前台时运行 但这是另一个主题 我读过最新版本的 Android 和 iOS 甚至制造商限制了该应用程
  • 是否可以用 phongeap 目录中的其他数据库文件替换现有的 sqlite 数据库文件

    我有一个要求用其他数据库文件替换设备数据库 就像备份和存储目录 下载 备份 应用程序名称 或服务器一样 如果我们删除数据或丢失手机 那么我已经从服务器恢复数据并替换为设备数据库文件 我正在尝试使用代码 但仅替换数据库我没有得到 functi
  • scrollsToTop 不适用于 UIViewController 包含

    使用SDK 6 1 Xcode 4 6 1 我制作了一个新项目Master Detail iOS App ARC 没有故事板 然后在DetailViewController 在里面viewDidLoad我添加两个UITableView包含在
  • 我应该选择哪个 graphql-spring-boot-starter ?

    我正在考虑将 GraphQL 功能添加到我的 Spring Boot 应用程序中 我发现有两个工件可以做到这一点 One is com graphql java kickstart graphql spring boot starter另一
  • 用 Espresso 单击 EditText 的可绘制右侧

    如何才能单击 EditText 右侧可绘制对象 检查屏幕截图 我尝试了多种方法 但总是陷入困境 public static Matcher
  • 在开始动画之前修改 iPhone 动画容器视图

    我正在向我正在开发的纸牌游戏添加一些基本动画 我的第一个 iPhone 应用程序 我正在创建一个自定义 UIView 类 AnimationContainer 它从 image1 翻转到 image2 同时从 rect1 移动到 rect2
  • 阅读 Whatsapp 消息

    我想创建一个 Android 应用程序来读取收件箱以及 Whatsapp 消息 我想做一个备份之类的东西 尽管可以在 Whatsapp 中选择这样做 但我想阅读这些消息 然后从我的应用程序中备份它们 我了解到 Whatsapp 在文件系统中
  • Symfony2表单类型实体添加额外选项

    我有以下 Symfony 表单字段 它是从实体加载的下拉列表 gt add measureunit entity array label gt Measure Unit class gt TeamERPBaseBundle MeasureU
  • Python正则表达式替换

    嘿 我正在尝试找出一个正则表达式来执行以下操作 这是我的字符串 Place 08 09 2010 15 531 2 909 650 我需要用逗号分隔该字符串 尽管由于数字数据字段中使用了逗号 但分割无法正常工作 所以我想在运行分割字符串之前
  • 处理和解决“网络使用过多(后台)”的正确方法

    问题背景 当前 我们面临 网络使用过多 背景 来自 Android Vital 报告 最后30天是0 04 但我们只是好于 9 过去 30 天 0 04 基准 优于 9 由于仅好于9 看起来像是一件可怕的事情 我们决定认真研究这个问题 该应
  • 如何在 x86_64 上准确地衡量未对齐访问速度?

    In 一个答案 https stackoverflow com questions 45116212 are packed structs portable 45116730 45116730 我已经说过 在很长一段时间内 未对齐访问的速度