【问题标题】:Program exceeding theoretical memory transfer rate程序超过理论内存传输率
【发布时间】:2015-09-21 15:53:45
【问题描述】:

我有一台配备 Intel Core 2 Duo 2.4GHz CPU 和 2x4Gb DDR3 模块 1066MHz 的笔记本电脑。

我希望这个内存可以以 1067 MiB/sec 的速度运行,只要有两个通道,最大速度就是 2134 MiB/sec(如果操作系统内存调度程序允许) .

我制作了一个小型 Java 应用程序来测试:

private static final int size = 256 * 1024 * 1024; // 256 Mb
private static final byte[] storage = new byte[size];

private static final int s = 1024; // 1Kb
private static final int duration = 10; // 10sec

public static void main(String[] args) {
    long start = System.currentTimeMillis();
    Random rnd = new Random();
    byte[] buf1 = new byte[s];
    rnd.nextBytes(buf1);
    long count = 0;
    while (System.currentTimeMillis() - start < duration * 1000) {
        long begin = (long) (rnd.nextDouble() * (size - s));
        System.arraycopy(buf1, 0, storage, (int) begin, s);
        ++count;
    }
    double totalSeconds = (System.currentTimeMillis() - start) / 1000.0;
    double speed = count * s / totalSeconds / 1024 / 1024;
    System.out.println(count * s + " bytes transferred in " + totalSeconds + " secs (" + speed + " MiB/sec)");

    byte[] buf2 = new byte[s];
    count = 0;
    start = System.currentTimeMillis();
    while (System.currentTimeMillis() - start < duration * 1000) {
        long begin = (long) (rnd.nextDouble() * (size - s));
        System.arraycopy(storage, (int) begin, buf2, 0, s);
        Arrays.fill(buf2, (byte) 0);
        ++count;
    }
    totalSeconds = (System.currentTimeMillis() - start) / 1000.0;
    speed = count * s / totalSeconds / 1024 / 1024;
    System.out.println(count * s + " bytes transferred in " + totalSeconds + " secs (" + speed + " MiB/sec)");
}

我预计结果会低于 2134 MiB/秒,但我得到了以下结果:

17530212352 bytes transferred in 10.0 secs (1671.811328125 MiB/sec)
31237926912 bytes transferred in 10.0 secs (2979.080859375 MiB/sec)

速度接近 3 GiB/秒怎么可能?

【问题讨论】:

  • 你忘记了 CPU 缓存。有 l1、l2 甚至 l3 缓存......仅仅因为你在随机浏览并不意味着你不会偶尔在缓存中获得命中。
  • @MarcB 是的。这就是我创建 256MiB 缓冲存储的原因。
  • DDRx 内存通常也是 64 位宽。仅仅因为它以 1066mhz 运行并不意味着它是 1byte/hz 传输速率......
  • 您对DDR频率的理解从根本上是错误的。看看en.wikipedia.org/wiki/DDR_SDRAM。一般来说,您的人数很少。
  • 对于初学者来说,除了执行复制之外,您还要执行代码,这会使您的测量从一开始就不准确。

标签: java performance memory hardware benchmarking


【解决方案1】:

这里有很多事情在起作用。

首先:formula for memory transfer rate of DDR3

memory clock rate
× 4  (for bus clock multiplier)
× 2  (for data rate)
× 64 (number of bits transferred)
/ 8  (number of bits/byte)
=    memory clock rate × 64 (in MB/s)

对于 DDR3-1066(主频为 133⅓ MHz),我们获得理论内存带宽8533⅓ MB/s8138.02083333... MiB/s 用于单通道,17066⅔ MB/s16276.0416666... MiB/s 用于双通道。

第二:传输一大块数据比传输许多小块数据要快。

第三:测试忽略了可能发生的缓存效果。

第四:如果要进行时间测量,则应使用System.nanoTime()。这种方法更精确。

这是测试程序的重写版本1

import java.util.Random;

public class Main {

  public static void main(String... args) {
    final int SIZE = 1024 * 1024 * 1024;
    final int RUNS = 8;
    final int THREADS = 8;
    final int TSIZE = SIZE / THREADS;
    assert (TSIZE * THREADS == THREADS) : "TSIZE must divide SIZE!";
    byte[] src = new byte[SIZE];
    byte[] dest = new byte[SIZE];
    Random r = new Random();
    long timeNano = 0;

    Thread[] threads = new Thread[THREADS];
    for (int i = 0; i < RUNS; ++i) {
      System.out.print("Initializing src... ");
      for (int idx = 0; idx < SIZE; ++idx) {
        src[idx] = ((byte) r.nextInt(256));
      }
      System.out.println("done!");
      System.out.print("Starting test... ");
      for (int idx = 0; idx < THREADS; ++idx) {
        final int from = TSIZE * idx;
        threads[idx]
            = new Thread(() -> {
          System.arraycopy(src, from, dest, 0, TSIZE);
        });
      }
      long start = System.nanoTime();
      for (int idx = 0; idx < THREADS; ++idx) {
        threads[idx].start();
      }
      for (int idx = 0; idx < THREADS; ++idx) {
        try {
          threads[idx].join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      timeNano += System.nanoTime() - start;
      System.out.println("done!");
    }
    double timeSecs = timeNano / 1_000_000_000d;

    System.out.println("Transfered " + (long) SIZE * RUNS
        + " bytes in " + timeSecs + " seconds.");

    System.out.println("-> "
        + ((long) SIZE * RUNS / timeSecs / 1024 / 1024 / 1024)
        + " GiB/s");
  }
}

通过这种方式,尽可能多地减轻“其他计算”,并且(几乎)只测量通过System.arraycopy(...) 的内存复制率。该算法在缓存方面可能仍然存在问题。

对于我的系统(双通道 DDR3-1600),我得到的东西大约是 6 GiB/s,而理论上的限制大约是 25 GiB/s(包括双通道)。

As was pointed out by Nick Mertin,JVM 引入了一些开销。因此,预计您无法达到理论极限。


1 旁注:要运行程序,必须给 JVM 更多的堆空间。就我而言,4096 MB 就足够了。

【讨论】:

  • 我试过这个测试。不得不将 SIZE 减小到 256Mb。得到:Transfered 2147483648 bytes in 1.42 seconds. -&gt; 1.41 GiB/s
  • @Antonio 为什么你减少了SIZE?为了更接近理论极限,块应该尽可能大。给 JVM 更多的堆空间 (java -Xmx4096m ...) 比减小块大小更好。
  • 我将存储大小设置为 1Gb,持续时间设置为 30 秒。结果几乎相同 - 1.42GiB/秒。 CPU 不是瓶颈(被 60% 使用)。
  • @Antonio 您的理论限制(包括双通道)约为 16 GiB/s,因此没有超过理论限制。请记住,我的基准测试必须将所有内容传输两次(从内存到 CPU 和从 CPU 传回内存)。所以我的程序实际上传输 2.84 GiB/s。但是,您的基准测试可能会从缓存中获得大量收益(您的源数组大小只有 1KB,因此可能会被完全缓存)。所以基本上这两个基准测试都表现出相同的性能。
  • @Turing85 重要的是要记住,因为它在 JVM 中运行,而 JVM 又在 OS 上运行,所以存在与 Java 代码本身无关的额外 CPU 开销。此外,操作系统可能会阻止单个进程使用大量 CPU,因此即使它只使用了 60%,CPU 仍然可能是瓶颈。
【解决方案2】:

您的测试方法在许多方面设计不当,以及您对 RAM 评级的解释。

让我们从评分开始;自从 SDRam 推出以来,市场营销人员根据其总线规范命名模块 - 即总线时钟频率,与突发传输率配对。这是最好的情况,在实践中它不能持续。

该标签省略的参数是实际访问时间(又名延迟)和总周期时间(又名预充电时间)。这些可以通过实际查看“时序”规范(2-3-3 的东西)来弄清楚。查找详细解释这些内容的文章。实际上,CPU 通常不会传输单个字节,而是传输整个高速缓存行(例如,每 8 个字节 8 个条目 = 64 个字节)。

您的测试代码设计不当,因为您正在使用与实际数据边界未对齐的相对较小的块进行随机访问。这种随机访问还会在 MMU 中导致频繁的页面丢失(了解 TLB 是/做什么)。因此,您正在测量不同系统方面的狂野混合物。

【讨论】:

  • 实际上,当所需的内存块不在缓存中时,我的目标是测试速度。让我知道时间是什么,我会回来的。谢谢你的回答。
  • 我添加了一张 DDR 模块的照片。据我所知,没有时间安排。
  • 经过一番谷歌搜索后,我发现 CL=7。那么如何计算限制呢?
  • @Antonio 从对您问题的评论中提到的 wiki 条目开始,CL 代表 Column Access Latency,这只是其中一个参数。模块“知道”它们的时序,即主板的 BIOS 读取参数并调整其时序以匹配 RAM。有一些工具可以显示这些值。对于如何从时序中准确计算有用的“公式”,请认真从 wiki 开始,也许可以补充 DRAM 访问的基础知识。它是一个相对复杂和广泛的话题。我自己并不了解每一个细节。
【解决方案3】:

在维基百科中有一个table of transfer rates。这台特定的笔记本电脑具有以下规格:

  • 模块类型:PC3-8500 DDR3 SDRAM
  • 芯片类型:DDR3-1066
  • 内存时钟:133MHz
  • 总线速度:1.066GT/s
  • 传输速率(比特/秒):64Gbit/s
  • 传输速率(十进制字节/秒):8GB/s

这是每个单个通道的单个 DDR3 模块。

【讨论】:

    【解决方案4】:

    这可能是硬件配置问题。根据提供的信息,有两个内核和两个内存模块,但内存通道的数量尚不清楚。虽然我从未见过在笔记本电脑的规模上进行过测试,但在较大的系统上,内存通道中的 DIMM 配置会对内存传输速率产生重大影响。

    例如,在现代服务器上,可以有每通道一个 DIMM (ODPC) 或每通道两个 DIMM (TDPC) 内存配置。每个物理 CPU 可以在所述 CPU 上的物理内核之间分配多个内存通道,并且每个服务器可能具有多个物理 CPU(在现代服务器中通常为 2-4 个)。

    内存在这些通道、内核和 CPU/芯片之间的分配方式会对内存性能产生重大影响,具体取决于所测量的内容。例如,与具有 TDPC 配置的系统相比,在内存量(以 GB 为单位)为TDPC 系统等于或大于 ODPC 配置中的内存量。

    基于这些知识,可以想象一台笔记本电脑在 ODPC 中设置了 2 个内存通道并且每个内核方式具有一个通道,理论上可以达到所描述的性能。

    综上所述,有许多预打包的内存分析和分析工具可以以非侵入方式运行,以获取有关系统内存性能的信息。 Memtest 是一个非常强大、易于理解且记录良好的内存测试工具。它可以下载到某种类型的可引导磁盘(USB、DVD、软盘等)上,可以安全地用于对系统内存施加压力,而不会损坏或干扰操作系统。它还包含在某些 Linux 发行版的安装 DVD 以及救援 DVD/图像中。这是一个非常强大的工具,我在很多场合都使用过它来调试和分析内存的性能,虽然通常是在服务器上。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-08
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多