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の方は試行錯誤していますので引き続きよろしくお願いします。
|