Подтвердить что ты не робот

Означает ли это, что Java Math.floor чрезвычайно медленный?

Я не очень много Java.

Я пишу некоторый оптимизированный математический код, и я был шокирован результатами моих профилировщиков. Мой код собирает значения, перемежает данные и затем выбирает значения на основе этого. Java работает медленнее, чем мои реализации на С++ и MATLAB.

Я использую javac 1.7.0_05 Я использую Sun/Oracle JDK 1.7.05

Существует функция floor, которая выполняет соответствующую задачу в коде. java math.floor profile results

  • Кто-нибудь знает о парадигматическом способе исправить это?
  • Я заметил, что моя функция floor() определена с помощью StrictMath. Есть что-то вроде -ffast-math для Java? Я ожидаю, что должен быть способ изменить функцию пола на что-то более разумно вычислительное, не написав мои собственные.

    public static double floor(double a) {
        return StrictMath.floor(a); // default impl. delegates to StrictMath
    }
    

Edit

Итак, несколько человек предложили мне попробовать сделать бросок. Я попробовал это, и в стенах не было никаких изменений.

private static int flur(float dF)
{
    return (int) dF;
}

413742 функция литого пола

394675 Math.floor

Эти тесты выполнялись без профилировщика. Было предпринято усилие для использования профилировщика, но время выполнения было радикально изменено (15 минут, поэтому я ушел).

4b9b3361

Ответ 1

Здесь вы можете проверить свою гипотезу о том, что код действительно тратит 99% своего времени в floor. Предположим, что у вас есть версии Java и С++ алгоритма, которые являются правильными с точки зрения результатов, которые они производят. Для аргумента предположим, что две версии называют эквивалентные функции floor одинаковым числом раз. Таким образом, функция времени

t(input) = nosFloorCalls(input) * floorTime + otherTime(input)

где floorTime - время, затраченное на вызов floor на платформе.

Теперь, если ваша гипотеза верна, и floorTime значительно дороже на Java (в той степени, в которой это занимает примерно 99% времени выполнения), тогда вы ожидаете, что версия Java приложения будет работать с большим коэффициентом (50 и более раз) медленнее, чем версия С++. Если вы этого не видите, ваша гипотеза скорее всего будет ложной.


Если гипотеза ложна, вот два альтернативных объяснения результатов профилирования.

  • Это аномалия измерения; т.е. профилировщик каким-то образом ошибся. Попробуйте использовать другой профилировщик.

  • В версии Java вашего кода есть ошибка, вызывающая вызов floor во много раз больше, чем в версии кода на С++.

Ответ 2

Возможно, вы захотите попробовать FastMath.

Вот сообщение о производительности Math в Java против Javascript. Есть несколько хороших советов о том, почему математика по умолчанию медленна. Они обсуждают другие операции, чем floor, но я думаю, их результаты могут быть обобщены. Мне было интересно.

ИЗМЕНИТЬ

В соответствии с эта запись ошибки на полу реализован чистый код Java в 7 (b79), 6u21 (b01), что приводит к лучшей производительности, Код пола в JDK 6 по-прежнему немного длиннее, чем в FastMath, но не может нести ответственность за такой перфект. деградация. Какой JDK вы используете? Не могли бы вы попробовать с более новой версией?

Ответ 3

Math.floor() безупречно быстро работает на моей машине примерно в 7 наносекунд за один вызов в узкой петле. (Windows 7, Eclipse, Oracle JDK 7). Я ожидал бы, что это будет очень быстро в почти всех обстоятельствах и будет очень удивлен, если это окажется узким местом.

Некоторые идеи:

  • Я бы предложил повторно запустить некоторые тесты без профайлера, работающего. Иногда бывает, что профилировщики создают ложные накладные расходы, когда они обрабатывают двоичный файл - особенно для небольших функций, таких как Math.floor(), которые могут быть встроены.
  • Попробуйте несколько разных JVM, возможно, вы столкнулись с неясной ошибкой
  • Попробуйте класс FastMath в отличной библиотеке Apache Commons Math, которая включает в себя новую реализацию пола. Я был бы очень удивлен, если бы это было быстрее, но вы никогда не знаете.
  • Убедитесь, что вы не используете технологию виртуализации или аналогичную, которая может мешать Java-возможности вызывать собственный код (который используется в нескольких функциях java.lang.Math, включая Math.floor())

Ответ 4

Прежде всего: ваш профилировщик показывает, что ваши расходы на 99% времени процессора в функции пола. Это не означает, что пол медленный. Если вы ничего не делаете, кроме пола(), это абсолютно здорово. Тем не менее, поскольку другие языки, похоже, более эффективно используют пол, ваше предположение может быть правильным.

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

Вот несколько микро-скамеек. Работает на моей машине и поддерживает то, что я узнал в школе;)

[email protected] ~/Desktop
$ javac Cast.java && java Cast
10000000 Rounds of Casts took 16 ms

[email protected] ~/Desktop
$ javac Floor.java && java Floor
10000000 Rounds of Floor took 140 ms
#
public class Cast/Floor {

    private static final int ROUNDS = 10000000;

    public static void main(String[] args)
    {
        double[] vals = new double[ROUNDS];
        double[] res = new double[ROUNDS];

        // awesome testdata
        for(int i = 0; i < ROUNDS; i++)
        {
            vals[i] = Math.random() * 10.0;
        }

        // warmup
        for(int i = 0; i < ROUNDS; i++)
        {
            res[i] = floor(vals[i]);
        }

        long start = System.currentTimeMillis();
        for(int i = 0; i < ROUNDS; i++)
        {
            res[i] = floor(vals[i]);
        }
        System.out.println(ROUNDS + " Rounds of Casts took " + (System.currentTimeMillis() - start) +" ms");
    }

    private static double floor(double arg)
    {
        // Floor.java
        return Math.floor(arg);
        // or Cast.java
        return (int)arg;
    }

}

Ответ 5

Стоит отметить, что мониторинг метода требует некоторых накладных расходов, а в случае с VisualVM это довольно высокий уровень. Если у вас есть метод, который вызывается часто, но он очень мало может показаться, что он использует много CPU. например Я видел Integer.hashCode() как большой нападающий один раз.;)

На моей машине пол занимает меньше 5.6 нс, но литой занимает 2,3 нс. Вы можете попробовать это на своей машине.


Если вам не нужно обрабатывать угловые случаи, простой литье выполняется быстрее.

// Rounds to zero, instead of Negative infinity.
public static double floor(double a) {
    return (long) a;
}

public static void main(String... args) {
    int size = 100000;
    double[] a = new double[size];
    double[] b = new double[size];
    double[] c = new double[size];
    for (int i = 0; i < a.length; i++) a[i] = Math.random()  * 1e6;

    for (int i = 0; i < 5; i++) {
        timeCast(a, b);
        timeFloor(a, c);
        for (int j = 0; j < size; j++)
            if (b[i] != c[i])
                System.err.println(a[i] + ": " + b[i] + " " + c[i]);
    }
}

public static double floor(double a) {
    return a < 0 ? -(long) -a : (long) a;
}

private static void timeCast(double[] from, double[] to) {
    long start = System.nanoTime();
    for (int i = 0; i < from.length; i++)
        to[i] = floor(from[i]);
    long time = System.nanoTime() - start;
    System.out.printf("Cast took an average of %.1f ns%n", (double) time / from.length);
}

private static void timeFloor(double[] from, double[] to) {
    long start = System.nanoTime();
    for (int i = 0; i < from.length; i++)
        to[i] = Math.floor(from[i]);
    long time = System.nanoTime() - start;
    System.out.printf("Math.floor took an average of %.1f ns%n", (double) time / from.length);
}

печатает

Cast took an average of 62.1 ns
Math.floor took an average of 123.6 ns
Cast took an average of 61.9 ns
Math.floor took an average of 6.3 ns
Cast took an average of 47.2 ns
Math.floor took an average of 6.5 ns
Cast took an average of 2.3 ns
Math.floor took an average of 5.6 ns
Cast took an average of 2.3 ns
Math.floor took an average of 5.6 ns