bigywater.tech
Java · 395

对象分配预取:内存带宽如何影响分配速度

“对象分配很便宜”背后是 TLAB 加预取。这篇用同一台机器量出:默认的预取参数是什么、改参数到底有没有稳定的效果、对象大小怎么改变每次分配的成本,以及多线程分配时的总带宽——并如实标出噪声有多大。

2 ns
16–64 字节的 byte[] 单次分配的耗时(1 个线程)
-23% / +40%
AllocatePrefetchDistance=512 对 64 字节 / 1024 字节分配速度的影响
44 → 33.6 GB/s
256 字节对象:1 线程与 10 线程的总分配带宽(含年轻代回收)

默认参数

实测 · 本机执行输出
  AllocatePrefetchStyle = 1
  AllocatePrefetchDistance = 384
  AllocatePrefetchLines = 3
  AllocatePrefetchStepSize = 128
  AllocatePrefetchInstr = 0
  AllocateInstancePrefetchLines = 1
  TLABSize = 0
  UseTLAB = true
  ZeroTLAB = false

预取 style

实测 · 本机执行输出
  AllocatePrefetchStyle=默认:64 字节:28.36 GB/s(三次 24.86–28.93)、2.3 ns/次;  1024 字节:7.75 GB/s(三次 7.53–7.97)、132.2 ns/次
  AllocatePrefetchStyle=0:64 字节:30.25 GB/s(三次 29.53–30.66)、2.1 ns/次;  1024 字节:7.49 GB/s(三次 7.06–7.80)、136.8 ns/次
  AllocatePrefetchStyle=1:64 字节:26.20 GB/s(三次 23.68–28.57)、2.4 ns/次;  1024 字节:7.60 GB/s(三次 7.56–7.99)、134.8 ns/次
  AllocatePrefetchStyle=2:64 字节:29.38 GB/s(三次 24.55–29.55)、2.2 ns/次;  1024 字节:8.12 GB/s(三次 7.71–8.32)、126.1 ns/次
  AllocatePrefetchStyle=3:64 字节:29.09 GB/s(三次 28.88–29.12)、2.2 ns/次;  1024 字节:8.00 GB/s(三次 7.76–8.22)、128.0 ns/次

预取距离

实测 · 本机执行输出
  AllocatePrefetchDistance=默认:64 字节:28.56 GB/s(三次 28.42–28.56);  1024 字节:8.02 GB/s(三次 7.57–8.16)
  AllocatePrefetchDistance=0:64 字节:28.71 GB/s(三次 28.65–28.94);  1024 字节:7.61 GB/s(三次 7.37–7.92)
  AllocatePrefetchDistance=64:64 字节:28.57 GB/s(三次 28.53–28.59);  1024 字节:8.97 GB/s(三次 8.87–9.18)
  AllocatePrefetchDistance=128:64 字节:28.39 GB/s(三次 27.35–28.40);  1024 字节:7.19 GB/s(三次 7.18–7.92)
  AllocatePrefetchDistance=256:64 字节:28.56 GB/s(三次 23.86–28.66);  1024 字节:8.00 GB/s(三次 7.45–8.39)
  AllocatePrefetchDistance=384:64 字节:28.56 GB/s(三次 26.69–28.60);  1024 字节:8.37 GB/s(三次 7.96–9.29)
  AllocatePrefetchDistance=512:64 字节:21.86 GB/s(三次 21.66–23.26);  1024 字节:11.20 GB/s(三次 10.44–11.56)
  AllocatePrefetchDistance=1024:AllocatePrefetchDistance (1024) must be between 0 and 512 Error: Could not create the Java Virtual Machine.

对象大小

实测 · 本机执行输出
     16 字节:每次分配 2.4 ns,6.63 GB/s
     32 字节:每次分配 2.1 ns,15.02 GB/s
     64 字节:每次分配 2.0 ns,31.79 GB/s
    128 字节:每次分配 3.1 ns,41.95 GB/s
    256 字节:每次分配 5.6 ns,45.86 GB/s
   1024 字节:每次分配 40.2 ns,25.47 GB/s
   4096 字节:每次分配 74.4 ns,55.06 GB/s
  16384 字节:每次分配 148.5 ns,110.37 GB/s

线程数

实测 · 本机执行输出
  256 字节 × 1 线程:总计 44.00 GB/s,每线程 44.00 GB/s,每次分配约 5.8 ns(按线程)
  256 字节 × 2 线程:总计 30.01 GB/s,每线程 15.00 GB/s,每次分配约 17.1 ns(按线程)
  256 字节 × 4 线程:总计 62.60 GB/s,每线程 15.65 GB/s,每次分配约 16.4 ns(按线程)
  256 字节 × 8 线程:总计 32.80 GB/s,每线程 4.10 GB/s,每次分配约 62.4 ns(按线程)
  256 字节 × 10 线程:总计 33.61 GB/s,每线程 3.36 GB/s,每次分配约 76.2 ns(按线程)

分步演示

对象分配预取
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;ParallelGC,-Xms3g -Xmx3g -Xmn2g;byte[] 的头部 16 字节计入大小;GB/s 用 ThreadMXBean.getThreadAllocatedBytes 与 nanoTime 计算;测的是“分配 + 年轻代回收”
    • 没有验证:x86_64 机器上这些参数的行为(这里是 Apple Silicon)、G1 / ZGC 下的同样测量、AllocatePrefetchLines / StepSize / Instr 的单独影响、1024 字节带宽低于 256 字节的原因、多线程结果的重复性(每个线程数只跑一次)、真实应用的分配模式
    京ICP备2026067502号-1京公网安备11010502063487号