计算DRAM的峰值性能。

3

亲爱的Stackoverflow社区,

我正在尝试理解DRAM访问性能极限的计算,但我的基准测试结果与规格书中的数字相差甚远。当然,人们不会期望达到理论极限,但可能有一些解释为什么相差如此之远。

例如,我在我的系统上测量DRAM访问速度约为11 GB/s,但WikiChip JEDEC规格列出双通道DDR4-2400系统的峰值性能为38.4 GB/s。

我的测量是否有误或这些数字只是计算峰值内存性能的错误数字?

测量方法

在我的系统上,使用一颗1.8GHz的英特尔酷睿i7-8550U处理器,该处理器基于卡比湖微架构

事实是,lshw 显示了两个 memory 条目。

     *-memory
        ...
        *-bank:0
             ...
             slot: ChannelA-DIMM0
             width: 64 bits
             clock: 2400MHz (0.4ns)
        *-bank:1
             ...
             slot: ChannelB-DIMM0
             width: 64 bits
             clock: 2400MHz (0.4ns)

那么这两个应该以“双通道”模式运行(这自动进行吗?)。

设置了系统以减少测量噪声:

  • 禁用频率缩放
  • 禁用地址空间布局随机化
  • scaling_governor设置为performance
  • 使用cpuset将基准测试隔离在自己的核心上
  • 设置一个niceness值为-20
  • 使用最少量的进程运行无头系统

然后,我从pmbw - Parallel Memory Bandwidth Benchmark / Measurement程序的ScanWrite256PtrUnrollLoop基准测试开始:

pmbw -f ScanWrite256PtrUnrollLoop -p 1 -P 1

可以使用内部循环进行检查

gdb -batch -ex "disassemble/rs ScanWrite256PtrUnrollLoop" `which pmbw` | c++filt

看起来这个基准测试创建了一个“流”,使用vmovdqa移动对齐的打包整数值AVX256指令来饱和CPU的内存子系统

<+44>:  
  vmovdqa %ymm0,(%rax)
  vmovdqa %ymm0,0x20(%rax)
  vmovdqa %ymm0,0x40(%rax)
  vmovdqa %ymm0,0x60(%rax)
  vmovdqa %ymm0,0x80(%rax)
  vmovdqa %ymm0,0xa0(%rax)
  vmovdqa %ymm0,0xc0(%rax)
  vmovdqa %ymm0,0xe0(%rax)
  vmovdqa %ymm0,0x100(%rax)
  vmovdqa %ymm0,0x120(%rax)
  vmovdqa %ymm0,0x140(%rax)
  vmovdqa %ymm0,0x160(%rax)
  vmovdqa %ymm0,0x180(%rax)
  vmovdqa %ymm0,0x1a0(%rax)
  vmovdqa %ymm0,0x1c0(%rax)
  vmovdqa %ymm0,0x1e0(%rax)
  add    $0x200,%rax
  cmp    %rsi,%rax
  jb     0x37dc <ScanWrite256PtrUnrollLoop(char*, unsigned long, unsigned long)+44>

作为 Julia 中类似的基准,我想到了以下内容:
const C = NTuple{K,VecElement{Float64}} where K
@inline function Base.fill!(dst::Vector{C{K}},x::C{K},::Val{NT} = Val(8)) where {NT,K}
  NB = div(length(dst),NT)
  k = 0
  @inbounds for i in Base.OneTo(NB)
    @simd   for j in Base.OneTo(NT)
      dst[k += 1] = x
    end
  end
end

当调查这个fill!函数的内部循环时

code_native(fill!,(Vector{C{4}},C{4},Val{16}),debuginfo=:none)

我们可以看到,这也创建了类似的“流”vmovups 移动非对齐打包单精度浮点值指令:
L32:
  vmovups %ymm0, -480(%rcx)
  vmovups %ymm0, -448(%rcx)
  vmovups %ymm0, -416(%rcx)
  vmovups %ymm0, -384(%rcx)
  vmovups %ymm0, -352(%rcx)
  vmovups %ymm0, -320(%rcx)
  vmovups %ymm0, -288(%rcx)
  vmovups %ymm0, -256(%rcx)
  vmovups %ymm0, -224(%rcx)
  vmovups %ymm0, -192(%rcx)
  vmovups %ymm0, -160(%rcx)
  vmovups %ymm0, -128(%rcx)
  vmovups %ymm0, -96(%rcx)
  vmovups %ymm0, -64(%rcx)
  vmovups %ymm0, -32(%rcx)
  vmovups %ymm0, (%rcx)
  leaq    1(%rdx), %rsi
  addq    $512, %rcx
  cmpq    %rax, %rdx
  movq    %rsi, %rdx
  jne     L32

现在,所有这些基准测试都以某种方式显示了三个缓存和主内存的不同“性能平台”。

有趣的是,对于更大的测试大小,它们都绑定在约11 GB/s左右:

使用多线程和(重新)激活频率缩放(将CPU频率加倍)对较小的测试大小有影响,但对较大的测试大小并没有真正改变这些发现。
1个回答

1
经过一些调查,我发现一篇博客文章描述了同样的问题,并建议使用所谓的非时间写操作。还有多个 其他 资源,包括Ulrich Drepper的LWN文章,提供更详细的研究资料。

在Julia中,可以通过SIMD.jl包中的vstorent实现此目的:

function Base.fill!(p::Ptr{T},len::Int64,y::Y,::Val{K},::Val{NT} = Val(16)) where
  {K,NT,T,Y <: Union{NTuple{K,T},T,Vec{K,T}}}
  # @assert Int64(p) % K*S == 0
  x  = Vec{K,T}(y)
  nb = max(div(len ,K*NT),0)
  S  = sizeof(T)
  p0 = p + nb*NT*K*S
  while p < p0
    for j in Base.OneTo(NT)
      vstorent(x,p) # non-temporal, `K*S` aligned store
      p += K*S
    end
  end
  Threads.atomic_fence()
  return nothing
end

对于4×Float64向量宽度和16个展开因子,它会编译下来。

code_native(fill!,(Ptr{Float64},Int64,Float64,Val{4},Val{16}),debuginfo=:none)

要使用非暂态提示指令vmovntps存储打包的单精度浮点值

...                                            # y is register-passed via %xmm0
        vbroadcastsd    %xmm0, %ymm0           # x = Vec{4,Float64}(y)
        nop
L48:
        vmovntps        %ymm0, (%rdi)
        vmovntps        %ymm0, 32(%rdi)
        vmovntps        %ymm0, 64(%rdi)
        vmovntps        %ymm0, 96(%rdi)
        vmovntps        %ymm0, 128(%rdi)
        vmovntps        %ymm0, 160(%rdi)
        vmovntps        %ymm0, 192(%rdi)
        vmovntps        %ymm0, 224(%rdi)
        vmovntps        %ymm0, 256(%rdi)
        vmovntps        %ymm0, 288(%rdi)
        vmovntps        %ymm0, 320(%rdi)
        vmovntps        %ymm0, 352(%rdi)
        vmovntps        %ymm0, 384(%rdi)
        vmovntps        %ymm0, 416(%rdi)
        vmovntps        %ymm0, 448(%rdi)
        vmovntps        %ymm0, 480(%rdi)
        addq    $512, %rdi                      # imm = 0x200
        cmpq    %rax, %rdi
        jb      L48
L175:
        mfence                                  # Threads.atomic_fence()
        vzeroupper
        retq
        nopw    %cs:(%rax,%rax)

这款产品的带宽可达34GB/s,接近理论最大值38.4GB/s的90%。

然而,实际带宽似乎取决于多种因素,例如线程数量和频率缩放是否启用:

non-temporal load instruction benchmarks

在我的系统上,可以通过单个线程实现最大频率缩放(4 GHz "turbo" 而不是 1.8 GHz "no_turbo")来实现测量峰值性能,但是如果没有频率缩放(即在 1.8 GHz 下),甚至使用多个线程也无法实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接