トップページ > 記事閲覧
MGetRotAxisとクォータニオンの速度差
名前:ゆーすけ 日時: 2016/08/10 01:44

ご無沙汰しております。 以前地球儀の作り方、Double型への対応等で大変お世話になりました。 おかげさまで作りたかったプログラムもほぼ作り上げることができました。 今回はベクトルの回転に使うVTransformD&MGetRotAxisD関数とクォータニオンの性能の違いについて質問させていただきたいと思います。 以前どこかで「クォータニオンは計算要素数が少ない上、計算負荷の高い三角関数を余り使わないので回転行列による回転処理よりも高速」という話を聞いたので、 作っていたプログラムの物理演算負荷を少しでも下げようと思い(物体の速度ベクトルを1秒間に数万回のオーダーで回転させる必要があった) https://wgld.org/d/webgl/w031.html を参考にクォータニオンによって任意のベクトルを任意の軸ベクトルの周りに任意の角度だけ回転させる関数を実装しました。 この関数にTransformD&MGetRotAxisD関数と全く同じパラメータを与え、両者を100万回ループさせて所要時間を測ってみたところ、MGetRotAxisD関数を用いた方が 4割ほど高速という結果になりました。 DXライブラリではわざわざ自分でクォータニオンを実装するよりも既にある関数を使った方が速いのでしょうか?ちょっと気になりましたのでもしご存知でしたら 教えていただけると幸いです。 どうぞよろしくお願いします。
メンテ

Page: 1 |

Re: MGetRotAxisとクォータニオンの速度差 ( No.1 )
名前:ゆーすけ 日時:2016/08/11 18:48

以下、サンプルプログラムで実装したクォータニオンのコードです(VB2010 + DXライブラリ3.16d) 実装したクォータニオンベースの任意軸回転関数VRotQに回転角度をそのまま与えると、MGetRotAxisDとは逆向きに回転させてしまい 結果が変わってしまうので、VRotQでは角度の引数を内部でマイナスにして計算しています。 'クォータニオン型の構造体を宣言 Public Structure Qternion Dim T As Double Dim X As Double Dim Y As Double Dim Z As Double End Structure '3次元空間上の点を任意の軸の周りに任意の角度だけ回転させる関数 Public Function VRotQ(P As DX.VECTOR_D, Axis As DX.VECTOR_D, Angle As Double) As DX.VECTOR_D Dim ResultQ As Qternion Dim Result As DX.VECTOR_D Dim P2 As Qternion Dim RotQ As Qternion P2.T = 0 P2.X = P.x P2.Y = P.y P2.Z = P.z RotQ = QTRot(Axis, -Angle) ResultQ = QTCross(QTCross(QTConj(RotQ), P2), RotQ) Result.x = ResultQ.X Result.y = ResultQ.Y Result.z = ResultQ.Z Return Result End Function '2つのクォータニオンの乗算の結果を返す Public Function QTCross(A As Qternion, B As Qternion) As Qternion Dim VA As DX.VECTOR_D Dim VB As DX.VECTOR_D Dim RV As DX.VECTOR_D Dim Result As Qternion VA.x = A.X VA.y = A.Y VA.z = A.Z VB.x = B.X VB.y = B.Y VB.z = B.Z Result.T = A.T * B.T - DX.VDotD(VA, VB) RV = DX.VAddD(DX.VAddD(DX.VScaleD(VB, A.T), DX.VScaleD(VA, B.T)), DX.VCrossD(VA, VB)) Result.X = RV.x Result.Y = RV.y Result.Z = RV.z Return Result End Function '回転を表すクォータニオンを返す Public Function QTRot(Axis As DX.VECTOR_D, Angle As Double) As Qternion Dim Result As Qternion Dim NAxis As DX.VECTOR_D Dim Cos As Double Dim Sin As Double NAxis = DX.VNormD(Axis) Cos = Math.Cos(0.5 * Angle) Sin = Math.Sin(0.5 * Angle) Result.T = Cos Result.X = NAxis.x * Sin Result.Y = NAxis.y * Sin Result.Z = NAxis.z * Sin Return Result End Function '共役クォータニオンを返す Public Function QTConj(A As Qternion) As Qternion Dim Result As Qternion Result.T = A.T Result.X = -A.X Result.Y = -A.Y Result.Z = -A.Z Return Result End Function
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.2 )
名前:管理人 日時:2016/08/11 22:40

> DXライブラリではわざわざ自分でクォータニオンを実装するよりも既にある関数を使った方が速いのでしょうか? いえ、そんなことはありません 恐らくクォータニオンによる計算より TransformD&MGetRotAxisD関数の方が早い原因は、 クォータニオンの処理を VisualBasic言語で行っているからです 仮に全く同じ処理をC言語( 若しくは C++言語 )で実行した場合は間違いなくクォータニオンを使用した 処理の方が速いと思います よろしければゆーすけさんが計測に使用したプログラムをこちらに書き込んでいただけないでしょうか? VRotQ や QTCross などの関数と一緒に C++言語のコードに変更して TransformD&MGetRotAxisD関数との 実行速度の差を計測してみたいと思います
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.3 )
名前:ゆーすけ 日時:2016/08/12 02:09

ありがとうございます。 そうですか・・・VB.NETとCの実行速度の違いが原因かもしれないのですね。 最近はVB.NETでも十分なパフォーマンスが出ると思っていたのでそこは意識していませんでした。 ちなみに計測に使用した部分の主要なコードはこちらです。   Dim SWatch As New System.Diagnostics.Stopwatch() '.NETのストップウォッチ用   Dim ResultDX As DX.VECTOR_D '回転処理の結果格納用(VTransformD+MGetRotAxisD関数使用時)   Dim ResultQT As DX.VECTOR_D '回転処理の結果格納用(クォータニオン使用時)   Dim LoopNum As Long = 10 ^ 6 '計測回数   Dim ETimeMat As Double '測定結果格納用(VTransformD+MGetRotAxisD関数使用時)   Dim ETimeQt As Double '測定結果格納用(クォータニオン使用時)   Dim PosIn As DX.VECTOR_D '回転させたい点の位置ベクトル   Dim AxisIn As DX.VECTOR_D '回転軸ベクトル   Dim AngleIn As Double '回転角度   PosIn = DX.VGetD(適当な値)   AxisIn = DX.VGetD(適当な値)   AngleIn = 適当な値   '以下、座標変換を100万回繰り返して所要時間を計測   'VTransformD + MGetRotAxisD版   SWatch.Start() '計測開始   For i As Integer = 0 To LoopNum     ResultDX = DX.VTransformD(PosIn, DX.MGetRotAxisD(AxisIn, AngleIn))   Next   SWatch.Stop() '計測終了   ETimeMat = CDbl(SWatch.ElapsedMilliseconds) '計測結果をミリ秒で取得しDouble型にキャスト   SWatch.Reset() 'ストップウォッチをリセット   'クォータニオン版   SWatch.Start() '計測開始   For i As Integer = 0 To LoopNum     ResultQT = VRotQ(PosIn, AxisIn, AngleIn)   Next   SWatch.Stop() '計測終了   ETimeQt = CDbl(SWatch.ElapsedMilliseconds) '計測結果をミリ秒で取得しDouble型にキャスト   SWatch.Reset() 'ストップウォッチをリセット   '以下、前レスに記述したクォータニオンの実装コード(省略) VC++とVBのフォームアプリケーションの違いもあるので主要部だけ抜粋しました。 どうぞよろしくお願いします。 それと厚かましいお願いなのですが、管理者様の検証の結果仮にクォータニオンの方が高速に計算できるという結果になった場合、 DXライブラリにVRotQの機能を持つ関数を実装していただけないでしょうか? VBからDLLを呼び出すオーバーヘッド等もあり検証通りにはいかないかもしれませんが、Cであればクォータニオンで高速に 計算できるということであれば、VB.NETでも恩恵に預かりたいと思いますので・・・ ご検討よろしくお願いします。
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.4 )
名前:ゆーすけ 日時:2016/08/12 11:10

すみません、最初のレスでクォータニオンより4割速かったと書いたのはVBのデバッグ機能を利用して実行した時の値でした その後Releaseビルドを行い生成された実行ファイルで試したところ、ある程度ばらつきはあるもののVTransformD + MGetRotAxisDの方が 若干(1割前後)速い、位の差に縮まりました(たまにですがクォータニオンの方が速くなることもあります)。 それでもVBで実装したクォータニオンの方が全般的にやや遅いという状況は変わらないようです。 何度か試してみた限り、こちらの環境ではVTransformD + MGetRotAxisD使用時が770ms前後、クォータニオン使用時が830ms前後になるようです (ただしある程度のばらつきはあります)。 QTRot関数内でsinとcosの計算をやる際に両方ともAngleに0.5をかける演算を行っているので、その演算を直前に行いsinとcosの引数に演算結果のみを 与えるようにすると乗算の回数が1回減るのでほんの少しは速くできるかもしれません(こちらの環境で試したところ、確かに速度差は縮まったのですが クォータニオン使用時に高速化されず回転行列使用時の方が若干遅くなったという不可解な結果になりました・・・)。 当方の環境を書き忘れていましたので追記します。 PC:SONY VAIO Z(VPCZ11AGJ) CPU:i5 520M(2.4GHz) メモリ:8GB OS:Windows7 Professional(64bit版)
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.5 )
名前:管理人 日時:2016/08/14 03:45

計測用コードの掲載ありがとうございます、手元の環境でC言語のコードに書き換えて 実行してみたところ、以下のような結果になりました VTransformD + MGetRotAxisD : 140.764ms クォータニオン : 61.015ms 環境 PC:BTO PC CPU:i7-3770K 3.50GHz メモリ:32GB OS:Windows7 Professional(64bit版) 処理時間が VTransformD + MGetRotAxisD使用時に比べて2分の1以下になりました、 やはりクォータニオン使用時の方が速いようです ( 因みに最適化で実行コードがごっそり消されてしまわないように SRand( GetNowCount() ) ; や GetRand を使用したコードに少し変更して計測しました ) …ただ、VRotQ は処理速度を稼ぐために以下のような、一見何の計算を しているのかわからないコードに書き換えていますので、VTransformD + MGetRotAxisD も 同様の最適化を施せばもう少し処理時間の差は縮まるかもしれません extern VECTOR_D VRotQD( VECTOR_D P, VECTOR_D Axis, double Angle ) { VECTOR_D Result ; DOUBLE4 Temp ; DOUBLE4 RotQ ; double Cos ; double Sin ; double Square ; double Size ; Square = Axis.x * Axis.x + Axis.y * Axis.y + Axis.z * Axis.z ; if( Square < 0.0000001 ) { return VGetD( -1.0, -1.0, -1.0 ) ; } Size = _SQRTD( Square ) ; _SINCOSD( 0.5 * -Angle, &Sin, &Cos ) ; RotQ.w = Cos ; RotQ.x = Axis.x / Size * Sin ; RotQ.y = Axis.y / Size * Sin ; RotQ.z = Axis.z / Size * Sin ; Temp.w = P.x * RotQ.x + RotQ.y * P.y + RotQ.z * P.z ; Temp.x = P.x * RotQ.w - RotQ.y * P.z + RotQ.z * P.y ; Temp.y = P.y * RotQ.w - RotQ.z * P.x + RotQ.x * P.z ; Temp.z = P.z * RotQ.w - RotQ.x * P.y + RotQ.y * P.x ; Result.x = RotQ.x * Temp.w + Temp.x * RotQ.w + ( Temp.y * RotQ.z - Temp.z * RotQ.y ) ; Result.y = RotQ.y * Temp.w + Temp.y * RotQ.w + ( Temp.z * RotQ.x - Temp.x * RotQ.z ) ; Result.z = RotQ.z * Temp.w + Temp.z * RotQ.w + ( Temp.x * RotQ.y - Temp.y * RotQ.x ) ; return Result ; } ( ゆーすけさんの VRotQ のコードの関数呼び出しを展開して不要な部分( 0.0 を乗算している 計算など )を削除しただけのものです ) 因みにC言語で VRotQ を実装したものをDLLを介して C# で使用して同様の計測をしたところ 以下のような結果になりました VTransformD + MGetRotAxisD : 244.452ms クォータニオン : 105.062ms > そうですか・・・VB.NETとCの実行速度の違いが原因かもしれないのですね。 > 最近はVB.NETでも十分なパフォーマンスが出ると思っていたのでそこは意識していませんでした。 C# で上記の最適化された VRotQD を実装して計測したところ 121.214ms でした、 オーバーヘッドがあるとはいえC言語版を呼び出す場合と比べても 20ms 程度しか 違いが無いので、確かに最近は.NETでも十分なパフォーマンスが得られると考えても 良いかもしれません( それでも計測処理も含め全てC言語で組んだ場合より2倍も時間が 掛かってはいますが…( 静的最適化強し… ) ) 因みに最適化前のゆーすけさんが掲載されたコードそのままでC#環境で計測したところ ゆーすけさんが試された際と同じくVTransformD + MGetRotAxisD使用時( 244.452ms )より 若干遅い 253.436ms でしたので、C# 上でも上記の最適化を行えば 253.436ms -> 121.214ms の高速化ができることになります( ゆーすけさんの環境では 830ms -> 400ms くらいになる? ) C言語で実装したものを DLL から呼び出した場合は 105.062ms なので、C#上で最適化を施した 場合と比べると純粋にC言語化+DLL呼び出しによって得られる高速化効果は14%程度ということのようです > それと厚かましいお願いなのですが、管理者様の検証の結果仮にクォータニオンの方が高速に計算できるという結果になった場合、 > DXライブラリにVRotQの機能を持つ関数を実装していただけないでしょうか? 実装してみましたので、よろしければ関数を追加したこちらのバージョンを お使いください m(_ _)m https://dxlib.xsrv.jp/temp/DxLibVCTest.exe // VisualC++ 用 https://dxlib.xsrv.jp/temp/DxLibBCCTest.exe // BorlandC++ 用 https://dxlib.xsrv.jp/temp/DxLibGCC_DevCppTest.exe // Dev-C++ 用 https://dxlib.xsrv.jp/temp/DxLibGCC_MinGWTest.exe // MinGW 用 https://dxlib.xsrv.jp/temp/DxLibDotNet.zip // .NET用 https://dxlib.xsrv.jp/temp/DxLibMakeTest.exe // ソース (中身を既存のライブラリのファイルに上書きして、BCCをお使いの 場合は『再構築』を、VCをお使いの場合は『リビルド』を、 Dev-C++をお使いの方は「Rebuild All(Ctrl+F11)」をして下さい) 因みに double 型を使用するので、VGetD などと同じく VRotQD となっています ( VRotQ は float 型となっています )
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.6 )
名前:ゆーすけ 日時:2016/08/14 14:38

ありがとうございます。 .NET用のDLLをダウンロードして置き換え、サンプルコード中のVRotQ関数をDX.VRotQD関数に置き換えたところ 回転行列使用時が570ms前後、クォータニオン使用時が380ms前後と、最初のコードと比べるとクォータニオン使用時の性能は ほぼ倍くらい、DXライブラリの回転行列使用時と比べても5割増しで速くなりました。 目的のプログラムのVTransformD + MGetRotAxisDをVRotQDに置き換えたところ、こちらも5割増しほどではありませんが 多少速くなったことが確認できました。 ところで3点ほど気になったのですが 1. 管理人様のコードの中で、引数として与える軸ベクトルの大きさが閾値未満の場合決めうちで(-1,-1,-1)というベクトルを 結果として返すようにしているようですが、これには何か理由があるのでしょうか? (double型の計算誤差のせいでこれより小さな軸ベクトルだとマトモな結果が返せないから、とか?) 2. ベクトルの大きさやsin、cosを計算する際に_SQRTD、_SINCOSDというキーワードが出てきていますが、これは関数でしょうか? こちらのVC++環境では上記のキーワードは「識別子が定義されていない」とエラーになっていてちょっと気になっています。 平方根や三角関数の計算であればmath.hをインクルードしてsqrtやsin,cosといった関数を使えばいいのでは?と思ったもので・・・ 3. 回転行列使用時の所要時間も200msくらい短縮された感じなのですが、VTransformD + MGetRotAxisDについても 何らかの最適化を施されたのでしょうか?
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.7 )
名前:管理人 日時:2016/08/15 03:11

> 1. 管理人様のコードの中で、引数として与える軸ベクトルの大きさが閾値未満の場合決めうちで(-1,-1,-1)というベクトルを > 結果として返すようにしているようですが、これには何か理由があるのでしょうか? > (double型の計算誤差のせいでこれより小さな軸ベクトルだとマトモな結果が返せないから、とか?) はい、限りなく 0 に近い値の平方根を計算しようとするとエラーになるので、このような処理を行っています > 2. ベクトルの大きさやsin、cosを計算する際に_SQRTD、_SINCOSDというキーワードが出てきていますが、これは関数でしょうか? はい、こちらはDXライブラリ内部の関数で、それぞれ double 型の値を引数にとる平方根を計算する関数と、 角度( ラジアン値 )を double型の値で引数にとって、その sin と cos の値を取得するための関数となっています > 平方根や三角関数の計算であればmath.hをインクルードしてsqrtやsin,cosといった関数を使えばいいのでは?と思ったもので・・・ VisualC++ 6.0 などの昔の Visual C++ のバージョンと最近の Visual C++ のバージョンで sqrt や sin, cos の 実装が異なるので sqrt や sin, cos などの関数は VisualC++ 6.0 でコンパイルした DxUseCLib.lib という別の lib ファイルを使用して使うためだったり、sin, cos については 32bit 環境では更にインラインアセンブラを 使用して sin と cos の戻り値を同時に取得できるニーモニックを使用するためだったりなどの理由から独自の関数名になっています > 3. 回転行列使用時の所要時間も200msくらい短縮された感じなのですが、VTransformD + MGetRotAxisDについても > 何らかの最適化を施されたのでしょうか? いえ、少なくとも「今回の機会に最適化した」などのことはありません 謎ですね…
メンテ
Re: MGetRotAxisとクォータニオンの速度差 ( No.8 )
名前:ゆーすけ 日時:2016/08/15 21:58

なるほど、ちょっと見慣れないコードだったので何かと思ったのですが関数だったのですね。 疑問が解けてすっきりしました。 回転行列関係の関数については特に最適化はしてないとのことで少し速くなった理由は謎ですが、 目的は達成できたのでとりあえずは気にしないことにします。 いろいろと対応ありがとうございました。 DLLの方は試行錯誤していますので引き続きよろしくお願いします。
メンテ

Page: 1 |

題名
名前
コメント
パスワード (記事メンテ時に使用)

   クッキー保存