16

多くの場合、同じパラメータの正弦だけでなく余弦も必要です。

C の場合sincos、共通の UNIXm数学ライブラリに関数があります。実際、少なくとも i386 では、これは単一のアセンブリ命令である必要がありますfsincos

sincos, sincosf, sincosl - sin と cos を同時に計算する

正弦と余弦の計算には明らかな重複があるため、これらの利点が存在すると思いますsin(x)^2 + cos(x)^2 = 1cos = Math.sqrt(1 - sin*sin)しかし、sqrt関数には同様のコストがかかるため、これを as としてショートカットしようとしても報われません。

Javaで同じ利点を享受する方法はありますか? その代償を払うことになると思いdouble[]ます。ガベージコレクションが追加されているため、すべての努力が無駄になる可能性があります。

または、Hotspot コンパイラは、両方が必要であることを認識し、これをsincosコマンドにコンパイルするほどスマートですか? Math.sinそれが認識されているかどうかをテストできますか? また、コード内でコマンドとMath.cosコマンドが直接連続していることを確認するなどして、これを認識できるようにすることはできますか? これは実際には、Java 言語の観点から最も理にかなっています。つまり、fsincosアセンブリ呼び出しを使用するようにコミラーにこれを最適化させることです。

いくつかのアセンブラーのドキュメントから収集:

Variations    8087         287        387      486     Pentium
fsin           -            -       122-771  257-354   16-126  NP
fsincos        -            -       194-809  292-365   17-137  NP
 Additional cycles required if operand > pi/4 (~3.141/4 = ~.785)
sqrt        180-186      180-186    122-129   83-87    70      NP

fsincos余分な pop が必要ですが、それは 1 クロック サイクルで発生するはずです。CPUもこれを最適化しないと仮定すると、 2回sincos呼び出すよりもほぼ2倍高速になるはずですsin(コサインを計算するのは2回目です。したがって、加算を行う必要があると思います)。sqrt状況によっては、sine の方が速い場合もありますが、sine の方が速い場合があります。

更新: C でいくつかの実験を行いましたが、決定的ではありません。興味深いことに、 (なし)sincosよりもわずかに高速であるように思われ、両方を計算するときにGCC コンパイラーが使用するので、Hotspot に実行してもらいたいことを実行します (または、Hotspot も実行しますか?)。を使用せずに except を使用することで、コンパイラが私を裏切ることをまだ防ぐことはできませんでした。その後、 ではなくC にフォールバックします。sincosfsincossincosfsincoscossinfsin

4

5 に答える 5

11

キャリパーでいくつかのマイクロベンチマークを実行しました。-4 * pi .. 4 * piの範囲の乱数の(事前計算された)配列に対する10000000回の反復。私は、思いつく可能性のある最速のJNIソリューションを取得するために最善を尽くしました。実際に取得するfsincosのか、エミュレートするのかを予測するのは少し難しいですsincos。報告された数は、10回のキャリパー試行のうちの最良のものです(これは、3〜10回の試行で構成され、その平均が報告されます)。つまり、おおよそ、それぞれ内側のループを30〜100回実行します。

私はいくつかの変種をベンチマークしました:

  • Math.sinのみ(参考)
  • Math.cosのみ(参考)
  • Math.sin+Math.cos
  • sincosJNI経由
  • Math.sin+cos経由Math.sqrt( (1+sin) * (1-sin) )+記号の再構築
  • Math.cosMath.sqrt( (1+cos) * (1-cos) )+サイン再構築による罪

(1+sin)*(1-sin)=1-sin*sin数学的には、しかし罪が1に近い場合、それはより正確であるはずですか?実行時の違いは最小限で、1つの追加を節約できます。

象限を介しx %= TWOPI; if (x<0) x+=TWOPI;て再構築に署名し、次に象限をチェックします。より少ないCPUでこれを行う方法をご存知でしたら、喜んでお聞きします。

sqrt少なくとも一般的な角度では、経由による数値の損失は問題ないようです。大まかな実験からの1e-10の範囲について。

Sin         1,30 ==============
Cos         1,29 ==============
Sin, Cos    2,52 ============================
JNI sincos  1,77 ===================
SinSqrt     1,49 ================
CosSqrt     1,51 ================

sqrt(1-s*s)sqrt((1+s)*(1-s))は約0.01の違いを生みます。ご覧のとおり、sqrtベースのアプローチは他のどのアプローチにも勝ちます(現在sincos、純粋なJavaではアクセスできないため)。JNIはコンピューティングやsincosよりも優れていますが、アプローチはさらに高速です。それ自体は一貫してティック(0,01)よりも優れているようですが、符号を再構築するためのケースの区別には追加のテストがあります。私の結果はそれを支持していないか、明らかに好ましいとは思いませんが、当時と比較して約40%の時間を節約できます。sincossqrtcossin>sin+sqrtcos+sqrtsincos

Javaを拡張して、本質的に最適化されたsincosを使用する場合、これはさらに優れている可能性があります。私見では、グラフィックスなどの一般的なユースケースです。AWT、Batikなどで使用すると、多くのアプリケーションがその恩恵を受ける可能性があります。

これを再度実行する場合は、JNIsinとaを追加noopして、JNIのコストを見積もります。たぶん、sqrtJNIを介してトリックをベンチマークすることもできます。sincos長期的には、私たちが実際に本質的なものを望んでいることを確認するためだけに。

于 2013-01-05T19:42:31.440 に答える
1

Hotspot コードを見て、Oracle Hotspot VM が sin(a) + cos(a) を fsincos に最適化していないことを確信しました: assembler_x86.cppの 7482ff 行を参照してください。

ただし、fsin と fcos を別々に使用することによるマシン サイクル数の増加は、GC の実行などの他の操作の影響を受けやすいのではないかと思います。標準の Java 機能を使用して、アプリケーションをプロファイリングします。プロファイルの実行によって、sin/cos 呼び出しにかなりの時間が費やされていることが示された場合にのみ、私はそれについて何かをしようと思います。

この場合、出力パラメーターとして 2 要素の jdoublearray を使用する JNI ラッパーを作成します。sincos JNI 操作を使用するスレッドが 1 つしかない場合は、静的に初期化された double[2] 配列を Java コードで使用して、何度も再利用することができます。

于 2012-11-20T11:12:40.417 に答える
1

ほとんどの sin および cos 計算は、ハードウェアへの直接呼び出しです。これほど速く計算できる方法はありません。具体的には、範囲 +- pi/4 では、レートは非常に高速です。一般的にハードウェア アクセラレーションを使用し、値を指定された値に制限しようとする場合は、問題ないはずです。ソース

于 2012-11-19T19:31:36.527 に答える
1

いつでもプロファイリングできます。

ただし、div と sqrt の内部実装は非常に似ているため、通常、sqrt は除算と同じ速度で実行されます。

サインとコサイン、OTOH は、最大 10 度の多項式で計算されます。共通の係数はなく、モジュロ 2pi の削減が難しい可能性もあります。これは、sinco で共有される唯一の共通部分です (CORDIC を使用しない場合)。

編集改訂されたプロファイリング (タイプミスを修正) は、次のタイミングの違いを示しています

sin+cos:  1.580 1.580 1.840 (time for 200M iterations, 3 successive trials)
sincos:   1.080 0.900 0.920
sin+sqrt: 0.870 1.010 0.860
于 2012-11-19T19:31:59.853 に答える
1

通常の Java で利用できるfsincosはありません。また、JNI バージョンは、java.lang.Math.sin() および cos() への二重呼び出しよりも遅くなる場合があります。

sin(x)/cos(x) の速度が気になるところだと思います。そこで、fsincos の代わりに高速な三角関数演算を提案します: Look Up Table。以下は私の元の投稿です。お役に立てば幸いです。

=====

ルックアップ テーブル (LUT) を使用して、三角関数 (sin と cos) で可能な限り最高のパフォーマンスを達成しようとしました。

私が見つけたもの:

  • LUT は、java.lang.Math.sin()/cos() よりも20 ~ 25 倍高速です。ネイティブの fsin / fcos と同じくらい高速です。fsincosと同じくらい速いかもしれません。
  • しかし、java.lang.Math.sin() と cos() は、0 ~ 45 度の角度を使用する場合、sin/cos を計算する他のどの方法よりも高速です。
  • しかし、12 度よりも小さい角度では、sin(x) はほぼ == x になることに注意してください。さらに高速です。

  • 一部の実装では、float 配列を使用して sin を格納し、別の配列を cos に格納します。これは不要です。ただ覚えておいてください:

cos(x) == sin(x + PI/2)

  • つまり、sin(x) テーブルがある場合、cos(x) テーブルは無料です。

java.lang.Math.sin(); を使用して、範囲 [0..45]の角度に対して sin() でいくつかのテストを行いました。360 の位置の単純なルックアップ テーブル、範囲 [0..90] のテーブル値を持つ最適化された LUT90 ですが、[0..360] で動作するように拡張されています。および補間を使用してテーブルをルックアップします。警告後、 java.lang.Math.sin() は他よりも高速であることに注意してください。

Size test: 10000000
Angles range: [0.0...45.0]
Time in ms
Trial | Math.sin() | Lut sin() | LUT90.sin() | Lut sin2() [interpolation]
0    312,5879        25,2280        27,7313      36,4127
1    12,9468         19,5467        21,9396      34,2344
2    7,6811          16,7897        18,9646      32,5473
3    7,7565          16,7022        19,2343      32,8700
4    7,6634          16,9498        19,6307      32,8087

ここで利用可能なソースGitHub

ただし、 range[-360..360]で高いパフォーマンスが必要な場合は、 java.lang.Math lib の方が遅くなります。ルックアップ テーブル (LUT) は約 20 倍高速です。高精度が必要な場合は、LUT と補間を使用できます。これは少し遅くなりますが、java.lang.Math よりも高速です。上記のリンクで、Math2.java の私の sin2() を参照してください。

以下の数値は角度の高い範囲のものです:

Size test: 10000000
Angles range: [-360.0...360.0]
Time in ms
Trial|Math.sin() | Lut sin() | LUT90.sin() | Lut.sin2() [interpolation]
0    942,7756        35,1488        47,4198      42,9466
1    915,3628        28,9924        37,9051      41,5299
2    430,3372        24,8788        34,9149      39,3297
3    428,3750        24,8316        34,5718      39,5187
于 2016-02-15T02:37:09.073 に答える