Реализация приоритетной очереди Java - локальность памяти

Я пытаюсь реализовать эффективную очередь приоритетов в Java. Я получил хорошую реализацию двоичной кучи, но у нее нет идеальной производительности кеша. Для этого я начал изучать макет Ван Эмде Боаса в двоичной куче, что привело меня к «заблокированной» версии двоичной кучи, где хитрость заключается в вычислении дочерних и родительских индексов.

Хотя я смог это сделать, поведение кэша (и время работы) ухудшилось. Я думаю, что проблема в следующем: локальность ссылки, вероятно, не достигается, поскольку это Java. Я не уверен, действительно ли использование массива объектов делает объекты непрерывными в память в Java, кто-нибудь может подтвердить это, пожалуйста?

Также мне очень хотелось бы знать, какие структуры данных использует Java PriorityQueue, если кто-нибудь знает.


person nuno    schedule 04.04.2011    source источник
comment
Массив объектов — это массив ссылок на объекты. Объекты находятся в куче. Никакого населенного пункта, извините.   -  person Vladimir Dyuzhev    schedule 05.04.2011


Ответы (3)


В общем, нет хорошего способа заставить ваши объекты в очереди занимать непрерывный кусок памяти. Однако есть некоторые методы, которые подходят для особых случаев.

На высоком уровне методы включают использование байтовых массивов и «сериализацию» данных в массив и из него. На самом деле это довольно эффективно, если вы храните очень простые объекты. Например, если вы храните набор 2D-точек + веса, вы можете просто написать байтовый эквивалент веса, координаты x, координаты y.

Проблема на данный момент, конечно, заключается в выделении экземпляров при просмотре/выталкивании. Вы можете избежать этого, используя обратный вызов.

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

Возвращаясь к подходу к хранению простого неизменяемого типа значения, вот неполный набросок того, что вы можете сделать:

abstract class LowLevelPQ<T> {

  interface DataHandler<R, T> {
    R handle(byte[] source, int startLoc);
  }

  LowLevelPQ(int entryByteSize) { ... }
  abstract encode(T element, byte[] target, int startLoc);
  abstract T decode(byte[] source, int startLoc);
  abstract int compare(byte[] data, int startLoc1, int startLoc2);

  abstract <R> R peek(DataHandler<R, T> handler) { ... }
  abstract <R> R pop(DataHandler<R, T> handler) { ... }
}

class WeightedPoint {
  WeightedPoint(int weight, double x, double y) { ... }
  double weight() { ... }
  double x() { ... }
  ...
}

class WeightedPointPQ extends LowLevelPQ<WeightedPoint> {
  WeightedPointPQ() {
    super(4 + 8 + 8); // int,double,double
  }

  int compare(byte[] data, int startLoc1, int startLoc2) {
    // relies on Java's big endian-ness
    for (int i = 0; i < 4; ++i) {
      int v1 = 0xFF & (int) data[startLoc1];
      int v2 = 0xFF & (int) data[startLoc2];
      if (v1 < v2) { return -1; }
      if (v1 > v2) { return  1; }
    }
    return 0;
  }

  ...
}
person Dilum Ranatunga    schedule 05.04.2011
comment
Хотя я верю, что подобная версия будет вести себя хорошо с точки зрения кэш-памяти, она, безусловно, создаст много накладных расходов, когда я ищу что-то такое простое, как непрерывные элементы в памяти. В любом случае, спасибо за беспокойство =) - person nuno; 05.04.2011
comment
Да, этот подход работает, когда вы храните очень большое количество элементов, но получаете доступ к нескольким из них в любое время. - person Dilum Ranatunga; 06.04.2011
comment
Кстати, в какой структуре данных вы храните элементы в этой реализации? Массив байтов, ByteBuffer или что-то в этом роде? - person nuno; 06.04.2011
comment
Да, массив байтов. Это классический массив для алгоритма PQ, где element[0] находится вверху, а element[2i], element[2i + 1] имеют более низкий приоритет, чем element[i]. Единственная деталь — все индексации нужно умножать на entryByteSize. - person Dilum Ranatunga; 06.04.2011

Я не думаю, что это было бы. Помните, что «массивы объектов» — это не массивы объектов, это массивы ссылок на объекты (в отличие от массивов примитивов, которые на самом деле являются массивами примитивов). Я ожидаю, что ссылки на объекты будут непрерывными в памяти, но поскольку вы можете сделать эти ссылки ссылающимися на любые объекты, которые вы хотите, когда захотите, я сомневаюсь, что есть какие-либо гарантии, что объекты, на которые ссылается массив ссылок, будут непрерывными в памяти.

Что бы это ни стоило, в разделе JLS о массивах говорится ничего о каких-либо гарантиях смежности.

person QuantumMechanic    schedule 04.04.2011
comment
Нет ли способа заставить объекты быть смежными в структурах данных java? Я просматривал и нашел это stackoverflow.com/questions/4106978/. Я думаю, что попробую это, хотя я не слишком уверен, что он будет хорошо себя вести в контексте кучи. Спасибо - person nuno; 05.04.2011
comment
Только массив примитивов хранит их в одном и том же блоке кучи. Невозможно заставить дочерние объекты объекта размещаться по соседним адресам. - person Vladimir Dyuzhev; 05.04.2011
comment
Просто чтобы прояснить эту тему, теперь, когда мое исследование стало более ясным. Java не допускает смежности элементов массива, однако необходимо учитывать ряд соображений, таких как время выделения (весьма вероятно, что JVM/GC будет выделять непрерывно, если элементы созданы близко друг к другу), также если нам нужно углубиться, есть некоторые области оптимизации, которые следует учитывать на уровне JVM и реализациях сборки мусора (Jikes реализует сборщик мусора, оптимизирующий локальность, определяя горячие поля). - person nuno; 12.12.2011

Я думаю, что здесь происходит какой-то FUD. В принципе немыслимо, чтобы любая реализация массивов не использовала непрерывную память. И то, как этот термин используется в спецификации JVM при описании формата файла .class, ясно дает понять, что никакая другая реализация не рассматривается.

java.util.PriorityQueue использует двоичную кучу, как сказано в Javadoc, реализованную через массив.

person user207421    schedule 05.04.2011
comment
Итак, стоит ли пытаться реализовать версию PriorityQueue с поддержкой кеша/забвения (или любую структуру данных в этом отношении)? Я читал, что нативный java PQ использует двоичную кучу, но я не знал, что он основан на массиве. Однако, когда я профилирую свои версии PQ Java, Java имеет гораздо лучшее поведение кэша - я хотел бы знать, какие методологии они используют для этого. Я искал вокруг, но ничего не нашел. - person nuno; 05.04.2011
comment
Насколько я вижу, они не используют для этого никакой методологии. Они просто используют симпатичную ванильную реализацию двоичной кучи. Если вам нужны комментарии к вашему собственному коду, опубликуйте его. - person user207421; 05.04.2011