グラフ描画のアルゴリズムについて。

[戻る]
ロケットサラダ 2007/07/23(月) 19:32:51
VC++6.0 MFC ダイアログベースです。

計測機器で測定したログ(温度)をグラフにするプログラムを作成しています。
作成したプログラムをスペックの低いPCでデバッグしたところ、処理が重すぎ、
描画されないまま時間だけだ過ぎてしまい、グラフの時間がずれてしまう不具合があり
ました。
  
根本的に改善する必要があると考えているのですが、
どのような方法があるのか、世間一般に出回っているデータロギングソフト(表示付
き)は
どのようなやり方をしているのか等アイディアをいただけませんでしょうか?
・精密な精度は必要ありません。
・1時間に10分程度の遅れが出てしまっています。
・時間軸は現在4時間まで表示させる事が出来るのですが、24時間まで表示できるように
拡張したいです。
  
  
私の考えたアルゴリズム(バグ)は下記考え方です。
・SetTimer()で1秒間隔にデータを計測機器から読み出し、
 LogData[14400]の配列(1秒に1プロットして4時間分)に値を保存します。

・10秒毎に、InvalidateRect(NULL, FALSE); を使い、
  無効領域を作り Draw()を呼び出します。
 
・pDC->MoveTo(PlotX, PlotY);
 pDC->LineTo(PlotX, PlotY);
  を使い測定値 LogData[14400] から計算したグラフのX位置、Y位置を14400回ループさ
せて、
 グラフにしています。

よろしくお願い致します。

たいちう 2007/07/23(月) 19:52:36
思いつきで回答します。
まず、ログを取るのは問題ないんですよね?
ログは取れている(もしくは、描画を諦めればログは取れる)ことを前提とします。

処理の重さとスペックが判らないのですが、よほど無駄な描画がないでしょうか?
折れ線グラフ程度のものを想像すると、10秒毎の描画ができないのはよほど遅いと
思います。遅いのは描画か、描画のための計算部分か、切り分けできますか?

描画を速くするためのアドバイスは、もっと具体的な情報がないと、
私以外の人にも難しいと思いますよ。



描画に手を加えないとして、15秒毎・30秒毎の描画ではどうなりますか?
それでも、顕著な遅れが出るならば、処理の遅さ以外の問題も考えなくてはならないか
も。

描画処理が開始される時に、現在時刻をもとにして、描画すべきデータを決めても
良いと思います。(描画が追いつかなくなったら、少し飛ばします)

Blue 2007/07/23(月) 20:53:16
SetTimerだと処理の時間もプラスされてしまうので多ければ多いほど遅れていってしま
うでしょう。

http://www.asahi-net.or.jp/~uk8t-ktu/wincode/global/0003.htm
を参考にしてみてください。

ぼっれ 2007/07/23(月) 22:32:21
それは

borrel 2007/07/23(月) 22:41:44
長時間表示するようなので、10秒毎に更新する部分だけ再描画すれば軽くなるのではないでしょ
うか?

borrel 2007/07/24(火) 10:14:23
仕事上、データロガーアプリを作成しましたが、1秒毎の描画でもCPU使用率が数%程度で収ま
っています。通信プログラムでの不具合等はないですか?

ロケットサラダ 2007/07/24(火) 10:17:47
たいちうさんありがとうございます。

>ログを取るのは問題ないんですよね?
・はい、おっしゃる通りです。描画を諦めればログが取れる状態です。

>処理の重さとスペックが判らないのですが、よほど無駄な描画がないでしょうか?
・処理遅れが出たPCは celeron 500MHz, メモリ128MB です。
・横幅700ピクセル程度のグラフ枠の中に、LogData[14400]ものデータを
 書き込もうとしているので、
  ここが少し無駄な気がします・・。
 データを間引いて、配列数(ループ数)を減らすことで、
 ある程度改善できると思うのですが、根本的に書き直すことも考えています。

>遅いのは描画か、描画のための計算部分か、切り分けできますか?
・調べてみます。
・計算は出来るだけループ外で計算できる部分は計算しているつもりなんですが、
 変数はdoubleで重い計算をさせてしまっています。
for(PM = 0; PM <14400; PM++){
 PlotX = G - F * PM;    
 PlotY = ((A) * (1 - (LogData[PM]/10 - B) / (C - B)));
// A〜Gまでの英数字はdouble型でグラフのピクセル数、表示温度幅等を
// ループの外で計算した値です。
}

>描画に手を加えないとして、15秒毎・30秒毎の描画ではどうなりますか?
・タスクマネージャーでCPU使用率を見ていると、
 10秒(描画)毎に使用率が100%になっています。
 試せていないのですが、15秒毎、30秒毎でも描画の瞬間は処理遅れが
 出ると思います。

>描画処理が開始される時に、現在時刻をもとにして、描画すべきデータを決めても
>良いと思います。(描画が追いつかなくなったら、少し飛ばします)
・SetTimer()で1秒毎にデータを保存してるので、データ数=測定時間[s]で、
 グラフを書いていました。
 データ数=測定時間[s]の考え方は改善した方が良いようですね。

Blueさんありがとうございます。
WM_TIMERメッセージ『終了後から』何ミリ秒待つかという方法で呼び出されるの
で・・。
重い処理をしているので、グラフの時間が遅れてしまうのは、これが原因ですね。
処理を軽くすれば良い。と言う単純な解決は難しいのが解りました。
今は4時間ですが、今後24時間表示可能にした場合ズレ幅が多くなるのが目に見えていま
す。

ぼっれさんありがとうございます。
>それは
私の提示している情報ではコメントし難く申し訳ありません。

borrelさんありがとうございます。
>長時間表示するようなので、10秒毎に更新する部分だけ再描画すれば軽く
>なるのではないでしょうか?
更新毎に、グラフを完全に消して全て書き直しています。
前に書いた線を生かして、少しずつ線を追加するようにグラフをかけないか考えたので
すが、
わからなかったため、毎回全ての線を書き直しています。
右端に一番新しいデータを表示し、SetTimer()毎に前のデータは、
『 グラフの横幅 / 14400 』左にずれます。その繰り返しになっています。
更新する部分だけ再描画するにはどうしたら良いのでしょうか?

ロケットサラダ 2007/07/24(火) 10:44:21
borrelさんありがとうございます。

>通信プログラムでの不具合等はないですか?
通信は正常に出来ているようです。
グラフ表示するのとは別に、CSVファイルにログデータを保存しているのですが、
全く別のスレッドで行っているので、グラフ表示が遅れても、この保存されたデータは
正常でした。

borrel 2007/07/24(火) 10:54:57
毎回全範囲を計算して描画するとCPUへの負荷がかなり重くなるはずです。
一度描画したものをデータとしてではなく、ビットマップとしてとらえて、それを移動するよう
にすれば、軽くなると思います。

rs1421 2007/07/24(火) 12:01:16
こういう場合は、いわゆる裏画面を用意して、そこに最新データのみ追加描画し、
OnDraw()ではBitBltで裏→実画面に転送するのが一般的です。
BitBltによる転送方法を工夫することで、スクロール表示やスケーリングにも対応
することもできます。

ロケットサラダ 2007/07/24(火) 12:12:38
borrelさん、rs1421さんありがとうございます。
  
>ビットマップとしてとらえて、それを移動するようにすれば
>BitBltによる転送方法を工夫することで

アドバイスありがとうございます。
早速ビットマップについて調べて試してみようと思います。

オレンジフィッシュ 2007/07/24(火) 17:38:16
「裏画面」とか「ダブルバッファ」とかで検索すると良いです。
私も裏画面(メモリデバイスコンテキスト)を作成する方法が良いと思っています。
タスクマネージャの「CPU使用率」のグラフを見れば分かると思いますが、縦線が
更新のたびにずれていきますよね。この方法だと更新部分だけを描画して裏画面
から表画面へ転送(BitBlt)すれば良いので早いと思います。

それから SetTimer() 関数だけでは絶対にずれます。
前、簡単なストップウォッチを作った経験がありますが1時間の計測で10分の
ずれが出たと思います。5分間の計測でも20秒ものずれだったと思います。
そこで GetTickCount() 関数と組み合わせました。

仕組みは
(1)SetTimer() 関数で 0.1秒間隔のタイマー
(2)タイマー部で現在の GetTickCount() 値と前回の GetTickCount() 値との差が
 1000ms以上になったらカウント(ログデータ保存)を行う
こんな感じです。

この方法ならかなり精度が良くなりました。
ストップウォッチでのお話ですが1時間の計測で1分以内だったです。秒数は忘れたが。
もう少し精度を良くしたいのなら timeGetTime() を使います。
この関数なら timeBeginPeriod()、timeEndPeriod() で精度を設定できます。
(1msに設定して使う)

その他
高精度タイマとしてQueryPerformanceFrequencey() と QueryPerformanceCounter()を
使う方法もる。

あと SetTimer() 関数の最後の引数にコールバック関数を指定して使います。
WM_TIMER だと他のメッセージが入っている場合はその処理がすべて終わった後に
WM_TIMER の処理が行われる仕組みなのでコールバック関数を使って下さい。

こんな感じです。
参考になるかな。

ロケットサラダ 2007/07/24(火) 18:29:35
オレンジフィッシュさんありがとうございます。

詳しく教えていただき、とても参考になりました。
早速検索して勉強している所です。
教えていただいた方法が実現できたら、24時間分のログも負荷が少なく表示できます
ね!!

>1時間の計測で10分のずれが出たと思います。
私も、古いPCで動作させた時、1時間で10分程度のズレがありました。

rs1421 2007/07/24(火) 23:51:21
時間の管理ですが、計測器側で測定間隔を設定しているのならば、PC側では
データ数×測定間隔=時間とすべきだと思います。
PC側でどうがんばっても、別の時間ベースを使用している限りは、どこかで
ずれはでてくるので、測定器側の時間を正とするのが普通です。

もちろん、測定器側の時間にも誤差があるので、長時間の測定を行うと真の
時間とのずれが出てきますが。

ロケットサラダ 2007/07/25(水) 09:25:36
rs1421さんありがとうございます。

計測機器から一定間隔でデータが送られてくるわけではなく、
パソコン側から送信要求を送った時のみ、
計測機器が現在の測定値(温度)を、パソコンに送ります。

なので、今回は、パソコンの時間を使って管理するしかない状況です。
時間軸については、SetTimer()とGetTickCount()を使用してみるつもりです。

BitBltの転送方式を工夫して処理が軽くなれば、時間のズレも少なくなると
考えています。(願っています。)  

PATIO 2007/07/25(水) 10:07:23
前回の値の取得時刻を取得しておいて
描画処理が終わったタイミングで残りのミリ秒数を計算して調整すれば、
少しは調整できそうですけれどこれでも限界は有りそうですね。
ある程度のズレが見えた時点で補正する処理は必要かもしれません。
とは言え、PCの時計の範囲でしか補正はできないのでそれが限界だと思います。
いずれにしても描画負荷を最小限に抑えてタイマー間隔を調整できる程度には
しないと難しいでしょうねぇ。
あと考えるとしたらデータの取得部分を完全に別スレッドにしてしまって
グラフ描画よりもこちらの優先度を上げるくらいかなぁ。
グラフはメインスレッドでタイマーを掛けて処理するようにして
CPUの手がすいた時に描画させるとか。
それにしても描画負荷を最小限に抑える努力は必要でしょうね。
既に紹介されているダブルバッファを使えばかなり負荷を軽く出来ると思いますよ。

ロケットサラダ 2007/07/25(水) 13:56:49
PATIO さんありがとうございます。

みなさんがおっしゃるように、時間の制度はかなり厳しいみたいですね。
今回は、24時間測定して、2分程度のズレに抑えることを目標にします。

やはりまずは、ダブルバッファを使い負荷を軽くするのが先決ですね。
その後、時間の制度を良くしたり、スレッドを使ったり改善させていきます。

 
回答して下さったみなさんのおかげで、どうしたら良いのか解りました。
ここで一旦解決とします。
本当にありがとうございました。

ロケットサラダ 2007/07/25(水) 13:58:03
[[解決]]
解決チェック忘れです。

古いスレッドには追加発言できません。