19

私はJavaをあまりしません。

最適化された数学コードを書いていますが、プロファイラーの結果にショックを受けました。私のコードは値を収集し、データをインターリーブしてから、それに基づいて値を選択します。Java は、私の C++ および MATLAB 実装よりも遅く実行されます。

javac 1.7.0_05 Sun/Oracle JDK 1.7.05 を使用しています

コードには、関連するタスクを実行するフロア関数が存在します。 Java math.floor プロファイルの結果

  1. これを修正するパラダイム的な方法を知っている人はいますか?
  2. floor()関数が という名前で定義されていることに気付きましたStrictMath-ffast-mathJavaのようなものはありますか?自分で書かなくても、floor 関数をより計算上合理的なものに変更する方法があるに違いないと期待しています。

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

編集

それで、ギプスをやろうと提案してくれた人が何人かいました。これを試してみましたが、ウォールタイムにまったく変化はありませんでした。

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

413742 キャストフロア機能

394675 数学フロア

これらのテストは、プロファイラーなしで実行されました。プロファイラーを使用する努力がなされましたが、実行時間が大幅に変更されました (15 分以上なのでやめました)。

4

6 に答える 6

8

FastMathを試してみてください。

Java と Javascript での Mathのパフォーマンスに関する投稿を次に示します。デフォルトの数学ライブラリが遅い理由について、いくつかの良いヒントがあります。彼らは 以外の操作について議論していますがfloor、彼らの調査結果は一般化できると思います。面白いと思いました。

編集

このバグ エントリによると、floor は 7(b79)、6u21(b01) で純粋な Java コードを実装しており、パフォーマンスが向上しています。JDK 6の floor のコードはFastMathのコードよりも少し長いですが、そのようなパフォーマンスの原因ではない可能性があります。劣化。どのJDKを使用していますか? より新しいバージョンで試していただけますか?

于 2012-08-21T06:34:23.720 に答える
5

Math.floor()私のマシンでは、タイトなループで呼び出しごとに約 7 ナノ秒と非常に高速です。(Windows 7、Eclipse、Oracle JDK 7)。ほとんどすべての状況で非常に高速であると予想されますが、それがボトルネックであることが判明した場合は非常に驚くでしょう.

いくつかのアイデア:

  • プロファイラーを実行せずに、いくつかのベンチマークを再実行することをお勧めします。Math.floor()プロファイラがバイナリをインストルメント化するときに、特にインライン化される可能性が高いような小さな関数の場合に、誤ったオーバーヘッドが作成されることがあります。
  • いくつかの異なる JVM を試してください。あいまいなバグに遭遇した可能性があります
  • floor の新しい実装を含む優れた Apache Commons MathFastMathライブラリのクラスを試してください。速ければ本当に驚きますが、それはわかりません。
  • ネイティブ コードを呼び出す Java の機能に干渉する可能性のある仮想化技術などを実行していないことを確認します (ネイティブ コードは、java.lang.Mathを含むいくつかの機能で使用されますMath.floor()) 。
于 2012-08-21T06:41:46.033 に答える
4

メソッドの監視にはある程度のオーバーヘッドがかかることに注意してください。VisualVM の場合、これはかなり高くなります。頻繁に呼び出されるがほとんど実行しないメソッドがある場合、大量の CPU を使用しているように見えることがあります。たとえば、私は Integer.hashCode() が大ヒットしたことがあります。;)

私のマシンでは、フロアの所要時間は 5.6 ns 未満ですが、キャストの所要時間は 2.3 ns です。あなたのマシンでこれを試してみてください。


特殊なケースを処理する必要がない限り、プレーン キャストの方が高速です。

// 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
于 2012-08-21T07:23:18.067 に答える
4

まず第一に、プロファイラーは、CPU 時間の 99% をフロア関数に費やしていることを示しています。これはフロアが遅いことを示しているわけではありません。floor() しか実行しない場合、それは完全に正気です。ただし、他の言語はフロアをより効率的に実装しているように見えるため、あなたの仮定は正しいかもしれません。

私は学校から、floor (正の数に対してのみ機能し、負の数に対しては 1 回オフ) の単純な実装は、integer/long にキャストすることで実行できることを知っています。それは言語にとらわれず、CS コースからのある種の一般的な知識です。

ここにいくつかのマイクロベンチがあります。自分のマシンで動作し、学校で学んだことを裏付けています ;)

rataman@RWW009 ~/Desktop
$ javac Cast.java && java Cast
10000000 Rounds of Casts took 16 ms

rataman@RWW009 ~/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;
    }

}

于 2012-08-21T06:28:43.340 に答える
0

アルゴリズムがそれに大きく依存している場合、Math.floor (および Math.ceil) は驚くべきボトルネックになる可能性があります。これは、これらの関数が気にしない可能性のあるエッジ ケース (マイナス ゼロやプラス ゼロなど) を処理するためです。これらの関数の実装を見て、実際に何をしているのかを確認してください。そこには驚くべき量の分岐があります。

また、Math.floor/ceil が double のみを引数として取り、double を返すことも考慮してください。これは望ましくない場合があります。int または long だけが必要な場合は、Math.floor のチェックのいくつかは不要です。

単純に int にキャストすることを提案する人もいます。これは、値が正である限り機能します (アルゴリズムは、Math.floor がチェックするエッジ ケースに依存しません)。その場合、単純なキャストが(私の経験では)かなりの差で最速のソリューションです。

たとえば、値が負になる可能性があり、フロートから int が必要な場合は、次のようにすることができます。

public static final int floor(final float value) {
    return ((int) value) - (Float.floatToRawIntBits(value) >>> 31);
}

(「if」を防ぎながら、キャストから float の符号ビットを減算して、負の数を正しくするだけです)

私の経験では、これは Math.floor よりもはるかに高速です。そうでない場合は、アルゴリズムを確認することをお勧めします。または、JVM パフォーマンスのバグに遭遇した可能性があります (可能性ははるかに低いです)。

于 2021-06-01T18:58:25.587 に答える