Quantcast
Channel: ドナドナされるプログラマのメモ
Viewing all 552 articles
Browse latest View live

mozjpegをつかった画像変換ソフトの開発 その4

$
0
0

どのようにマルチスレッド化するかを考え中。

考えられる方針は大きく2つ。

  1. ファイルを1個開くたびに1スレッド作り、1変換をするたびに1スレッドを作り、1個書き出すたびに1スレッド作る。ブルジョワ方針。
  2. 物理メディアごとに1スレッド作り、当該メディアで読み書きするファイルはすべてそのスレッドで処理。CPUコアごとに1スレッド作り、当該コアで処理するデータは(略)。けちんぼ方針。

2.のほうがかっこいい実装だし、スレッドの生成はリソースを食うので極力しないほうがよい。一方、この実装は面倒でもある。そこで、2.の実装が努力に値するのか軽く検討してみた。

今回のソフトは、とにかく大量の画像を変換することを目標にしている。そのため、処理の速さは大事な指標だ。そこで、2.の実装によりどのくらい処理が早くなるかを考えてみた。もしとても早くなるなら、努力に値するはずだ。

1.の方法で生成するスレッド数は、画像ファイル数の3倍。なので1万ファイルを処理するならば3万スレッドほど生成することになる。一方、スレッドの生成にかかる時間はかんたんなテストの結果368.8us程度 @ Ryzen7 3700Xだった。ということは、1.の方式では368.8×10^-6 × 3×10^4 = 1106.4×10^-2 ≒ 11秒ほど遅くなるのか。

f:id:donadonasan:20200204001514p:plain

スレッド生成にかかる時間の計測結果 @ 10000サンプルの平均

1ファイルあたりにかかる合計処理時間が1秒だとすると、処理時間が0.1%ほど伸びる計算だ。これは誤差だなあ。2.は手間がかかる割に早くないようだ。というわけで1の方式にする。


mozjpegをつかった画像変換ソフトの開発 その5

$
0
0

どういうふうにスレッドの制御をするか考え中。今回のソフトは、タスクごとにスレッドを作ってしまうという富豪方針である。ということは、リソースに見合った数のスレッドが常に走っているようにスレッドを生成し続ければよい。これを実現するには、ワーカースレッドが完了した際には制御クラスに通知する必要がある。

通知は、いくつかの方法がある。一つはSendMessage / PostMessageでメッセージを投げること。あとはシグナルを駆使する方法。一番ラクなのはメッセージを投げつける方法だけど、処理時間が心配。

SendMessage / PostMessageにかかる時間

$
0
0

SendMessageやPostMessageにてメッセージを投げてから受け取るまでにかかる時間を調べてみた。Ryzen3700X @ Windows10 Prof. 1909では、SendMessage: 20us, PostMessage: 34usだった。SendMessageのほうが早いのは、WindowProcedureを速やかに呼ぶかららしい(公式ドキュメントのRemarksより)。

SendMessage / PostMessage speed test

f:id:donadonasan:20200208115818p:plain

SendMessageの所要時間測定結果

 

f:id:donadonasan:20200208115924p:plain

PostMessageの所要時間測定結果

 

mozjpegをつかった画像変換ソフトの開発 その6

$
0
0

マルチスレッド関連の実装方法に迷いが生じたので、ちょっと考えを整理する。

■やりたいことは何か

やりたいことはスレッドからスレッドへの通知?いや違う。それは手段であって、目的ではない。やりたいのは、

  1. 処理Aが終了したあと、その結果を使う処理Bを起動する
  2. 処理Aが終了したあと、次の同様の処理A'を起動する
  3. 処理A'、処理Bはリソースが空くまで動作しない

である。つまり条件付きで処理を開始したい、ということになる。

これを実装するシンプルな方法は、こんな感じだろうか。

ThreadA(){ WaitForResource(); Do(); BeginThread(ThreadA); BeginThread(ThreadB);}

ThreadB(){ WaitForResource(); Do();}

この方式の問題点は、ThreadBの速度がThreadAよりも遅い場合である。リソースの空きを待つThreadBがどんどん増えてしまう。最悪、WindowsのHANDLE上限を超えるかもしれない。となると、ThreadBの実行状況に応じてThreadAの実行を制御する必要がある。ThreadAの実行制御はWaitForResource()が担っているので、これがThreadBの状況も監視するようになっていれば良さそうだ。

では、WaitForResource()はどのようなものなのだろうか?要件は以下だろう。

  1. 実行に必要なリソースの空きができるまで実行を停止する
  2. 後続処理の処理状況に空きができるまで実行を停止する
  3. 異なる複数のスレッドからの呼び出しに対応する

これはCSemaphore(セマフォ)で実現できそうだなあ。

■書き込みと読み込みの同時実行防止

書き込みと読み込みを同時に実行すると、遅くなる気がする。なので排他処理にしたい。そして、できれば書き込みを優先したい。これをセマフォでやるには、どうすればいいのだろう?まずは、優先を考えずに擬似コードを書いてみる。

CSemaphore hdd(initial=0,max=1);

CSemaphore CPU(initial=0,max=4);

ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); BeginThread(CPUThread); BeginThread(ReadThread);}

CPUThread(){ Lock(CPU); Calc(); UnLock(CPU); Lock(hdd); WriteFile(); UnLock(hdd);}

このコードはデッドロックは起きなさそうだがWriteFile待ちのCPUThreadが大量にできそうだ。あまりよくない。

ならば、ReadThreadの開始に制限を追加したこれならどうか?

ReadThread(){ Lock(CPU); Lock(hdd); ReadFile(); UnLock(hdd); BeginThread(ReadThread); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU);}

 なお、CPUの数だけReadThreadができうるので、CPUThreadはReadThreadと統合してみた。すっきりはしたが、CPUの空きができるまでファイルを読み込まないというのは悲しい。となると、Lock(CPU)の位置を調整すればいいのか?ファイルを読み込んでからCPU実行待ち状態にしよう。次のファイル読み込みスレッドも、CPUリソースが空くまで待つか。

ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); Lock(CPU); BeginThread(ReadThread); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU);}

 だいぶ良くなった。だがしかし、複数のスレッドがたまたま同時に終了した場合、こんどはReadFileが間に合わなくなりCPUが暇してしまう。できればCPUコア数分ぐらいは先読みしておきたい。ということは、読み込みスレッドのBeginThread実行条件とCPU計算実行条件を分ける必要があるのか。こんな感じ?

CSemaphore ReadBuff(initial=0, max=4+4);

ReadThread(){ Lock(hdd); ReadFile(); UnLock(hdd); Lock(ReadBuff); BeginThread(ReadThread); Lock(CPU); Calc(); Lock(hdd); WriteFile();UnLock(hdd); UnLock(CPU); UnLock(ReadBuff); }

 UnLock(ReadBuff)の位置はもっと前でもデッドロックせずに動作するが、Lock(hdd);より後ろとすることでWriteFileがReadFileよりも先に実行されるはず。うん、これでOKかな。あとは実装だ。

Visual Studioでクラスが登録されていませんというエラーが出る

$
0
0

Visual Studioのリソースエディタで、ダイアログのクラスを追加しようとして「クラスの追加」を選び必要事項を入力したら、「クラスが登録されていません」というエラーがでてしまった。

f:id:donadonasan:20200209124757p:plain

クラスが登録されていません

直接の原因はわからないが、適切なプロジェクトをスタートアッププロジェクトに設定したら治った。ちなみに、英語環境だと以下のエラーになる。

f:id:donadonasan:20200209125148p:plain

Error HRESULT E_FAIL has been returned from a call to COM component

Visual Studioのコード上で32bit用コードと64bit用コードを切り替える

$
0
0

Visual StudioにてC / C++のプログラミングをする際に、32bit向けと64bit向けでコードを変えたくなる場合がある。切り替え自体は当然#ifdefでやるのだが、どの定数で判定するのが良いのだろうか?以下、整理してみた。情報ソースはマイクロソフトの以下記事。

docs.microsoft.com

  • _WIN32
    x86, x64, 32bitARM, 64bitARMのときに1。64bitでも1な点に注意。Windows環境のときのみ何かしたい場合に有用?
  • _WIN64
    x64, 64bitARMのときに1。Windows環境かつ64bit環境のときのみ何かしたい場合に有用。
  • _M_IX86
    x86のときに600。x86のときのみ何かしたい場合に有用。ARM他は別途判定が必要。
  • _M_X64
    x64のときに100。x64のときのみ何かしたい場合に有用。ARM他は別途判定が必要。
  • _M_ARM, _M_ARM64, _M_ARM_ARMV7VE, M_AMD64
    名前から察してください。なおAMD64AMD専用プログラムにする場合のみ指定?Intel/AMD両方用なら_M_X64を指定するのが正しそう。

 

CEventとCMultiLock::IsLocked

$
0
0

CEventとCMultiLock::IsLocked()の組み合わせでちょっと悩んだのでメモ。

CSemaphoreオブジェクトAのLock待ち中にAbortできるよう、以下のように組んだ。

悩んだのは、IsLocked()。Microsoftのドキュメントでは"Nonzero if the specified object is locked; otherwise 0." (ロックされている場合は非ゼロ, それ以外はゼロ)を返すと書かれている。ところで、CSignalの場合、SetとResetのどちらがロック状態なのだろう?

Reset状態ではLock()を通過できないのでResetがロック状態だと思ったのだが、テストしてみたらSetがロック状態だった。そのため、AbortがSetされたかどうかを確認するのは、if(IsLocked(0) != FALSE){Abort処理} が正解ぽい。

Aborting by using CEvent and CMultiLock

CMultiLockの問題

$
0
0

Semaphoreの参照カウント数を知るために、カウント数を保持するクラスを作ってみたのだがうまくカウントしてくれない。原因を調べるために、CMultiLockのコードを読んでみたらびっくりした。Unlock()とかはCSyncObjectクラスのUnlock()を呼び出すようになっているのに。Lock()は直接Win32APIを叩きに行っていて、CSyncObjectのLock()を呼んでいない。そのため、CSemaphoreのLock()をオーバーライドしても意味がない!!!

ちなみにCSingleLockならLock()はちゃんとCSyncObjectのLock()を呼び出しているので安心。

それにしても、どうしてくれよう、この実装・・・。CMultiLockを継承したクラスを作るしかないのか?面倒だなあ。


_RPTN, _RPTWN等をTCHARに対応させる

$
0
0

Visual Studioの出力ウィンドウに文字を出力する_RPT系関数は便利だが、なぜかTCHAR版が無い。

(_RPT系関数の参考)

donadona.hatenablog.jp

仕方ないので、TCHAR版を作ってみた。なお、毎回_CRT_WARNを書くのが面倒だったので入力不要としている。別のものを指定したい場合は、適宜書き換えてください。

TCHAR version of _RPTN functions

これで少し楽ができるようになるはず。

2020/2/23更新:Releaseにて無効化されないミス(ビルドエラー発生)を修正。

mozjpegをつかった画像変換ソフトの開発 その7

$
0
0

mozjpegを使った画像変換ソフト、開発が一段落した。

以下に121個のpngファイルを処理した場合の例を示す。64bit版では約6秒で全ファイルの処理が終わった。


MozJpegGUI 64bit ver. example

なお、32bit版と64bit版を作ったが処理速度は以下のように64bit版のほうが8%ほど早いようだ。メモリ制約も64bit版のほうが少なく、特に理由がない限り64bit版を使うべきだろう。

計433MBのpngファイル911個を処理するのにかかった時間

x86: 115.9 sec.
x64: 106.7 sec.

スキャナ設定のメモ

$
0
0

Canon DR-M200でモノクロ原稿を読む場合の設定メモ。

f:id:donadonasan:20200301152453p:plain

基本

f:id:donadonasan:20200301152555p:plain

明るさ

f:id:donadonasan:20200301152623p:plain

画像処理

f:id:donadonasan:20200301152645p:plain

画質調整

f:id:donadonasan:20200301152749p:plain

MozJpegGUIの設定

 

リソースエディタがWS_EX_COMPOSITEDを認識しない

$
0
0

とあるリソースをVisual Studio2019で開こうとしたら、error RC2104: undefined keyword or key name: WS_EX_COMPOSITED というエラーが出た。

f:id:donadonasan:20200305004914p:plain

error RC2104: undefined keyword or key name: WS_EX_COMPOSITED

でも、おかしい。このキーワードはWindowsに存在しているものなのだ。色々と調べてみたところ、Visual Studio2017からあるバグのようだ。WS_EX_COMPOSITEDを設定したダイアログはWindows8で正常に動作しないことと関係あるのかもしれない。

解決方法はとても簡単で、resource.hの先頭に以下を追加すればよい。

#define WS_EX_COMPOSITED 0x02000000L

 

CArchive::ReadString, CArchive::WriteStringの罠

$
0
0

CArchive::ReadString()とCArchive::WriteString()を使って文字列を読み書きしようとしたら文字化けしてハマったのでメモ。

以下のようなコードを書いたら、読み込み時に文字化けした。

 

void Serialize(CArchive &ar){

 TCHAR path[MAX_PATH+1];

 if(ar.IsStoring()){  // save

  (中略)

  ar.WriteString(path);

} else {  // load

  ar. ReadString(path, MAX_PATH);

}

 原因はCArchive::WriteString()とCArchive::ReadString()の設計が非対称なため。ReadString()が'\0'までを読み込む関数だと思ったら大間違いで、こいつは'\n'までを読み込む関数。一方、WriteString()は’\0'の直前までを書き出す関数で、書き終わっても'\n'を付与しない。つまり、読み込みと書き込みで区切り文字が異なるのだ。そのため上記プログラムだとMAX_PATHまで読んでしまい、本来読むべき領域からオーバーランし文字化けしたデータとなる。

また、この仕様だと'\n'を含む文字列をWriteStringした場合、ReadStringの処理がかなり困ったことになりそうである。すなわち、改行がある場合は意図したよりも短い文字列が読み込まれてしまう。

なんというか、getstr()との互換性をもたせようとした?せいで最悪な事態になっている気がする。この仕様を審査承認した人たちの罪は大きいと思う。

CDocumentからCViewを取得する一例

$
0
0

今時ドキュメントビューアーキテクチャなんて人は居ないだろうけど、メモ。

CDocumentからCViewにメッセージを投げたいときや関数を直接呼び出したいときに困るのが、CViewのアドレス取得方法。特に、自分はCSplitterWndを多用したためCViewが複数あり決め打ち取得ができなずに困ってしまった。そこで以下のような簡単なコードを書いてみた。

Example of finding CView from CDocument

ここにたどり着くまでに4時間かかってしまったorz

mozjpegをつかった画像変換ソフトの開発 その8

$
0
0

mozjpegを使った画像変換ソフト、無事Vectorにて公開。

www.vector.co.jp

 そのうち、英語版もどこかで公開予定だけど、どこにしよう?Vectorってどう見ても国内オンリーなのよね。英語ページを作れる気がしない。


ダイアログバー上のエディットボックスから文字列を取得する方法

$
0
0

ダイアログバー上のエディットボックスから文字列を取得するのにちょっと苦労したのでメモ。

問題の背景

ダイアログを作る場合、通常はCDialog等を継承した、そのダイアログ固有のクラスを作成する。しかしMicrosoft曰く、CDialogBarは通常固有のクラスを作成しないらしい。これは、ダイアログバーはメインウィンドウの一部で、各コントロールからのメッセージはメインウィンドウのウィンドウプロシージャで処理するという設計だからだろう。このため、メッセージハンドラはCMainFrameに書く必要がある。*1

問題

しかし、固有のクラスを作らなかった場合、メッセージハンドラにてダイアログバー上のエディットボックスにはどうやってアクセスすればよいのだろうか?単純にCMainFrameからGetDlgItemText(ID_DlgBar_Edit)を呼び出しても失敗するし、固有のクラスを作っても上手く行かない。

解決方法

実は答えは単純で、CMainFrame上のメッセージハンドラから、ダイアログバーのオブジェクト(デフォルトだとm_wndDlgBarだと思う)に対しGetDlgItemText(ID_DlgBar_Edit)すればよい。

m_wndDlgBar.GetDlgItemText(ID_DlgBar_Edit, str);

その他の操作をしたい場合は、同様にGetDlgItem()すればよい。

*1:なお、リソースエディタはこの仕様に非対応なため、メッセージハンドラは手動で追加する必要があるようだ。

CListViewにポップアップメニューを追加したけどON_UPDATE_COMMAND_UIが送られてこない件について

$
0
0

事象

CListViewにポップアップメニューを追加し、条件に応じてメニューのEnable/Disableを切り替えようとMFCの流儀に則りON_UPDATE_COMMAND_UIのハンドラを追加した。しかし、ポップアップメニュー表示時になぜかON_UPDATE_COMMAND_UIが来ない。

原因

CListViewは、ON_UPDATE_COMMAND_UIの送信に必要なOnInitMenuPopup()を実装していない。

対策

ここを参考に、OnInitMenuPopup()を実装する。

https://support.microsoft.com/sr-latn-me/help/242577/you-cannot-change-the-state-of-a-menu-item-from-its-command-user-inter

重いイベントハンドラの処理状況をダイアログで表示する

$
0
0

背景

重い処理の進捗状況をダイアログで表示する場合、自分がよくやるのは

  • 進捗状況を示すモードレスダイアログを作成
  • 重い処理をAfxBeginThread()により別のワーカースレッドとして起動
  • モードレスダイアログは適宜タイマによって状況を更新
  • モードレスダイアログ上のキャンセルボタンが押された場合はフラグを立てる
  • 重い処理内では適宜フラグを監視し必要に応じて処理を中断する

というもの。たいていはこれで問題なく動く。

問題

しかし、この実装だと例えばOnUpdate()にてCTreeViewやCListViewに数十万個のアイテムを追加する場合、フレームワークがOnUpdate()を呼び出している間モードレスダイアログのGUIが無反応・無更新となる問題がある。

原因

この原因は、ワーカースレッドとして作られたスレッドで新たにダイアログを作っても、そのスレッドプロシージャはウインドウプロシージャの一部となってしまうことにある。すなわち、メインウィンドウのウィンドウプロシージャが非常に重い処理を呼び出してしまった場合はモードレスダイアログ上のGUI向けのものも含めウィンドウメッセージがキューにたまり続け、処理されないのである。

解決方法

  • 対症療法:重いイベントハンドラ内の処理もマルチスレッド化してしまう
  • 根本対策:ダイアログのウィンドウプロシージャをメインウィンドウから独立させる(ワーカースレッドではなく、ユーザーインターフェーススレッドにする)

An example of user interface thread implementation

参考

dodonpa.la.coocan.jp

 

ガンダムオンラインのCPU使用率およびGPU使用率

$
0
0

ガンダムオンラインにおけるCPU使用率とGPU使用率をメモ。

CPUはRyzen3700X, GPUGeForce GTX 1070、解像度は1920×1080で画質は最高。全ていずれもCPU使用率20%程度、GPU使用率20%程度。CPU使用率の波形から、相変わらず2スレッドぐらいしか使っておらずマルチコアよりも高クロックの方が快適度の高い設計に見える。

ロビー:

f:id:donadonasan:20200409151437p:plain

ガンダムオンライン ロビーでのCPU・GPU使用率

ロビー。

f:id:donadonasan:20200409153033p:plain

ガンダムオンライン 戦闘中の機体選択画面におけるCPUおよびGPU利用率@ニューヤーク

戦闘中。

f:id:donadonasan:20200409154912p:plain

ガンダムオンライン 戦闘中におけるCPUおよびGPU使用率@ア・バオア・クー

 

ガンダムオンラインで回復ビームが他人から見えないことがあるバグ

$
0
0

ガンオンで死体を起こそうと回復ビームを当ててたら、それを遮って修理しろアピールをする人がいた。何故だろうと思ってリプレイを見てみたら、回復ビームが写っていない!そりゃ、死体を起こそうとしているのは分からないし、修理アピールもするわ。

以下、リプレイによる検証動画。回復ビームが出ていた証拠に死体は無事復活したし、復帰ポイントも入っている。なので、自分がビームを出していたのは間違いない。しかし、リプレイでも回復ビームが見えない。なので、これはガンダムオンラインのバグであろう。

結論。バグが悪い。


ガンオンで回復ビームが他人から見えない

Viewing all 552 articles
Browse latest View live
<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>