Вычисление объектов без генерации слишком большого количества мусора

#java #performance #garbage-collection

#java #Производительность #сборка мусора

Вопрос:

На данный момент я разрабатываю игру для Android. Для этого все мои объекты имеют Vec3 позицию. Это выдержка из класса:

 public class Vec3 {
    public float x, y, z;

    public Vec3(float x, float y, float z) {
        this.x = x; this.y = y; this.z = z;
    }

    public Vec3 add(Vec3 v) {
        return new Vec3(this.x   v.x, this.y - v.y, this.z   v.z);
    }

    public Vec3 sub(Vec3 v) {
        return new Vec3(this.x - v.x, this.y - v.y, this.z - v.z);
    }
}
  

Теперь предположим, что я хочу запустить этот код, где a , b и c являются векторами из Vec3 класса:

 a.add(b).sub(c);
  

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

Другая проблема заключается в том, что я генерирую много векторных объектов методом add и sub. Я хочу вычислять векторы, не изменяя их самостоятельно, вот почему я каждый раз возвращаю новый Vec3 . Допустим, я запускаю этот код:

 a.add(b).add(b).add(b).add(b).add(b).sub(c);
  

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

Есть ли у вас какие-либо идеи, как я могу эффективно решить эти проблемы?

Комментарии:

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

2. Да, я уверен, что эти проблемы возникают из-за накладных расходов на сборку мусора.

Ответ №1:

Вы можете писать методы на месте add и sub :

 public Vec3 addInPlace(Vec3 v) {
    x  = v.x;
    y  = v.y;
    z  = v.z;
    return this;
}

// and similarly for subInPlace
  

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

 temp = a.add(b);
temp.addInPlace(b).addInPlace(b).addInPlace(b).addInPlace(b).subInPlace(c);
  

Вы должны управлять повторным использованием объекта явно, и вы теряете неизменяемость Vec3 объектов. Обе эти вещи отстой.

Ответ №2:

В вашем более сложном примере

 a.add(b).add(b).add(b).add(b).add(b).sub(c);
  

вы можете устранить большую часть мусора, используя более мощную операцию

 a.mulAdd(4, b, -1, c);
  

Я бы боялся времени умножения, но не сильно. Вы также могли бы написать специализированные операции, такие как

 a.addFourSubOne(b, c);
  

что звучит довольно идиотски, поскольку вы никогда не сможете реализовать все операции, которые вы можете возможно использовать. OTOH вы можете легко реализовать все операции, которые вы действительно используете.


Я бы, вероятно, рекомендовал использовать изменяемые объекты. Вы можете предоставить для них операции как изменения, так и копирования, называемые чем-то вроде add / addInPlace или plus / add . Вы также могли бы реализовать как изменяемые, так и неизменяемые версии (это скоро надоедает, но вы можете справиться с этим, не внедряя ничего, что вам на самом деле не нужно).