Search
Categories
Articles
Rainmeter関連
ファイル置き場
お知り合いなど

スポンサーサイト

--.--.-- | スポンサー広告

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[Rainmeter-dev] 負荷を減らす取り組み

2010.12.22 | Rainmeter-dev

0 Comments

Rainmeterもアップデートを繰り返し、バグフィックスだけでなく新規能も多く取り入れられました。特に新機能については、いままで出来なかったことをするためのものなので、当然コードサイズも増えるし、出来上がる実行ファイルのサイズも増えるし、実行時の負荷も(処理内容によっては)増えています。ベタ実装なものも多いので、処理が改善できそうな部分については適宜変更を入れています。

例えば、無駄な一時オブジェクト生成をなくしたり、処理に入る前に前提条件をチェックして必要なければ処理しないようにするとか。前者は1.4 betaに入ってから、コンパイル時に生成されたアセンブラコードをチェックしながらいろいろと洗い出して、大分減らしました。ほとんどが文字列絡みです。dllサイズも結構減りました(その分新規能が追加されて、増えてますが)。

後者については、主にフラグを設置して処理が必要かどうかをチェックしていますが、Rainmeterの場合はいろんなものが動的に変化する可能性があるので、適用できる部分は少ないです。むやみにフラグチェックを入れてしまうと、逆に本来の処理を行うよりも非効率的になってしまうこともあるためです。逆に、処理時間がかかるような部分には積極的に取り入れています(例えば画像処理とか、DynamicVariablesで変更された設定を反映させる処理とか)。

そういう部分と比較しても、まだまだ重い部分があります。Rainmeterで実際に一番重い(遅い)部分はどこでしょうか。
縦横サイズの大きなスキンを使っている人にはわかるかもしれませんが、描画(GDI+)です。

GDI+はいろいろと面倒な描画を簡単に使えるので重宝しますが、描画にかかる時間はGDIでDIBSectionを使った場合に比べると、とても遅いです(DIBSectionについては次回説明)。描画サイズが大きくなるほど、または更新間隔が短くなればなるほど、その差はハッキリと体感できるようになります。
(※Measureが大量にあってMeterは少ない・小さいとか、そういったスキンではこの限りではありません)

GDI+な部分についても、個別のMeterごとに、描画用にキャッシュを使うとか、GraphicsPathを使って一気に描くとか、負荷を減らせそうな変更は適宜入れていますが、表示の互換性も考えないといけないので、大胆には弄れません(オフセット位置が1pxズレるだけで表示結果が変わってしまう)。

ただ、描画について考えるのであれば、RainmeterではUpdate間隔ごとに全Meterをバックバッファに再描画し直していることを考慮すべきです。もし「前回の表示内容から何も変更がない」なら、この再描画処理は本来必要なく、描画済みのバックバッファをそのまま使えます。

「変更がない」ことを確認するには、「全てのMeterの設定・使用するMeasure値が変更されていない」ことを確認すれば、目的を達せられそうです。

例えば、スキンを以下のように定義したとします。

[MeterImage]
Meter=IMAGE
ImageName=TestImage.png
W=2000
H=2000

バックバッファには、2000x2000に引き伸ばしたTestImage.pngが描画されます。その後もUpdate間隔ごとにバックバッファへと再描画が繰り返されます。
でも、設定もTestImage.pngもスキンの読み込み・refresh時にしか再読込されませんし、2回目以降は省略して、バックバッファをそのまま使っても問題ないように思えます。

実際に、1.4 betaでは、各MeterのUpdate()時に、再描画が必要かどうか(更新があったかどうか)をチェックして、全Meterが変更なしなら再描画処理をスルーするようにしてあります。

   1:     // Update the meters
   2:     bool bActiveTransition = false;
   3:     bool bUpdate = false;
   4:     std::list<CMeter*>::const_iterator j = m_Meters.begin();
   5:     for( ; j != m_Meters.end(); ++j)
   6:     {
   7:         if ((*j)->HasDynamicVariables() &&
   8:             ((*j)->GetUpdateCounter() + 1) >= (*j)->GetUpdateDivider())
   9:         {
  10:             try
  11:             {
  12:                 (*j)->ReadConfig((*j)->GetName());
  13:                 m_Parser.ClearStyleTemplate();
  14:             }
  15:             catch (CError& error)
  16:             {
  17:                 MessageBox(m_Window, error.GetString().c_str(), APPNAME, MB_OK | MB_TOPMOST | MB_ICONEXCLAMATION);
  18:             }
  19:         }
  20:  
  21:         if ((*j)->Update())
  22:         {
  23:             bUpdate = true;
  24:         }
  25:  
  26:         // Update tooltips
  27:         if (!(*j)->HasToolTip())
  28:         {
  29:             if (!(*j)->GetToolTipText().empty())
  30:             {
  31:                 (*j)->CreateToolTip(this);
  32:             }
  33:         }
  34:         else
  35:         {
  36:             (*j)->UpdateToolTip();
  37:         }
  38:  
  39:         // Check for transitions and start the timer if necessary
  40:         if (!bActiveTransition && (*j)->HasActiveTransition())
  41:         {
  42:             bActiveTransition = true;
  43:         }
  44:     }
  45:  
  46:     if (!nodraw && (bUpdate || m_ResetRegion || m_Refreshing))
  47:     {
  48:         if (m_DynamicWindowSize)
  49:         {
  50:             // Resize the window
  51:             m_ResetRegion = true;
  52:         }
  53:  
  54:         // If our option is to disable when in an RDP session, then check if in an RDP session.
  55:         // Only redraw if we are not in a remote session
  56:         if (!Rainmeter->GetDisableRDP() || !GetSystemMetrics(SM_REMOTESESSION))
  57:         {
  58:             Redraw();
  59:         }
  60:     }

21-24行目で更新があればフラグを立て、46行目で使っています。Meterのどれか1つにでも更新があれば全Meterを再描画します。bUpdateが立っていなくても、m_ResetRegion(!RainmeterShowMeterなどを呼ぶと立てられる)、m_Refreshing(スキン読み込み時とrefresh時に立てられる)のどちらかが立っていれば再描画します。

Meter=IMAGEでは、MeasureNameを使わず、かつDynamicVariablesも使っていなければ、Update()にて更新すべき情報がないため、falseが返ってきます。よって、簡単に再描画をスキップできます。

ただこれは特殊な例で、他のMeterはUpdate()またはDraw()にて描画内容が変化するものがほとんどです。
HISTOGRAMやLINEは横幅分のデータを保持し、さらにそれは横にスクロール描画されていくため、ほぼ確実にスキップすることができません。
その他のMeterでも(Meter=IMAGEも含み)、DynamicVariables=1が指定されているのであればさらに複雑で、Update()/Draw()での変化だけでなく、ReadConfig()での設定値の変化もチェックし続ける必要があります。さらにLuaスクリプト側から動的に変更する機能なんて加えられたら、トラッキングするのも大変です。チェック機構を入れるだけオーバーヘッドが増えて非効率そうです。

とりあえずは、「DynamicVariablesが使われていない」「MeterがIMAGEまたはSTRINGのみで構成されている」あたりが実装できそうなラインでしょうか。Meterはもう少し増やせるかもしれない。でも、結局はMeterのどれか1つでも更新があれば全て再描画になるので、この効果が出るようなスキンは本当に限定的でしょう。EnigmaやGnometerにあったような単純な背景用スキンとか。

 

最後になりましたが、MeterのUpdate()にて、falseが返ってくる条件がもう1つあります。UpdateDividerに2以上を指定したとき、更新回数がその値に到達していない間はfalseが返されます。その間はDynamicVariables=1であろうが設定の再読み込みも起きないので、=0と同様に描画をスキップできます。

[Variables]
Var=red.png

[MeterImage]
Meter=IMAGE
ImageName=#Var#
W=2000
H=2000
ImageTint=FFFFFF60
DynamicVariables=1
UpdateDivider=10
LeftMouseUpAction=!Execute [!RainmeterSetVariable Var blue.png]
MiddleMouseUpAction=!Execute [!RainmeterSetVariable Var yellow.png]

極端な例ですが、10秒間は再描画されません。
ちなみに、ActionのSetVariableの後に!RainmeterRedrawを入れても何の効果もありません。負荷が増えるだけです。!RainmeterRedrawは設定を再読込しないので、Variablesの変更があっても以前に読み込んだ設定のままで描画します。

« [Rainmeter-dev] 負荷を減らす取り組み #2 [Rainmeter] スキン作成時に気をつけてること »

- Comments
0 Comments

管理者にだけ表示を許可する
- Trackbacks
0 Trackbacks


この記事にトラックバックする(FC2ブログユーザー)

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。