Выполняйте работу в параллельном режиме
Потоки не обязательно являются единственным способом распараллеливания кода, в процессорах часто есть наборы инструкций, такие как SIMD, которые позволяют вам вычислять одну и ту же арифметику для нескольких чисел одновременно.Графические процессоры берут эту идею и работают с ней, позволяя вам запускать одну и ту же функцию параллельно от сотен до тысяч номеров.Я не знаю, как сделать это в Java, но я уверен, что с помощью некоторого поиска в Google можно найти метод, который работает.
Алгоритм - Делать меньше работы
Возможно лиуменьшить количество времени, которое должна вызываться функция?Вызов любой функции миллион раз за кадр будет больно.Если издержки каждого вызова функции не управляются (вставка, повторное использование стекового фрейма, кэширование результата, если это возможно), вы захотите выполнять меньше работы.
Возможные варианты:
- Уменьшите окно / разрешение игры.
- Работайте с другим представлением.Вы выполняете много операций, которые легче выполнять, когда пиксели - это HSV вместо RGB?Затем конвертируйте в RGB только тогда, когда вы собираетесь визуализировать пиксель.
- Используйте ограниченное количество цветов для каждого пикселя.Таким образом, вы можете заранее определить возможные оттенки, и это всего лишь поиск, в отличие от вызова функции.
- Оттенки как можно меньше.Может быть, есть какой-то пользовательский интерфейс, который тонирован и не должен быть.Возможно световые эффекты только путешествуют.
- В крайнем случае, сделайте тонированное по умолчанию.Если тонирование пикселей выполняется так много, то, возможно, «отрисовка» происходит гораздо реже, и вы можете добиться более высокой производительности, делая это.
Производительность - (микро-) оптимизация кода
Если вы можете согласиться на «приблизительный оттенок» , этот ответ SO дает приближение для яркости (яркости) пикселя, который должен быть дешевле для вычисления.(Формула из ссылки: Y = 0,33 R + 0,5 G + 0,16 B, что можно записать как Y = (R + R + B + G + G + G) / 6.)
Следующий шагэто измерить ваш код (профиль - это хороший термин для поиска в Google) и посмотреть, что занимает больше всего ресурсов.Вполне может быть, что здесь не эта функция, а еще один фрагмент кода.Или ожидание загрузки текстур.
С этого момента мы будем считать, что функция, представленная в вопросе, занимает больше всего времени.Давайте посмотрим, на что он тратит свое время.У меня нет остальной части вашего кода, поэтому я не могу его сравнить, но я могу скомпилировать его и посмотреть на полученный байт-код.Используя javap для класса, содержащего функцию, я получаю следующее (байт-код обрезан там, где есть повторы).
public static int tintABGRPixel(int, Color);
Code:
0: iload_0
1: bipush 16
3: ishr
4: sipush 255
7: iand
8: i2d
9: ldc2_w #2 // double 0.2126d
12: dmul
13: iload_0
...
37: dadd
38: ldc2_w #8 // double 255.0d
41: ddiv
42: dstore_2
43: iload_0
44: bipush 24
46: ishr
47: sipush 255
50: iand
51: bipush 24
53: ishl
54: aload_1
55: pop
56: invokestatic #10 // Method Color.getBlue:()I
59: i2d
60: dload_2
61: dmul
62: d2i
63: sipush 255
66: iand
67: ior
68: aload_1
69: pop
...
102: ireturn
Поначалу это может показаться страшным, но байт-код java хорош тем, что вы можете сопоставитькаждая строка (или инструкция) до точки в вашей функции.Он не сделал ничего сумасшедшего, например, переписал или векторизовал его или что-то, что делает его неузнаваемым.
Общий метод, чтобы увидеть, внесло ли изменение улучшение, состоит в измерении кода до и после.С этим знанием вы можете решить, стоит ли вносить изменения.Как только производительность станет достаточно хорошей, остановитесь.
Наш профилирующий бедняк должен посмотреть на каждую инструкцию и посмотреть (в среднем, согласно онлайн-источникам), насколько она дорогая.Это немного наивно, так как продолжительность выполнения каждой инструкции может зависеть от множества вещей, таких как аппаратное обеспечение, на котором она работает, версии программного обеспечения на компьютере и инструкции вокруг нее.
У меня нет исчерпывающего списка затрат времени на каждую инструкцию, поэтому я собираюсь перейти к некоторым эвристикам.
- целочисленные операции выполняются быстрее, чем операции с плавающей запятой.
- константы работают быстрее, чем локальная память, что быстрее, чем глобальная память.
- Сила двух может дать мощныеоптимизации.
Некоторое время я смотрел на байт-код, и все, что я заметил, это то, что в строках 8 - 42 много операций с плавающей запятой.Этот раздел кода отрабатывает lum (яркость).Кроме этого, больше ничего не выделяется, поэтому давайте перепишем код с нашей первой эвристической идеей.Если вам не нужны объяснения, я предоставлю окончательный код в конце.
Давайте просто рассмотрим, каким будет синий цвет (который мы обозначим B) к концу функции.,Изменения будут применяться и к красному и к зеленому цветам, но мы будем их кратко исключать.
double lum = ((pixelColor>>16 & 0xff) * 0.2126 +
(pixelColor>>8 & 0xff) * 0.7152 +
(pixelColor & 0xff) * 0.0722) / 255;
...
... | ((int)(tintColor.getBlue()*lum) & 0xff) | ...
Это можно переписать как int x = (pixelColor >> 16 & 0xff), y = (pixelColor>> 8 & 0xff), z = (pixelColor & 0xff);double a = 0,2126, b = 0,7152, c = 0,0722;двойной свет = (a x + b y + c * z) / 255;int B = (int) (tintColor.getBlue () * lum) & 0xff;
Мы не хотим делать так много операций с плавающей запятой, поэтому давайте сделаем некоторый факторинг.Идея состоит в том, что 0.2126 можно записать как 2126 / 10000.
int x = (pixelColor>>16 & 0xff), y = (pixelColor>>8 & 0xff), z = (pixelColor & 0xff);
int a = 2126, b = 7152, c = 722;
int top = a*x + b*y + c*z;
double temp = (double)(tintColor.getBlue() * top) / 10000 / 255;
int B = (int)temp & 0xff;
Так что теперь мы делаем три умножения целых чисел (imul) вместо трех dmuls.Стоимость - одно дополнительное плавающее деление, которое само по себе, вероятно, не стоило бы того.Но мы можем решить эту проблему, объединив два последовательных деления.Мы также можем настроить код для еще одной оптимизации, переместив приведение и деление на одну строку.
int x = (pixelColor>>16 & 0xff), y = (pixelColor>>8 & 0xff), z = (pixelColor & 0xff);
int a = 2126, b = 7152, c = 722;
int top = a*x + b*y + c*z);
int temp = (int)((double)(tintColor.getBlue()*top) / 2550000);
int B = temp & 0xff;
Это может быть хорошим местом для остановки.Однако, если вам нужно немного повысить производительность этой функции, мы можем оптимизировать деление на константу и приведение двойного к целому (что я считаю двумя дорогими операциями) к умножению (на длинное) иshift.
int x = (pixelColor>>16 & 0xff), y = (pixelColor>>8 & 0xff), z = (pixelColor & 0xff);
int a = 2126, b = 7152, c = 722;
int top = a*x + b*y + c*z;
int Btemp = (int)(( * top * 1766117501L) >> 52);
int B = temp & 0xff;
где магические числа равны двум, которые были замаскированы, когда я скомпилировал версию кода на c ++ с помощью clang.Я не могу объяснить, как создать это волшебство, но оно работает, насколько я тестировал, с парой значений для x, y, z и tintColor.getBlue ().При тестировании я предполагал, что все значения находятся в диапазоне от 0 до 256, и я попробовал только пару примеров.
Окончательный код приведен ниже.Имейте в виду, что это не очень хорошо проверено и может иметь пропущенные края, так что дайте мне знать, если есть какие-либо ошибки.Надеюсь, это достаточно быстро.
public static int tintABGRPixel(int pixelColor, Color tintColor) {
//Calculate the luminance. The decimal values are pre-determined.
int x = pixelColor>>16 & 0xff, y = pixelColor>>8 & 0xff, z = pixelColor & 0xff;
int top = 2126*x + 7252*y + 722*z;
int Btemp = (int)((tintColor.getBlue() * top * 1766117501L) >> 52);
int Gtemp = (int)((tintColor.getGreen() * top * 1766117501L) >> 52);
int Rtemp = (int)((tintColor.getRed() * top * 1766117501L) >> 52);
//Calculate the new tinted color of the pixel and return it.
return ((pixelColor>>24 & 0xff) << 24) | Btemp & 0xff | (Gtemp & 0xff) << 8 | (Rtemp & 0xff) << 16;
}