Подтвердить что ты не робот

Программа, превышающая скорость передачи теоретической памяти

У меня есть ноутбук с процессором Intel Core 2 Duo 2,4 ГГц и 2DRGb DDR3 модулями 1066 МГц.

Я ожидаю, что эта память может работать со скоростью 1067 Мбайт/сек, и пока есть два канала, максимальная скорость 2134 Мбайт/с (в случае, если диспетчер памяти ОС позволит).

Я сделал крошечное 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/sec, но у меня есть следующее:

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

Как возможно, что скорость была почти 3 гигабайта/сек?

DDR3 module photo

4b9b3361

Ответ 1

Вот несколько вещей на работе.

Прежде всего: формула для скорости передачи памяти 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/s или 8138.02083333... MiB/s для одноканального, и 17066⅔ MB/s или 16276.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(...). Этот алгоритм все еще может иметь проблемы с кэшированием.

Для моей системы (Dual Channel DDR3-1600) я получаю около 6 GiB/s, тогда как теоретический предел составляет около 25 GiB/s (включая DualChannel).

Как указывает MagicM18, JVM вносит некоторые накладные расходы. Поэтому ожидается, что вы не сможете достичь теоретического предела.


1 Sidenote: для запуска программы необходимо предоставить JVM больше пространства кучи.В моем случае достаточно 4096 МБ.

Ответ 2

Ваш метод тестирования плохо разработан во многих аспектах, а также ваша интерпретация рейтинга ОЗУ.

Начните с рейтинга; с момента внедрения SDRam маркетинг называет модули после их спецификации шины, то есть тактовой частоты шины, в сочетании с скоростью передачи пакета. Это лучший случай, и на практике он не может постоянно поддерживаться.

Параметры, опущенные этой меткой, - это фактическое время доступа (так называемая латентность) и общее время цикла (например, время предварительной зарядки). Их можно понять, фактически посмотрев на "временные" спецификации (2-3-3 вещи). Посмотрите статью, в которой подробно объясняется это. На самом деле CPU обычно не передает одиночные байты, а целые строки кэша (например, 8 записей на 8 байтов = 64 байта).

Ваш тестовый код плохо разработан, так как вы делаете произвольный доступ с относительно маленьким блоком, не привязанным к фактическим границам данных. Этот случайный доступ также вызывает частые пропуски страниц в MMU (узнайте, что делает TLB). Таким образом, вы измеряете дикую смесь различных системных аспектов.

Ответ 3

В Википедии есть таблица тарифов . Этот конкретный ноутбук имеет следующие характеристики:

  • Тип модуля: PC3-8500 DDR3 SDRAM
  • Тип чипа: DDR3-1066
  • Часы памяти: 133 MHz
  • Скорость шины: 1.066GT/s
  • Скорость передачи (бит/с): 64 Gbit/s
  • Скорость передачи (десятичные байты/с): 8 GB/s

Это один модуль DDR3 на один канал.

Ответ 4

Это может быть вопрос конфигурации оборудования. На основе предоставленной информации есть два ядра и два модуля памяти, но количество каналов памяти неясно. Хотя я никогда не видел тестирования, выполненного в масштабе ноутбука, в более крупных системах конфигурация модулей DIMM в каналах памяти может существенно повлиять на скорость передачи данных.

Например, на современных серверах возможно наличие конфигурации одного DIMM на канал (ODPC) или двух модулей DIMM на канал (TDPC). Каждый физический ЦП может иметь несколько каналов памяти, разделенных между физическими ядрами на упомянутом ЦП, и каждый сервер может потенциально иметь несколько физических ЦП (как правило, 2-4 на современных серверах).

Как распределяется память между этими каналами, ядра и процессоры/чипы могут существенно влиять на производительность памяти в зависимости от того, что измеряется. Например, системы с конфигурацией ODPC значительно улучшат время передачи (с точки зрения передачи в секунду или MegaTransfers в секунду, MT/s) по сравнению с системами, которые имеют конфигурацию TDPC в случаях, когда объем памяти (в ГБ) в система TDPC равна или превышает объем памяти в конфигурации ODPC.

Основываясь на этих знаниях, можно предположить, что ноутбук, который настроен с двумя каналами памяти в ODPC и один канал на один сердечник, теоретически может достичь описанной производительности.

При всем том, что имеется ряд предварительно упакованных профилей памяти и анализа, которые можно запускать неинвазивно, чтобы получить информацию о производительности памяти в вашей системе. Memtest - очень мощный, хорошо понятный и хорошо документированный инструмент для тестирования памяти. Его можно загрузить на загрузочный диск (USB, DVD, флоппи-дисковод и т.д.), Который можно безопасно использовать, чтобы подчеркнуть память в системе без возможности повредить или нарушить работу ОС. Он также включен на установочный DVD для некоторых дистрибутивов Linux, а также для спасения DVD/изображений. Это очень мощный инструмент, который я использовал во многих случаях для отладки и анализа производительности памяти, хотя обычно на серверах.