Эффективный код для проверки смещения в координатах пикселей

#java #performance #math

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

Вопрос:

Я создаю метод рендеринга на основе плиток для игры, используя Java.

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

Я придумал это (для проверки максимальных координат X разбитых плиток):

 int brokenX = xOffset; //xOffset can be any number
while (brokenX < 0){
    brokenX  = tileSet.getTileSize(); //corrects if the number is negative
}
while (brokenX >= tileSet.getTileSize()){
    brokenX -= tileSet.getTileSize(); //corrects if the number is too big
}
 

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

Есть ли какой-нибудь эффективный способ (битовые маски и тому подобное) сделать это?

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

1. Когда вы заметили, что ваша частота кадров была слишком низкой, и профилировали свое приложение, является ли этот фрагмент кода тем, что вы нашли узкое место?

2. Нет, я не профилировал. Я просто проверил время до и после render() метода. Это была только первая его часть.

Ответ №1:

Я думаю, вы ищете

 brokenX = xOffset % getTileSize();
 

Я думаю, что вышесказанное должно работать и для отрицательного значения, но если xOffset может быть больше, то вы могли бы сделать

хорошо… хорошо. Я думаю, что простое добавление чека в последнюю строку должно это исправить.

 hiddenTiles = xOffset /  getTileSize();
xOffset -= hiddenTiles * getTileSize();
if(xOffset < 0) {
    brokenX=  -(-xOffset % getTileSize());
}
else {
    brokenX = xOffset % getTileSize();
}
 

Или как предполагает Джейсон:
Только одна строчка…

 brokenX=  (xOffset   getTileSize())% getTileSize());
 

Я действительно думаю, что это должно сработать и для отрицательных смещений. Так что никаких изменений не требуется.

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

1. Это был мой первый выбор, но xOffset может быть отрицательным или превышать tileSize

2. Я попробовал ваше решение. Время рендеринга уменьшается со 118 до 86 мс. Но это работает только для положительных значений.

3. хорошо .. хорошо. Я думаю, что простое добавление проверки в последней строке должно это исправить. Скрытые значения = xOffset / getTileSize(); xOffset -= Скрытые значения * getTileSize(); если (brokenX < 0) { brokenX= xOffset % getTileSize(); }

4. Если вы знаете xOffset > -tileSize , что, по-видимому, подразумевает @MarlonDias, вы можете просто обработать оба случая brokenX = (xOffset tileSize) % tileSize; . Классический вариант, когда это условие не выполняется, но первое значение по модулю не обязательно, если . brokenX = ((xOffset % tileSize) tileSize) % tileSize; xOffset > -tileSize

5. @SahilBajaj Я уже опубликовал окончательный код. brokenX = (xOffset tileSize) % tileSize; . Вот и все. Никаких операторов if или чего-либо еще.

Ответ №2:

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

 int result = x % m;
return (result >= 0) ? result : result   m;
 

Пример должен прояснить это. Я пытаюсь использовать числа 13, 3, -7, -17 по модулю 10 , которые все должны возвращать один и тот же результат.

  • 13 % 10 = 3 все в порядке
  • 3 % 10 = 3 , все в порядке
  • -7 % 10 = -7 отрицательный и непригодный для использования в качестве индекса, но с -7 % 10 10 = 3 ним все в порядке.
  • -17 % 10 10 = 3 тоже хорошо.

Итак, для result < 0 или, что эквивалентно m < 0 , вам нужно добавить m , чтобы получить положительный результат.

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

1. Я не понимаю. Не могли бы вы объяснить, что делают эти выражения, пожалуйста?

2. Теперь я понял! Это просто и сокращает время рендеринга на 60000нс. Спасибо 🙂

Ответ №3:

Ниже приведен распространенный способ преодоления негативной проблемы по модулю без условного:

 int tileSize = tileSet.getTileSize();
int brokenX = ((xOffset % tileSize)   tileSize) % tileSize;
int brokenY = ((yOffset % tileSize)   tileSize) % tileSize;
 

Это работает с тех пор, как:

  1. Первый модуль гарантирует, что число меньше размера плитки
  2. Добавление гарантирует, что число является положительным, так как оно было меньше, чем tileSize, и было добавлено tileSize
  3. Второе значение по модулю гарантирует, что число снова положительное и меньше размера плитки

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

1. Конечно, вызов % дважды — не самый быстрый способ. Оптимальное выражение имеет примерно такую же длину.

2. @maaartinus Предполагая, что вы говорите о том, что написали в своем ответе, он все еще длиннее и, что более важно, менее оптимален с точки зрения использования процессора. JVM обрабатывает по модулю гораздо эффективнее, чем ветвление, вводимое условными обозначениями.

3. Я в этом сильно сомневаюсь. Довольно часто ветвь предсказуема (так как вы часто защищаетесь от отрицательных значений, но на самом деле не ожидаете их) и поэтому почти бесплатна. В противном случае можно использовать условный ход. Обратите внимание, что ребята из Guava довольно сильно оптимизируют. В любом случае, хороший момент, кто-то должен написать бенчмарк (Caliper или JMH), чтобы разрешить спор.

4. На самом деле это довольно быстро. Однако вы можете оптимизировать его немного дальше. Если вы знаете xOffset , что никогда не будет <= -tileSize (что подразумевает описание OP), вы можете сделать int brokenX = (xOffset tileSize) % tileSize; и исключить первое значение по модулю (единственная причина, по которой первое значение по модулю должно существовать в первую очередь, — это поместить операнд в диапазон [-tileSize, tileSize], exclusive). То же самое для Y.

5. @JasonC Вы, конечно, правы, но я не делал такого предположения, поскольку в прошлом я слишком часто видел, как это оказывалось ошибкой, чтобы воспользоваться этим шансом. Тем не менее, хороший момент.

Ответ №4:

Если размер плитки равен степени двойки, вы могли бы сделать

 brokenX = xOffset amp; (tileSize - 1);
 

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

1. На данный момент моему tileSize 32, но я планирую повторно использовать метод рендеринга для других размеров. Будет ли этот код работать, если я попробую tileSize = 48 ?

2. Нет, tileSize должно быть степенью двойки (…, 32, 64, 128, 256, …)

Ответ №5:

 if (brokenX < 0)
    brokenX = tileSizeX - (Math.abs(brokenX) % tileSizeX);