Почему Java Math.min так медленно работает в моем приложении для Android?

1

У меня был код, который я профилировал, и был удивлен, сколько времени тратится на Math.min(float, float).

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

Мой оригинальный метод:

private static float min2(float v1, float v2, float v3) {
    return Math.min(Math.min(v1,v2),v3);
}

Но я обнаружил, что это было примерно в 5 раз быстрее:

private static float min1(float v1, float v2, float v3) {
    if (v1 < v2 && v1 < v3) {
        return v1;
    }
    else if (v2 < v3) {
        return v2;
    }
    else {
        return v3;
    }
}

Для справки это код для Math.min:

public static float min(float f1, float f2) {
    if (f1 > f2) {
        return f2;
    }
    if (f1 < f2) {
        return f1;
    }
    /* if either arg is NaN, return NaN */
    if (f1 != f2) {
        return Float.NaN;
    }
    /* min(+0.0,-0.0) == -0.0 */
    /* 0x80000000 == Float.floatToRawIntBits(-0.0f) */
    if (Float.floatToRawIntBits(f1) == 0x80000000) {
        return -0.0f;
    }
    return f2;
}

Примечание. Мой прецедент был симметричным, и выше все было верно для max вместо min.

EDIT1: Оказывается, ~ 5x было преувеличением, но я все еще вижу разницу в скорости в моем приложении. Хотя я подозреваю, что это может быть связано с неправильным проведением теста времени.

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

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

Я занимаюсь графикой в приложении для Android, и я обнаружил min/max значений из 3 событий касания. Опять же, краевые случаи, такие как -0.0f и разные бесконечности, здесь не являются проблемой. Значения варьируются от 0.0f до 3000f.

Первоначально я профилировал свой код с помощью инструмента профилирования метода Android Device Monitor, который показал разницу в 5 раз. Но это не лучший способ для микропрофильного кода, как я теперь узнал.

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

long min1Times = 0L;
long min2Times = 0L;
...
// loop assigning touch values to v1, v2, v3
        long start1 = System.nanoTime();
        float min1 = min1(v1, v2, v3);
        long end1 = System.nanoTime();
        min1Times += end1 - start1;
        long start2 = System.nanoTime();
        float min2 = min2(v1, v2, v3);
        long end2 = System.nanoTime();
        min2Times += end2 - start2;
        double ratio = (double) (min1Times) / (double) (min2Times);
        Log.d("", "ratio: " + ratio);

Это отображает коэффициент работы с каждым новым событием касания. Когда я вихлю пальцем по экрану, первые регистрируемые отношения равны либо 0.0 либо Infinity или NaN. Это заставляет меня думать, что этот тест не очень точно измеряет время. По мере сбора большего количества данных отношение имеет тенденцию варьироваться от .85 до 1.15.

  • 0
    Какие тестовые значения вы использовали? Много ли подходил случай, когда двое или более были равны?
  • 0
    Какую версию Java вы используете?
Показать ещё 7 комментариев
Теги:
optimization
micro-optimization

2 ответа

0

Проблема заключается в точности значений float.

Если вы (0.0f, -0.0f, 0.0f) свой метод с помощью аргументов (0.0f, -0.0f, 0.0f), он вернет вам 0.0f чтобы быть самым маленьким поплавком - это не так (поплавковый, -0.0f меньше)

вложенный Min-Method вернет ожидаемый результат.

Итак, чтобы ответить на ваш вопрос: если два метода не на 100% равны - нет смысла сравнивать их производительность :-)

Java будет обрабатывать значение 0.0f == -0.0f как true, но: new Float(0.0)).equals(new Float(-0.0)) будет false ! Math.Min рассмотрит это, ваш метод не будет.

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

float delta = 0.005
if (Math.abs(f1 - f2) < delta) //Consider them equal.
if (Math.abs(f1 - f2) > delta) // not equal. 

И то, что происходит в конце метода Math.min - ТОЛЬКО очень точно, фактически проверяя, является ли одно число -0.0f - побитовым.

Таким образом, недостаток производительности является тем более важным результатом.

Однако, если вы сравнили значения float, такие как "10", "5" и "8", не должно быть разницы в производительности, потому что 0-проверка никогда не попадает.

  • 0
    Неравенство не проблема.
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню