- ベストアンサー
マルチスレッドについて。
今、大きな配列を元に処理を行うプログラムを作成しています。 シングルスレッドでも十分速度を向上するようチューニングに成功しましたが、マルチスレッド化をすればさらに速度を向上させることができるだろうと考え、先日マルチスレッドかに成功しました。 しかし・・・奇妙な現象が起こりました。 マルチスレッドで性能を引き出すには、排他制御はないほうが良いと考え、メモリは食いますがスレッドに与える入力情報(大きな配列)を2つ用意し、排他制御なしの2スレッドを実行できるようにしました。しかしやはりメモリを消費しすぎてしまうため、配列にアクセスする部分のみ排他制御を行うようクリティカルセクションを設定し入力情報を2スレッドで共有して処理を行うよう組み替えました。 結果、やはり排他制御なしの場合よりはるかにスピードダウンしてしまい、シングルスレッドより少し早い処理時間で終了してしまいました。 余りにも悔しいため、ちょっと危険な実験だとは思いましたが、入力情報を2つのスレッドで共有しているにもかかわらず、排他制御の部分、つまりクリティカルセクションを取り除いて実行してみようと考えました。予想としては同時にアクセスし衝突が起きてエラーで停止してしまうと考えましたが・・・・・・ 結果なぜかエラーなく処理をし続け、普通に終了してしまいました。 これはなぜでしょう? 偶然にも共有情報に同時にアクセスすることがなかったためでしょうか?
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
排他制御しないというよりは、同期/ロックしないのがコツというか、 単純にクリティカルセクションを取ったら唯のバグですから。 # ブレーキが邪魔だとブレーキ外してアクセルを踏むようなもの。 (小物は変に弄ってもかえって重くなることもあるので)規模にもよりますが、 その配列自体を二分割して最後に結合するようなアルゴリズム (処理中は排他する必要がなくなる)に変更するとか、 読み出し回数が圧倒的に多いならReaders/Writerロック作るとか、 リードオンリオブジェクトにするとか。そういう組み換えを考えられえてみては。 どう考えても、巨大な単一の配列を使ってる時点で、 マルチスレッド用に最適化された設計/アルゴリズムではありませんから。 目先/小手先でどうこうするより、まずアルゴリズムを練るのが常道かと。 # プロファイリングとかして見てますか。 # 後は、キャッシュヒット率とか考慮されてますか。
その他の回答 (3)
- galluda
- ベストアンサー率35% (440/1242)
がると申します。 ちょっとご自身のお立場が不明なので「お仕事でコーディングされている」前提とします。 > マルチスレッドで性能を引き出すには、排他制御はないほうが良いと考え これは確定でNGです。thread化したプログラムできちんと排他処理をしていないのは「エラーが出るプログラムです」と明記しているようなものなので。 性能云々以前の問題として、排他制御は「必ず」やってください。 > 結果なぜかエラーなく処理をし続け、普通に終了してしまいました。 > これはなぜでしょう? > > 偶然にも共有情報に同時にアクセスすることがなかったためでしょうか? 偶然、です。まぁ2threadくらいなら「たまたまぶつからなかった」んでしょう。 経験的には、2桁前半くらいなら、排他制御をしなくても(或いは雑な排他制御でも)「ある程度」動くみたいです。 3桁になるといきなりぶつかり出しますが。 いずれにしても、threadできちんと排他制御をせずに、というのは、あらゆる観点から「お勧めできない」ので。 趣味であればそれはそれで「面白い」と思いますが、お仕事であれば「絶対にやってはいけない」と思います。
補足
返答ありがとうございました。 失礼なので申し訳ないですが、疑問が解決したので、一応一番上の回答者様のお礼にまとめて記述します。
- zwi
- ベストアンサー率56% (730/1282)
それはたぶん、共有している情報が同時アクセスしてもハングアップにつながらない情報だったからでは無いでしょうか? 同じメモリアドレスに同時アクセスすると言っても、メモリにアクセスできるのは、瞬間的にはどちらかのスレッドだけです。 クリティカルセクションが必要な理由は、以下の様な場合です。 簡単にするためスレッドの処理は、 a++; だけです。 (1)int a = 1;でaが0x800000番地だったとします。 (2)番地0x800000からint値をCPUレジスタに読み出す(スレッドA) (3)スレッドA→B切り替え (4)番地0x800000からint値をCPUレジスタに読み出す(スレッドB) (5)CPUレジスタでint値に+1を行う(スレッドB) (6)番地0x800000にCPUレジスタのint値を書き出す(スレッドB) (7)スレッドB→A切り替え (8)CPUレジスタでint値に+1を行う(スレッドA) (9)番地0x800000にCPUレジスタのint値を書き出す(スレッドA) (10)printf("a=%d\n",a);(スレッドA) (11)スレッドA→B切り替え (12)printf("a=%d\n",a);(スレッドB) で結果は、どちらのスレッドもa=2と表示されます。 ちゃんと排他処理ができていればa=3となるべき処理ですよね。 ちゃんと処理できてませんが、エラーになるわけではありません。結果はちゃんと合っていますか?もし合っていたら単なる偶然だと思ってください。
お礼
返答ありがとうございました。 失礼なので申し訳ないですが、疑問が解決したので、一応一番上の回答者様のお礼にまとめて記述します。
- MrBan
- ベストアンサー率53% (331/615)
> 結果、やはり排他制御なしの場合よりはるかに > スピードダウンしてしまい、シングルスレッドより > 少し早い処理時間で終了してしまいました。 読んでいる限りでは、妥当だろうと納得できる結果です。 コードを見ていないので、ご自身も意図せず排他されてる、 または排他不要、読み出ししかしていない等の可能性も ゼロではないと思いますが、普通に考えてただの偶然です。 たまたま発生頻度が低かったか、発生しても害がないコードだったか、 害に発生に気づかなかったか、いずれかでしょう。 > 予想としては同時にアクセスし衝突が起きてエラーで停止してしまうと考えましたが・・・・・・ 配列の中身のデータに不整合が出た場合にそれを検出したり、 エラーを出したり、停止したりというコードを書いているのですか? それらがなくて、単純に同時アクセスが発生しただけなら、 衝突してもエラーなんて出ませんし、停止もしません。 スレッド間の排他制御はプログラマの責任です。 排他が漏れても、おかしな値のまま処理がそのまま動くだけですので、 「実は結果がおかしくなっている(が気づかなかった)」という可能性もありそうに思います。
お礼
返答ありがとうございました。 失礼なので申し訳ないですが、疑問が解決したので、一応一番上の回答者様のお礼にまとめて記述します。
お礼
返答ありがとうございました。 実はマルチスレッドの概念を自分で勘違いしていました。 MrBan様のおっしゃられた通り、共有する配列は読み出しのみ行っていました。自分は共有する配列を同時に読みに行ってしまった場合衝突が起きるのではと考え、毎回の読み出しに排他制御を行っていました。 しかしまずマルチスレッドで同時に・・・なんてことは起こりませんよね。回答者No.2様がおっしゃられるように矛盾のみ発生するもので、同時になんてあるわけがないし、別に読み出しに排他制御は必要ありませんね。 今回の処理は入力の配列を複数のスレッドに渡し、共有変数への書き込みは行わない処理で、各スレッドはそれぞれ入力として与えられた配列から別々に結果を出力するプログラムなので、排他制御はまず不要と考えるべきでしょうね。MrBan様がおっしゃられる通り、巨大な単一の配列を共有して読み書きを行うとなるとそれはアルゴリズムを改良すべきでしょうね。 皆様返答ありがとうございました。