You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86_64架构下如何将内存解引用延迟控制在3.6周期的10倍以内?

问题背景与需求

我正在优化并发链表访问,针对x86_64架构(AMD Ryzen处理器)、Ubuntu系统,对内存解引用的平均耗时做了基准测试,结果如下:

  • 连续邻近内存访问平均耗时3.6处理器周期;
  • 非邻近内存访问的平均耗时最高攀升至390周期,是前者的110倍。

需明确技术方案:在不更换处理器架构(AMD/Intel x86_64)和操作系统(Ubuntu)的前提下,如何让内存解引用的耗时最多仅为3.6周期的10倍(即≤36周期),而非当前的110倍?


基准测试代码
#include <iostream>
#include <iomanip>
#include <cstdint>

#define repeat_0x10(x) x x x x  x x x x  x x x x  x x x x 
#define repeat_0x100(x) repeat_0x10(repeat_0x10(x))
#define repeat_0x1000(x) repeat_0x100(repeat_0x10(x))

const int loopn=0x4000;
#define asmTimes 0x100
#define tt(x) repeat_0x100(x)

#include <x86intrin.h>
#include <unistd.h>

using namespace std;

int64_t aaa=0x0;

typedef void fT();

// 传入一个函数,bench0执行它n次,返回执行所需的处理器时钟周期数
// 执行前会将变量aaa的地址加载到R15寄存器
auto bench0(fT f,size_t n=loopn){
    auto s1=__rdtsc();
        asm ("lea %0,%%r15" : : "m" (aaa));
        for(int i=n;i--!=0;) f();
    auto s2=__rdtsc();
    return s2-s1;
}
// 传入一个由rep次重复代码组成的函数,循环执行n次,返回单次重复代码所需的时钟周期数(浮点型)
auto bench(fT f,size_t n=loopn,size_t rep=asmTimes){
    return (float)bench0(f,n)/n/rep;
}

#define countof(A) (sizeof(A)/sizeof(*A))

void* P[0x1000000];

// 构建链表,链表中连续节点的间隔为"step"
void initP(size_t step){
    void* p=nullptr;
    for(size_t i=0;i!=step;i++)
    for(size_t j=0;j!=countof(P);j+=step){*(P+j+i)=p;p=P+j+i;}
    p=P+countof(P)-1;
    //for(size_t i=countof(P);i--!=0;)  p=*(void**)p; std::cout<<" p="<<p<<endl;
}

void walkP(){repeat_0x100(asm("mov (%r15),%r15");)}

auto cpuFrequency(){
    const int sleepN=500000;
    auto t1=__rdtsc();
        usleep(sleepN);
    auto t2=__rdtsc();
    return double(t2-t1)/(sleepN*1e-6);
}

int main(int argc, char **argv)
{   
    cout<<" cpuFrequency()="<<cpuFrequency()<<" sizeof(void*)="<<sizeof(void*)<<endl;
    
    for(size_t i=0;i<0x10;i++){
        aaa=(uintptr_t)(P+countof(P)-1);
        size_t step=1<<i;
        initP(step);cout<<"bench(walkP("<<step<<"))="<<bench(walkP,0x10000,0x100)<<endl;
    }
    return 0;
}

测试结果
cpuFrequency()=3.69355e+09 sizeof(void*)=8
 p=0
bench(walkP(1))=3.6146
 p=0
bench(walkP(2))=4.48525
 p=0
bench(walkP(4))=8.33419
 p=0
bench(walkP(8))=16.4063
 p=0
bench(walkP(16))=126.335
 p=0
bench(walkP(32))=177.424
 p=0
bench(walkP(64))=336.674
 p=0
bench(walkP(128))=340.422
 p=0
bench(walkP(256))=350.896
 p=0
bench(walkP(512))=360.694
 p=0
bench(walkP(1024))=362.763
 p=0
bench(walkP(2048))=367.925
 p=0
bench(walkP(4096))=391.085
 p=0
bench(walkP(8192))=386.941
 p=0
bench(walkP(16384))=398.989
 p=0
bench(walkP(32768))=389.542

技术优化方案

以下是针对x86_64(AMD Ryzen)+ Ubuntu环境的优化手段,可将非邻近内存解引用耗时控制在连续访问的10倍以内:

1. 硬件预取机制利用

  • 显式预取指令:在访问链表节点前,使用__builtin_prefetch()或x86汇编指令prefetcht0/prefetcht1提前加载下一个节点到缓存。例如遍历链表时,每次解引用当前节点后立即预取下一个节点地址:
    void walkP() {
        repeat_0x100(
            asm("mov (%r15),%r15");
            asm("prefetcht0 (%r15)");
        );
    }
    
    AMD Ryzen的预取器对显式预取响应良好,可将高延迟内存访问转化为缓存命中。
  • 调整预取距离:根据链表节点间隔和CPU缓存行大小(通常64字节),设置合适的预提前瞻步数,避免预取失效或缓存污染。

2. 内存布局优化

  • 节点紧凑化:将链表节点的核心数据(含指针)压缩到同一缓存行内,避免跨缓存行访问。必要时采用分离式存储,将高频访问的指针、数据与低频数据拆分。
  • 强制内存对齐:用alignas(64)关键字确保链表节点和指针地址按64字节(缓存行大小)对齐,避免缓存行拆分访问:
    struct Node {
        alignas(64) Node* next;
        // 其他数据字段
    };
    
  • 内存池分配:使用自定义内存池(如基于mmap或posix_memalign的固定块分配器)分配链表节点,确保节点在物理内存中连续或邻近分布,减少页表跳转和TLB miss。

3. 并发访问优化

  • 链表分片:将大链表拆分为多个小分片,每个分片由单独线程负责访问,减少线程间缓存争用(伪共享)。每个分片的节点集中存储,提升局部性。
  • 隔离共享变量:将线程间共享的控制变量(锁、原子计数器)用alignas(64)单独放在缓存行中,防止多线程频繁修改同一缓存行导致的缓存失效风暴。
  • 批量操作:在并发场景下,批量读取多个链表节点到本地缓存后再处理,利用CPU乱序执行能力隐藏内存延迟。

4. 软件局部性优化

  • 链表转数组遍历:若访问模式允许(如读多写少、短暂数据不一致可接受),定期将链表节点复制到连续数组中,利用数组的连续内存访问特性获得缓存命中优势,完成后同步回链表。
  • 循环展开与指令调度:手动展开遍历循环,增加指令级并行度,让CPU乱序执行单元提前调度内存访问指令,隐藏延迟。

5. 系统层面优化

  • 启用透明大页:在Ubuntu上执行echo always > /sys/kernel/mm/transparent_hugepage/enabled强制开启透明大页,减少TLB miss次数,降低地址转换延迟。
  • 线程核心绑定:用sched_setaffinity将线程绑定到同一CCX(AMD Ryzen核心复合体)内的CPU核心,避免线程迁移导致的缓存失效,利用CCX共享的L3缓存提升命中率。

内容的提问来源于stack exchange,提问作者George Kourtis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 09:54:55