Могу ли я размещать объекты непрерывно в Java? - PullRequest
6 голосов
/ 09 марта 2012

Предположим, у меня есть большой массив относительно небольших объектов, которые мне нужно часто повторять.
Я хотел бы оптимизировать свою итерацию за счет повышения производительности кэша, поэтому я хотел бы распределить объектов [а не ссылку] в памяти, так что у меня будет меньше пропусков кэша,общая производительность могла бы быть значительно лучше.

В C ++ я мог бы просто выделить массив объектов, и он будет распределять их так, как я хотел, но в Java - при выделении массива я только выделяю ссылкуи выделение выполняется по одному объекту за раз.

Я знаю, что если я выделю объекты "сразу" [один за другим], jvm будет , наиболее вероятно выделить объекты как можно более смежно, но этого может быть недостаточно, если память фрагментирована.

Мои вопросы:

  1. Есть ли способсказать jvm дефрагментировать память перед тем, как я начну распределять свои объекты?Будет ли этого достаточно для обеспечения [насколько это возможно] непрерывного распределения объектов?
  2. Есть ли другое решение этой проблемы?

Ответы [ 3 ]

11 голосов
/ 09 марта 2012

Новые объекты создаются в пространстве Эдема.Пространство Эдема никогда не фрагментируется.После GC оно всегда пустое.

Проблема, с которой вы сталкиваетесь, заключается в том, что при выполнении GC объект может быть размещен случайным образом в памяти или даже неожиданно в обратном порядке, на который они ссылаются.

AОбходной путь - хранить поля в виде серии массивов.Я называю это таблицей на основе столбцов вместо таблицы на основе строк.

Например, вместо записи

class PointCount {
    double x, y;
    int count;
}

PointCount[] pc = new lots of small objects.

используйте типы данных на основе столбцов.

class PointCounts {
    double[] xs, ys;
    int[] counts;
}

или

class PointCounts {
    TDoubleArrayList xs, ys;
    TIntArrayList counts;
}

Сами массивы могут находиться в трех разных местах, но в остальном данные всегда непрерывны.Это может быть даже несколько более эффективным, если вы выполняете операции над подмножеством полей.

public int totalCount() {
   int sum = 0;
   // counts are continuous without anything between the values.
   for(int i: counts) sum += i;
   return i;
}

Решение, которое я использую, состоит в том, чтобы избежать накладных расходов GC при наличии больших объемов данных, - это использовать интерфейс дляполучить доступ к прямому или отображенному в памяти ByteBuffer

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class MyCounters {
    public static void main(String... args) {
        Runtime rt = Runtime.getRuntime();
        long used1 = rt.totalMemory() - rt.freeMemory();
        long start = System.nanoTime();
        int length = 100 * 1000 * 1000;
        PointCount pc = new PointCountImpl(length);
        for (int i = 0; i < length; i++) {
            pc.index(i);
            pc.setX(i);
            pc.setY(-i);
            pc.setCount(1);
        }
        for (int i = 0; i < length; i++) {
            pc.index(i);
            if (pc.getX() != i) throw new AssertionError();
            if (pc.getY() != -i) throw new AssertionError();
            if (pc.getCount() != 1) throw new AssertionError();
        }
        long time = System.nanoTime() - start;
        long used2 = rt.totalMemory() - rt.freeMemory();
        System.out.printf("Creating an array of %,d used %,d bytes of heap and tool %.1f seconds to set and get%n",
                length, (used2 - used1), time / 1e9);
    }
}

interface PointCount {
    // set the index of the element referred to.
    public void index(int index);

    public double getX();

    public void setX(double x);

    public double getY();

    public void setY(double y);

    public int getCount();

    public void setCount(int count);

    public void incrementCount();
}

class PointCountImpl implements PointCount {
    static final int X_OFFSET = 0;
    static final int Y_OFFSET = X_OFFSET + 8;
    static final int COUNT_OFFSET = Y_OFFSET + 8;
    static final int LENGTH = COUNT_OFFSET + 4;

    final ByteBuffer buffer;
    int start = 0;

    PointCountImpl(int count) {
        this(ByteBuffer.allocateDirect(count * LENGTH).order(ByteOrder.nativeOrder()));
    }

    PointCountImpl(ByteBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void index(int index) {
        start = index * LENGTH;
    }

    @Override
    public double getX() {
        return buffer.getDouble(start + X_OFFSET);
    }

    @Override
    public void setX(double x) {
        buffer.putDouble(start + X_OFFSET, x);
    }

    @Override
    public double getY() {
        return buffer.getDouble(start + Y_OFFSET);
    }

    @Override
    public void setY(double y) {
        buffer.putDouble(start + Y_OFFSET, y);
    }

    @Override
    public int getCount() {
        return buffer.getInt(start + COUNT_OFFSET);
    }

    @Override
    public void setCount(int count) {
        buffer.putInt(start + COUNT_OFFSET, count);
    }

    @Override
    public void incrementCount() {
        setCount(getCount() + 1);
    }
}

, запущенному с опцией -XX:-UseTLAB (для получения точных размеров выделения памяти), печатает

Создание массива из 100 000 000 используемых 12 512 байткуча и потребовалось 1,8 секунды, чтобы установить и получить

В качестве своей кучи, он не имеет никакого влияния GC.

0 голосов
/ 09 марта 2012

К сожалению, в Java нет никакого способа гарантировать, что объекты создаются / остаются в смежных областях памяти.

Однако, объекты, созданные в последовательности, скорее всего, окажутся смежными друг с другом (конечно, это зависит отфактическая реализация ВМ).Я почти уверен, что создатели виртуальной машины знают, что локальность крайне желательна, и не стараются изо всех сил разбрасывать объекты вокруг.

Сборщик мусора в какой-то момент, вероятно, переместит объекты- если ваши объекты недолговечны, это не должно быть проблемой.Для долгоживущих объектов это зависит от того, как GC реализует перемещение оставшихся в живых объектов.Опять же, я думаю, что разумно, что ребята, пишущие GC, потратили некоторое время на размышления по этому вопросу и будут выполнять копии таким образом, чтобы не портить местность более чем неизбежно.

Очевидно, нет никаких гарантий для любого из вышеперечисленныхпредположения, но так как мы ничего не можем с этим поделать, перестаньте беспокоиться:)

Единственное, что вы можете сделать на уровне исходного кода Java, это иногда избегать компоновки объектов - вместо этого вы можете "встроить"состояние, которое вы обычно помещаете в составной объект:

class MyThing {
    int myVar;
    // ... more members

    // composite object
    Rectangle bounds;
}

вместо:

class MyThing {
    int myVar;
    // ... more members

    // "inlined" rectangle
    int x, y, width, height;
}

Конечно, это делает код менее читаемым и потенциально дублирует большой объем кода.

Упорядочивание членов класса по шаблону доступа кажется незначительным (я заметил небольшое изменение в тестируемом фрагменте кода после того, как я переупорядочил некоторые объявления), но я никогда не удосужился проверить,правда.Но было бы разумно, если бы виртуальная машина не переупорядочивала элементы.

По той же теме было бы также неплохо (с точки зрения производительности) переосмыслить существующий массив примитивов как другой тип (например, приведениеint [], чтобы плавать []).И пока вы занимаетесь этим, почему бы не пожелать и членам профсоюза?Я уверен, что да.Но в обмен на эти возможности нам пришлось бы отказаться от значительной части независимости от платформы и архитектуры.

0 голосов
/ 09 марта 2012

Не работает в Java. Итерация не заключается в увеличении указателя. Это не влияет на производительность в зависимости от того, где в куче физически хранятся объекты.

Если вы все еще хотите подойти к этому на C / C ++, подумайте о массиве Java как о массиве указателей на структуры. Когда вы перебираете массив, не имеет значения, где размещены фактические структуры, вы перебираете массив указателей.

Я бы отказался от этой линии рассуждений. Это не то, как работает Java, а также субоптимизация.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...