トップページ⇒tech
254コメント91KB

プログラムの高速化について語る

■ このスレッドは過去ログ倉庫に格納されています
0001デフォルトの名無しさんNGNG
おい,貴様ら!

速いプログラムをつくるテクニックを教えて下さい。
0204妄想君NGNG
忘れてた、上の書いたのは妄想君です。
これと妄想matrix.hの m1 = m2*m3を比べてみようかと。
0205妄想君NGNG
template <typename T = complex>
class matrix
{
private:
int m_row, m_col;

public:
T *m_pMatrix;

explicit matrix(int row = 0, int col = 0) : m_row(row), m_col(col) { NewMatrix(); } // デフォルトコンストラクタ

matrix(const matrix<T> &m) // コピーコンストラクタ
{
m_row = m.GetRow(), m_col = m.GetCol();
NewMatrix();

int n = m_row*m_col;
T *p1 = m_pMatrix, *p2 = m.m_pMatrix;

for (int i = 0; i < n; i++, p1++, p2++) {
*p1 = *p2;
}
}

~matrix() { DelMatrix(); } // デストラクタ

void NewMatrix() { m_pMatrix = new T[m_row*m_col], assert(m_pMatrix != 0); } // 行列の領域確保
void DelMatrix() { delete [] m_pMatrix; } // 行列の領域開放

int GetRow(void) const { return m_row; } // プライベートメンバへのアクセス
int GetCol(void) const { return m_col; }
void SetSize(const int row, const int col) { DelMatrix(), m_row = row, m_col = col, NewMatrix(); }

T &operator () (int row, int col) // 配列演算子
{
assert(row >= 0 && col >= 0 && row < m_row && col < m_col);
return m_pMatrix[row*m_col + col];
}

const T &operator () (int row, int col) const
{
assert(row >= 0 && col >= 0 && row < m_row && col < m_col);
return m_pMatrix[row*m_col + col];
}

matrix<T> &operator = (const matrix<T> &);
};
0206妄想君NGNG
template <typename T>
matrix<T> &matrix<T>::operator = (const matrix<T> &m) // 行列 =行列
{
assert((m_row == m.m_row) && (m_col == m.m_col));

if (&m != this) {
if (m_row == 0 && m_col == 0) SetSize(m.m_row, m.m_col);

int n = m_row*m_col;
T *p1 = m_pMatrix, *p2 = m.m_pMatrix;

for (int i = 0; i < n; i++, p1++, p2++) *p1 = *p2;
}

return *this;
}

template<typename T>
matrix<T> operator * (const matrix<T> &m1, const matrix<T> &m2) // 行列×行列
{
int row1 = m1.GetRow(), col1 = m1.GetCol(), row2 = m2.GetRow(), col2 = m2.GetCol();
int n1 = row2*col2 - 1, n2 = col2*(row2 - 1);
matrix<T> m3(row1, col2);
T tmp, *p1 = m1.m_pMatrix, *p2 = m2.m_pMatrix, *p3 = m3.m_pMatrix, *p4 = p2;

assert(col1 == row2);

for (int i = 0; i < row1; i++, p1 += col1, p2 = p4) {
for (int j = 0; j < col2; j++, p1 -= col1, p2 -= n1, p3++) {
tmp = 0;
for (int k = 0; k < col1; k++, p1++, p2 += col2) {
tmp += *p1 * *p2;
}
*p3 = tmp;
}
}

return m3;
}
0207妄想君NGNG
↑行列遅いバージョン

つかれたので高速化したのは明日…
つーかようやく今帰宅

ふと思ったけど連続書き込みで荒らしみたい
0208デフォルトの名無しさんNGNG
参考になるからどんどんやってもらえると嬉しいです、そのうち僕も書くかも。
でも今は時間がない。(;;)
0209デフォルトの名無しさんNGNG
STLportのvararrayは問題ありか
http://www.tietew.jp/cppll/archive/274
0210145NGNG
非常にあたりまえすぎて申し訳ないけど,
汎用的なやつに対しては上のようなものを書いて,
2x2, 3x3, 4x4 は別に用意するっていうのが吉かなあと思います.
この方式だと 2x2 とかに関してはクラス + 演算子のオーバーロードを
用いたやつでも結構いい性能がだせます.

あと new/delete を malloc/free に変えてみると, 速度の点で
おもしろいことがおこるかもしれません. これは行列のサイズ依存かも
知れないけど.
0211妄想君NGNG
mallocって初期化しないからnewより速いんだったかな?わからん、厨房丸出し。

4*4くらいまでは別に用意するのは禿道、
2*2でオーバーロードで十分なのは作業用の配列が必要ないのが原因と思われ。

ところで行列乗算で乗算回数を減らすアルゴリズムを適用したんだけど
馬鹿でかい(200*200くらい)複素行列くらいじゃないと効き目がないことに落胆の色隠せず。
書き方下手なんじゃろうか。

結局上に書いたのをちょっと書き換えて作業用配列も参照渡しするのが
実用的な行列に対しては自己最速。

>>203 に書いたのと比較したんだけど
203の書きかたがまずいのか妄想Verの方が速かった。
vararray使った速いソースきぼんぬる。
0212145NGNG
>>211
>2*2でオーバーロードで十分なのは作業用の配列が必要ないのが原因と思われ。
ってどういうこと? 行列の各要素は配列に入れてるんだけど, ひょっとして
x, y, z, w とかいう変数に入れたほうが速い ?

2x2, 3x3, 4x4 で速い要因としては
1. ループが手動で全部展開できる.
2. コンストラクタも matrix(double a11, double a12, double a21, double a22)
とかいう変態的な形が使える.
3. malloc/free がいらない.
4. インラインも展開 OK
5. 逆行列も, 行列式も決めうち可能.
というところかと(ほんとに当たり前のことばかり書いてごめんなさい).
いくつかは賢いコンパイラがよきに計らってくれるんでしょうが ...
0213デフォルトの名無しさんNGNG
ちょうどいい具合にプリフェッチが効いている状態かも
0214デフォルトの名無しさんNGNG
newはほぼ直接mallocを呼び出すのと同じじゃないかなあ
規格上0クリア必須だっけ?>new
0215デフォルトの名無しさんNGNG
別に0クリアはないけどコンストラクタ呼び出すからmallocの方が手間は減る

けどmallocもANSI準拠だと明示的に未開放でもexitした時に
OSにメモリを返す機構が必要なんで多少のオーバーヘッドはある

速度稼ぐんであればOSべったりのメモリ関数つかうのが良いと思われ
0216デフォルトの名無しさんNGNG
まだいい加減なこというやついるんだな
0217妄想君NGNG
>>212
要するに演算後の要素を直接与えられた行列の要素を使って
書き出せるってことだよねん?
でも考えてみりゃ*=使わなければ値返す行列が必要か。
結局高速にしたい場合はコンストラクタ作るオーバーヘッドなくすのに
mulみたいにするんだろうか、あるいはここまで最適化してくれるのかな。
試してないけどしてくれそうだね。
0218デフォルトの名無しさんNGNG
>と明示的に未開放でもexitした時に
>OSにメモリを返す機構が必要
はつみみです。
0219145NGNG
>>217
>要するに演算後の要素を直接与えられた行列の要素を使って
>書き出せるってことだよねん?
ごめんなさい... 日本語が分からない ...

>結局高速にしたい場合はコンストラクタ作るオーバーヘッドなくすのに
>mulみたいにするんだろうか、あるいはここまで最適化してくれるのかな。
>試してないけどしてくれそうだね。
アセンブラのコード見てないへたれだけど, 最適化してくれると思ってる.
最適化してくれないと complex もうまくいかなかっただろうし.

任意の次元の行列はどうしたらいいのか分からないので, 妄想さんに期待.
0220デフォルトの名無しさんNGNG
>任意の次元の行列はどうしたらいいのか分からな
テンプレートでどうぞ。い
0221デフォルトの名無しさんNGNG
>>216
215 にはコテハン「知ったかクン」を贈呈したいと思うが、どうか?

とりあえず Inside the C++ Object Model とエキスパート C プログラミングを
読んで出直して来い。
0222妄想君NGNG
>>145
漏れの素人考えでは
要素はmalloc使わずに
double e11, e12, e21, e22;
と書けるし
乗算に限らず逆行列でもmallocで作業用の行列をつくることなく直接
det = 1/(_e11*_e22 - _e12*_e21);
e11 = det*e22;
とかfor文使わずに書けるし、これなら実行速度が速くなるかなぁと。
って>>212に書いてあるまんまじゃん(鬱

一般行列の乗算は
>>206の
matrix<T> operator * (const matrix<T> &m1, const matrix<T> &m2) // 行列×行列
を
matrix<T> &mul(const matrix<T> &m1, const matrix<T> &m2, matrix<T> &m3)
にして
matrix<T> m3(row1, col2);
を消せばいいんじゃないでしょうか。

いろいろやってみたけど、小さい行列は
>>212のように matrix2とかmatrix3とか専用クラス作って
中くらいの行列(n = 5 〜 100)は↑に書いたmulで
大きい行列(n > 100)はブロック化するのでfinal answer。

STLのvalarrayは...速いの組める人教えて下さい、おながいします。
>>203みたいに使うとどうも遅くて。
0223妄想君NGNG
ちなみに大きい行列の場合は
妄想mulよりも>>203の方が速いです。

キャッシュが絡んでるような気がしますがよくわかりません。
0224デフォルトの名無しさんNGNG
http://www.oonumerics.org/blitz/index.html
0225デフォルトの名無しさんNGNG
>>224
照れ屋さんな貴方ってステキ
0226145NGNG
>>222
>matrix<T> &mul(const matrix<T> &m1, const matrix<T> &m2, matrix<T> &m3)
で, 満足なんですか ? と煽ってみる(w

224 にもあるように一般的な大きさの行列に対してはテンプレートの
再帰的な利用(expression template)になるんですかね ?
ていうか Blitz++ を素直に使え ?
0227煽られている妄想君NGNG
↑再帰的なテンプレートをうまく使えば展開後のソースにfor文が含まれないから
速くなるってことなんだろうか(しったか。)
いや、C++始めたばっかなんでそんな高度なことは考えたこともなかったYO。
時間あれば勉強したいが当分…

ちなみにBlitz++はVC++なので使用不可〜。
0228煽られている妄想君NGNG
http://www.microsoft.com/japan/developer/library/dsmsdn/msdn_090798.htm
もっとテンプレートの勉強にいいページない?
0229デフォルトの名無しさんNGNG
>>227
Generic Programming with C++ Template
http://pc.2ch.net/test/read.cgi/tech/1008593126/l50

のスレでもそのテの話題があるよん。課題は汎用性と効率の両立。
0230!=189NGNG
>>190
遅レスですが, Intel コンパイラの valarray って
きわめてふつうな実装に見えます. C++ の入門の本にのっているやつと
あんまり変わらないような ? 違うんかな, 識者のレス求む.

GNU の valarray は遅延評価をやっているみたいです.
全部参照でデータを持っていて, 代入演算子のところでばばっと
演算しているみたい.

>> 227
GNU のやつは add(v3, v1, v2) とかと同じくらいの性能がでるので,
汎用的な行列は遅延評価を組み込んだものにすれば, いいのでは ?
0231煽られ妄想NGNG
>>230
遅延評価ってなんですかと厨房全開質問決行
0232デフォルトの名無しさんNGNG
何を計算するかを覚えておいて、
実際に演算結果が必要になった時点で計算するやり方。

配列同士の計算だと、全部が全部必要ってわけでもないでしょ?
0233煽られ妄想NGNG
なるほど、特に長方形行列の積が多い場合は効果が大きそうですね
積を計算する順序でたいぶ計算量が変わりますから
0234デフォルトの名無しさんNGNG
VectorC試した人いる?
もうすぐ出るCマガに記事が載るんでしょ?
0235デフォルトの名無しさんNGNG
http://www.codeplay.com/japanese/
0236数学に弱いプログラマNGNG
話は変わっちゃいますが、三角関数の高速な計算式誰か知りませんか?
sinは、0<=x<=PI/4のとき 0.99959257 * x - 0.1615350 * x * x * x
ってのは知ってるんだけど、それ以外調べても見つからず。。。
とりあえず、sinとcos、arctanについて知りたいんだけど。
何がやりたいのかと言うと、複素数ベクトルの直行座標<->極座標の変換なんだけど、
特にarctanが重過ぎてお話にならないのです。
誰か高速な手法教えてください。

ちなみに、SIMDで処理したいので、できれば分岐とかないアルゴリズムをキボンヌ。
0237デフォルトの名無しさんNGNG
>>236
 入力の角度精度によっちゃあ、テーブル持った方がいいんじゃないか?
0238デフォルトの名無しさんNGNG
>sinは、0<=x<=PI/4のとき 0.99959257 * x - 0.1615350 * x * x * x
>ってのは知ってるんだけど

理解せずに使って平気かい。
それの求め方分かっていれば他のも求められるよ。
0239数学に弱いプログラマNGNG
意外と返信早いのね。

>>237
うーん、さすがにテーブルだと、実用的なサイズに収まらないので、
たとえ160〜300クロックかかってもfpatan呼んだ方がマシかも。
>>238
テーラー展開くらいなら多少は。
でも、その式はテーラー展開を途中で打ち切った式より精度が良いとか。

ちなみに、IntelのHPみたら、なんかSSEで各種数学関数を
近似計算してくれるとかいうソースコードを見つけたので
それで解決するかも。5倍は速いみたい。
ほぼフルアセンブラだから、そのまま抜き出して使えば、
かなりの速度向上が望めるかも。
0240デフォルトの名無しさんNGNG
対称性利用でかなり区間を縮小できる・…ってのはもうやってるか。
また、テーブルを間引いて補間多項式も併用できる。
区間が短ければ次数の低い補間多項式でも誤差は小さく抑えられる>>239
0241デフォルトの名無しさんNGNG
CORDIC --- 望みの精度で絶対値と角度が同時に得られる。

絶対値の誤差 2%、角度誤差 0.5度程度だったような...で
8087に勝った記憶あり。
0242デフォルトの名無しさんNGNG
検討済みだと思うけど
>複素数ベクトルの直行座標<->極座標の変換
自体の回数は減らせないの?
0243デフォルトの名無しさんNGNG
>>236
どれくらいの精度でやりたいの?

整数なら分岐有りだけど
ttp://www.infoeddy.ne.jp/~tensyo/prog/ALGO.HTM

整数だけでやればSIMD並の速度が出る場合もあるよ
0244デフォルトの名無しさんNGNG
age
0245デフォルトの名無しさんNGNG
>>236
きゅーすーてんかい
0246デフォルトの名無しさんNGNG
//逆行列
Matrix Inverse()
{
Matrix d;

//行列式計算
f32 d0 = +( (_11*_22*_33)+(_12*_23*_31)+(_13*_21*_32)-(_13*_22*_31)-(_23*_32*_11)-(_33*_12*_21));
f32 d1 = -( (_01*_22*_33)+(_02*_23*_31)+(_03*_21*_32)-(_03*_22*_31)-(_23*_32*_01)-(_33*_02*_21));
f32 d2 = +( (_01*_12*_33)+(_02*_13*_31)+(_03*_11*_32)-(_03*_12*_31)-(_13*_32*_01)-(_33*_02*_11));
f32 d3 = -( (_01*_12*_23)+(_02*_13*_21)+(_03*_11*_22)-(_03*_12*_21)-(_13*_22*_01)-(_23*_02*_11));

f32 s = (_00*d0+_10*d1+_20*d2+_30*d3);

if (s == 0){
//逆行列が存在しない
d.Identity();
return d;
}
//余因子行列計算と係数乗算
s = 1.0f / s;
d._00 = d0 * s;
d._01 = d1 * s;
d._02 = d2 * s;
d._03 = d3 * s;
d._10 = -( (_10*_22*_33)+(_12*_23*_30)+(_13*_20*_32)-(_13*_22*_30)-(_23*_32*_10)-(_33*_12*_20)) * s;
d._11 = +( (_00*_22*_33)+(_02*_23*_30)+(_03*_20*_32)-(_03*_22*_30)-(_23*_32*_00)-(_33*_02*_20)) * s;
d._12 = -( (_00*_12*_33)+(_02*_13*_30)+(_03*_10*_32)-(_03*_12*_30)-(_13*_32*_00)-(_33*_02*_10)) * s;
d._13 = +( (_00*_12*_23)+(_02*_13*_20)+(_03*_10*_22)-(_03*_12*_20)-(_13*_22*_00)-(_23*_02*_10)) * s;
d._20 = +( (_10*_21*_33)+(_11*_23*_30)+(_13*_20*_31)-(_13*_21*_30)-(_23*_31*_10)-(_33*_11*_20)) * s;
d._21 = -( (_00*_21*_33)+(_01*_23*_30)+(_03*_20*_31)-(_03*_21*_30)-(_23*_31*_00)-(_33*_01*_20)) * s;
d._22 = +( (_00*_11*_33)+(_01*_13*_30)+(_03*_10*_31)-(_03*_11*_30)-(_13*_31*_00)-(_33*_01*_10)) * s;
d._23 = -( (_00*_11*_23)+(_01*_13*_20)+(_03*_10*_21)-(_03*_11*_20)-(_13*_21*_00)-(_23*_01*_10)) * s;
d._30 = -( (_10*_21*_32)+(_11*_22*_30)+(_12*_20*_31)-(_12*_21*_30)-(_22*_31*_10)-(_32*_11*_20)) * s;
d._31 = +( (_00*_21*_32)+(_01*_22*_30)+(_02*_20*_31)-(_02*_21*_30)-(_22*_31*_00)-(_32*_01*_20)) * s;
d._32 = -( (_00*_11*_32)+(_01*_12*_30)+(_02*_10*_31)-(_02*_11*_30)-(_12*_31*_00)-(_32*_01*_10)) * s;
d._33 = +( (_00*_11*_22)+(_01*_12*_20)+(_02*_10*_21)-(_02*_11*_20)-(_12*_21*_00)-(_22*_01*_10)) * s;
return d;
}

余因子行列と行列式による4×4逆行列の算出アルゴリズムを書いてみました。
がんばればSIMDで最適化できそうではあるのですが、
ガウス法による算出とどちらが良いと思われますか。
0247デフォルトの名無しさんNGNG
後50行くらい書いたらコンパイラがcore吐きそうなコードだな
0248デフォルトの名無しさんNGNG
特異行列の逆を単位行列としてしまって良いのかと小一時間問い詰めたい。
0249デフォルトの名無しさんNGNG
>>248
何事もなく過ごせるかと思ったのですが(w
本当は例外投げるべきなんでしょうけど。
0250sageNGNG
>>247
47 は読みましたか ?
まさにあなたの知りたいことがのっていると思います.
0251デフォルトの名無しさんNGNG
てか, 下げそこなった.
0252デフォルトの名無しさんNGNG
>>248
だって、例外投げるようにしたら少し遅くなっちゃうんですもの。
(by 元の行列を返してる不届き者)
0253246NGNG
>>250
今読んだ。うーん流石インテル。
知りたい情報がずばり詳細に。

このやり方は、クラマーっていうのか。
クラマールール英語で言うとかっこイイ。
0254煽られ妄想NGNG
http://www2.tky.3web.ne.jp/~bandai/24net/prog2/tips1.html
VCはダメ?
■ このスレッドは過去ログ倉庫に格納されています