Стратегия кэширования для небольших неизменяемых объектов в Java?

Я разрабатываю приложение, которое создает большое количество небольших неизменяемых объектов Java. Примером может служить:

public class Point {
  final int x;
  final int y;
  final int z;
  .....
}

где вероятно, что многие экземпляры точки должны будут ссылаться на одно и то же (x,y,z) местоположение.

в какой степени имеет смысл пытаться кэшировать и повторно использовать такие объекты в течение всего срока службы приложения? Никаких особых хитростей в этой ситуации?

6 ответов


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

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

проблема со многими аппроксимациями пула объектов заключается в том, что они a) требуют ключевого объекта, который стоит столько же или больше, чем создание простого объекта, b) включают некоторую синхронизацию/блокировку, которая снова может стоить столько же, сколько создание объекта c) требует дополнительного объекта при добавлении в кэш (например, карта.Запись), то есть ваша скорость попадания должна быть намного лучше, чтобы кэш стоил того.

самая легкая, но тупая стратегия кэширования, которую я знаю, - использовать массив с хэш-кодом.

например

private static final int N_POINTS = 10191; // or some large prime.
private static final Point[] POINTS = new Point[N_POINTS];

public static Point of(int x, int y, int z) {
    int h = hash(x,y,z); // a simple hash function of x,y,z
    int index = (h & 0x7fffffff) % N_POINTS;
    Point p = POINTS[index];
    if (p != null && p.x == x && p.y == y && p.z == z)
       return p;
    return POINTS[index] = new Point(x,y,z);
}

Примечание: массив не является потокобезопасным, но с точки неизменна, это не имеет значения. Кэш работает на основе лучших усилий и, естественно, ограничен по размеру с очень простой стратегией выселения.

для целей тестирования можно добавить счетчики попадания/пропуска, чтобы определить эффективность кэшей для набора данных.


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

В любом случае, вы можете легко реализовать это с помощью PointFactory что вы звоните, чтобы получить Point, который всегда возвращает один и тот же экземпляр объекта для любого заданного x, y и z. Но тогда вам нужно управлять, когда точки должны быть удалены из кэша, потому что они не будут собирать мусор.

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

public class PointFactory{
    public static Point get(int x, int y, int z){
        return new Point(x, y, z);
    }
}

Это звучит почти как пример учебника Flyweight узор.


сколько экземпляров будут иметь одинаковые координаты, сколько будет существовать одновременно и сколько будет отброшено?

повторное использование объектов имеет преимущества, только если значительный процент живых объектов в одно время дублируются (по крайней мере, 20%, я бы сказал), и общее использование памяти проблематично. И если объекты часто отбрасываются, вы должны построить кэш таким образом, чтобы он не стал утечкой памяти (возможно, используя мягкие/слабые ссылки).


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


как в большинстве случаев: это зависит.

Если ваш объект довольно сложный (требует много времени для создания экземпляра), put может быть выражен в строке, имеет смысл создавать и загружать их с помощью статического Заводского метода.

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

Эл.г

private static final HashMap<String, Point> hash = new HashMap<String, Point>();

public static Point createPoint(int x, int y, int z) {
 String key = getKey(x,y,z);
 Point created = hash.get(key)
 if (created == null) {
  created = new Point(x,y,z);
  hash.put(key,created);
 }
 return created;
}

private static String createKey(int x, int y, int z) {
 StringBuffer buffer = new StringBuffer();
 buffer.append("x:");
 buffer.append(x);
 buffer.append("y:");
 buffer.append(y);
 buffer.append("z:");
 buffer.append(z);
 return buffer.toString()
}