Joplinを排除してノートアプリを完全にZimに移行したあと、うっかり その修正・改良を始めてしまい※、随分手こずったものの(2週間くらい掛かった!)かなり良くなって、概ね満足できるレベルになった。
※いろいろムカつく問題があったのだが、他にないので変更・修正や代替策などで数年間我慢して使っていた。
そして、僕の改良・改造版はノートのフォーマット(タグ)は違うし、機能・動作は かなり違うし、プログラムの構造も少し変更してオリジナルとは全く違うと言っても いいほどなので、名前を"Zimb"(Butty's modified Zim または Butty's note taking app. (Modified version of Zim))に変えた(例によって(夜中に)思い付いたw)。
オリジナルを尊重して元の名前を残し、読みも同じに なりそうなものにした。
なんかイメージの良くなさそうな南のほうの国を連想するが、まあ良しとするw
- Zimb(改良版Zim)のウインドウ(例)
- ZimbのAboutダイアログ
作業を始めた切っ掛け
Joplinの作業が終わり、延々と放置して来たZimのTODO(修正・改良)に着手すべきだけど、やっぱり面倒な気分でも あった(やらなくても とりあえず使えるし・・・)。ただ、以下のような ちょっと切実なことが切っ掛けになったように思う。
- ベースを最新版※にしたくなった(V.0.74.2 → 0.75.2)。: 以前からやりたかったが、面倒なので放置していた。
- どうせ変更(修正・改良)するなら、最新版をベースにしたほうが良いので。
- ※作者は開発を止めた訳ではないようで、思い出したように(年に数回)更新している。ただ、機能や動作は ほとんど変わっていない感じだ。
- Zimのノートのgitリポジトリ(バージョン管理プラグインが使う)が大きくなってしまった(500MB以上)。: Joplinの画像が多かった。ノート以外はgitに入れる必要は ないのに、馬鹿みたいに全部入れていた。
- ある時、バックアップのサイズが大きいファイルが目に止まって気付いた。
- 余計なファイルを削除してgitリポジトリを小さくするために、(調べて見付かった方法がうまく行かなかったので、)BFGというプログラムを使った。結構簡単で良かった。
- 作業中に気付いたが、ZimのノートのDB(SQLite)も大きくなってしまった。: 上と同じ原因で、ご丁寧にもノートでないファイルもスキャンしてDBに登録・更新していた。
- それでノートのエクスポートやプログラムの終了が遅くなって居た(多数(1万個以上)のファイルのインデックス処理をするため)。
- ZimのPage indexプラグインで鬱陶しいURL(link placeholder)を消したい。
- URLをノートにペーストすると、左サイドバーのノート一覧に それ由来らしき(エンコード? ダイジェスト?: 分からないので部分的に隠した)記号の名前のページが出て増えて本当のノート名が見えにくくなって、すごく不便だった。
- 以前削除しても復活したので、諦めていた。
ベースを最新版に
手間は掛かったが、意外にすんなりできた(最新版までの変更が少ないということ)。比較とマージをGUIで手軽にしたかったので※、Linuxで使えるものを探したら いくつかあったもののどれも満足できなかったが、一番良かったMeldにした。
※昔はWinMergeを使ったのを思い出す。
Zimの修正内容
主なものを以下に列挙する。
- 2重表示の問題(ページの再描画が おかしいことがある)
- 現象
- 大きいノートの後半辺りに行を挿入したりすると、その前後の行が微妙に ずれて2重に表示されることがある。
- 一旦 別のアプリのウインドウをアクティブにしてZimに戻ると直る。 → 今まではそれで しのいでいた。
- ↑のためにキャプチャを取るのが難しく、提示できない。
- 原因: プログラムの処理が不適切(ページ描画処理手順が悪い(推測)のと その回避のための"HACK"処理(下を参照)が良くない)。
- 参考
- これを解決できたので、プログラム中の謎の処理"_hack_on_inserted_tree()"(zim/gui/pageview/__init__.py, 以下、"HACK")を不要に出来た。
- この問題と下のノート切り替え時のカーソル位置の復元は関連している。
- カーソル位置の復元などのためにHACKがあるのだが、その処理が不適切なために2重表示が起こっていた。
- そもそも、ZimのGTKの使い方が今一つなためにページ描画が遅くなり、HACKがないとスクロールできない(→ カーソル位置が復元できない)問題が起こっていたと推測する。
- (9/18 14:59) LibreOffice Calcでも起こった(以前からあったのを忘れて居た)ので、GTK全般で起こりうる問題のようだ。ただ、いつも・どのアプリでも起こる訳でないので、起こりやすい条件(描画の仕方? グラフィックデバイス関係?)があるのだろう。
- 対処: ページ描画手順を真っ当と思われるものに変更し、HACKを不要にした。 (詳細は実装メモを参照)
- 現象
- (2重表示の問題が起こらないようにHACKを無効にした構成では、)ノート切り替え時にカーソル位置が復元できない。
- 現象: 上のHACKを有効にしないと、ノート切り替え時にカーソル位置が復元できない。
- ただ、HACKが有効でも、大きいノートでは復元できないことが多い。
- 原因: プログラムの処理が不適切(ページ描画処理手順が悪い(推測))
- 対処: ページ描画手順の変更。
- 現象: 上のHACKを有効にしないと、ノート切り替え時にカーソル位置が復元できない。
- 長い行を綺麗にラップ(折り返し)できず、横スクロールバーが出ることがある。
- 現象: 長い行は適切にラップするはずだが、横スクロールバーが出るうえにページが左にずれて行の先頭が隠れてしまうことがある。
- なぜかHomeキーではカーソルを戻せないので、すぐには行の先頭が見えず大変不便。
- 原因: 不明。
- Zimの問題なのかGTKの(設定の)問題※なのか不明
- ※他のアプリでも似たような現象が起こることがある。
- Zimの問題なのかGTKの(設定の)問題※なのか不明
- 暫定対処: ページのマージン(特に右マージン)の調整
- かなり大き目(例: ページ幅約800pxに対し、50px)に右マージンを取った。
- 試行錯誤のため、マージンなどを設定で容易に調整できるようにした。
- 参考: これでもスクロールバーが出ることがあるので、完全には対処できていない。
- スクロールバーが出るのは、大きな画像がある他にVerbatim(文字を そのまま出す書式)の長いブロックが関係していそうだ。あと、日本語は単語の区切りの空白がないので、そもそもラップしにくいのかも知れない。
- このブログの表示を見て思ったが、単語間(駄目な時は文字)で切るのに加えて、字の間隔を微妙に伸縮させて調整できると随分良くなりそうだが、GTKでできるのだろうか?
- ただ、それも破綻する(日本語の文字が飛び石になってしまう)ことがあるので良し悪しだ。。。
- 現象: 長い行は適切にラップするはずだが、横スクロールバーが出るうえにページが左にずれて行の先頭が隠れてしまうことがある。
- Zimアプリ(GUIプログラム)を閉じて終了させてもプロセスが残って居ることがある。
- 現象: Zimのウインドウを閉じてもプロセスが終了しない。
- 修正・改良後の確認時にはkillコマンドで強制終了する必要があって(そうしないと残ったプロセスが動くので、変更点の確認ができない)、大変不便だった。
- 原因: バックグラウンドで実行しているインデックス処理が終わらない。 (上の「切っ掛け」を参照)
- ノートのディレクトリにあるすべてのファイル(1万個以上)をインデックスするため、時間が掛かっていた。
- 対処: ノートのファイルだけをインデックスするようにした。
- インデックス対象と除外するファイル・ディレクトリ名のパターン(正規表現)や種類(sym-linkを除外するか)を設定で指定できるようにした。
- 現象: Zimのウインドウを閉じてもプロセスが終了しない。
- Zimのノート一覧で鬱陶しかった、記号化URLの羅列 (一部を伏せた)
- Zimb: 表示する画像サイズやマージンや行間を設定できるようにした。 (下半分)
- Zimb: インデックスするファイルの対象・除外パターンなどを設定できるようにした。 (中央辺り)
- FcitxがCtrl+5を取っていた。 (中央辺り)
- FcitxのアドオンKeyboad LayoutがAltをmodifierにしている。 (右上)
Zimの改良内容
主なものを以下に列挙する。
- タグの中に使えない文字がある。: 例: VERBATIM(文字をそのまま出す書式)なのに ' が使えない。
- VERBATIMは、タグが '' で本来の文と競合するようなので、書式のタグを変更した。
- Zimを使い始めた時の変更(下も参照)と同様に、タグの文字列を、本来の文と競合することが少なそうなものにした。また、タグは2文字構成だが、開きと閉じそれぞれの先頭と最後の文字を、一般には使わない「変な文字」にした。
- 参考: Zimを使い始めた時に、かなり不便だったので(その他の"__"の件を参照)次のタグを変更した。: EMPHASIS(斜体), MARK(下線)
- Zimを使い始めた時の変更(下も参照)と同様に、タグの文字列を、本来の文と競合することが少なそうなものにした。また、タグは2文字構成だが、開きと閉じそれぞれの先頭と最後の文字を、一般には使わない「変な文字」にした。
- 同様に、次のタグの書式を変えた。: VERBATIM, STRONG(太字), STRIKE(取り消し), SUBSCRIPT(下付き), SUPERSCRIPT(上付き)
- タグを変更すると以前のノートを修正(タグを変換)する必要がある※が、読み込みは新旧両方に対応し、書き込みは新しい書式にすることで、変換を不要(自動変換)にした。
- ※それで追加のタグ変更を保留していたが、今回は少しスキルが上がって上のような うまい方法ができた。
- VERBATIMは、タグが '' で本来の文と競合するようなので、書式のタグを変更した。
- 埋め込み画像サイズの自動調整
- 画像をインデントした場合でもページ幅を超えないように、自動で縮小するようにした。: 上述の横スクロールバーをなるべく出さないため。
- デフォルトや最小サイズは設定で変更できるようにした。
- そのため、画像が多いノートの描画が遅い。
- → 思い付きだが、ノートをビューに表示する時に初めて、実際に表示される画像を読み込めば良い気がする(webのlazy loadingのイメージ)。ただ、それがGTKで可能なのかは不明だ。
- 例えば、TextBufferの中にコールバックを設定とかできるのだろうか? あとは本当に そういう属性があるか。: まあ、忘れたほうが良いw
- → 思い付きだが、ノートをビューに表示する時に初めて、実際に表示される画像を読み込めば良い気がする(webのlazy loadingのイメージ)。ただ、それがGTKで可能なのかは不明だ。
- 画像をインデントした場合でもページ幅を超えないように、自動で縮小するようにした。: 上述の横スクロールバーをなるべく出さないため。
- URLをペーストした時、自動でクリッカブルリンクにしないようにした。
- 上述のURL由来の変なノートができるのを防ぐため。
- 自動でリンクになるのは便利だが、不意にクリックすると勝手にブラウザでアクセスするため、神経を遣って却って不便なので無効にした。
- 手動操作でリンクにすることは できるので、問題ない。
- ウインドウなどの設定ファイルなどの位置を変えた。
- オリジナルではノートのインデックスのDBとともにキャッシュディレクトリ(~/.cache/zim)に入っているが、キャッシュは「いつ消しても大きな問題は起こらない」と思わせて適切でないので、(いい場所が思いつかなかったものの)~/.local/share/zimにした。
- 本来はウインドウなどの設定は~/.configが良いが、なぜかDBも一緒に保存されて分割が容易でないので そうした。
- オリジナルではノートのインデックスのDBとともにキャッシュディレクトリ(~/.cache/zim)に入っているが、キャッシュは「いつ消しても大きな問題は起こらない」と思わせて適切でないので、(いい場所が思いつかなかったものの)~/.local/share/zimにした。
- MDでのエクスポート時の上下付き書式を変更した。
- これまではJoplinが対応している形式(^, ~)だったが、スマフォのビューアをObsidianにしたのでGFMの形式(HTML, <sup>, <sub>)で出すようにした。
- キャレットを点滅しないようにした。: イライラする(までは行かないが、「うるさい」)ので。
- 点滅を設定する方法を調べたが、プログラムからはできないようなので、GTKの設定ファイル(~/.config/gtk-3.0/settings.ini)に以下のように書いた。※
[Settings] gtk-cursor-blink=0
-
-
- ※そのため、他のアプリも同じ設定になるが、そもそも点滅が嫌なので問題はない。
- それに伴い、キャレットの色や太さを調整した。
- キャレットが点滅せず細いと、黒はもちろん 色が付いていても、ページ切り替え後に どこにあるか分からないことがあるため。
- また、太過ぎると文字の一部を隠してしまうことがあるので、太さ(アスペクト比)を微調整した。
- 太さは今一つ調整の自由度(ステップ)が少ない感じだ。
- また、アスペクト比の設定のため、行が高い場合(例: 画像のある行)はキャレットが巨大になってしまう。この回避方法は まだ分からない。
- 変更する方法を調べたが、プログラムからはできないようなので、GTKのCSSファイル(~/.config/gtk-3.0/gtk.css)に以下のように書いた。※
-
* {
caret-color: #812a2f;
-GtkWidget-cursor-aspect-ratio: 0.12;
}
-
-
- ※キャレットの点滅と同様に他のアプリも同じ設定になるが、このブログのヘッダの色に近く、悪くない感じの色なので大きな問題はない。
-
- キャレットのスタイルの調整前 (色: #7b2424, アスペクト比: 0.09)
- 同、調整後 (#812a2f, 0.12)
- キャレットの太さの調整前 (アスペクト比: 0.15)
- 同、調整後 (0.12)
- 画像の行で巨大化したキャレット (右端)
実装メモなど
A. 2重表示問題の対処・描画処理手順の改良
僕はGTKに詳しくないが、Zimのプログラムを調べて分かったことと推測したことの概略を書く。
- オリジナルのZimは、以下の順序でページ(ノート)を描画している。
- ページ(ノート)全体(ノートのテキストと埋め込まれている画像全部)を読み込んで解釈し、バッファ(TextBuffer)に描画オブジェクトを作り上げる。
- 出来上がったバッファをビュー(TextView)に登録する。 → GTKの描画処理が始まる(全体を一気に描く)。
- [HACK処理]
- GTKの処理がアイドルになるまで待つ(GLib.idle_add(hack関数))。
- hack関数: 指定時間待ってからスクロールする(GLib.timeout_add(タイムアウト, スクロール関数))。
- 本来はアイドルになるまで待てば充分なはずだが、うまく行かなかったのか、指定時間待ってからスクロールしている。
- 実際、タイムアウトが短いと充分にスクロールしない。
- 本来はアイドルになるまで待てば充分なはずだが、うまく行かなかったのか、指定時間待ってからスクロールしている。
- スクロール関数: カーソル位置を復元するため、ビューをスクロールさせる。
- GTKで一気に表示処理をするため、どういう理由かは分からないが、上述のスクロールが行われない問題が起こる。
- それを回避するためにHACK処理をしているのだろうが、2重表示の問題が起こる。
- 調べた限りでは、(理解不能なものの)HACK処理自体は悪くないが、複数の(余計な)ところから実行するのが良くないようだ。
ただ、他のアプリで 上のような問題(スクロールしない, 2重表示)が起こっているのを見たことがないし、検索しても出て来なかった。※
※スクロールが起こらない問題に関しては あったが、Zimは ちゃんとスクロールできるはずの方法で実行していた。
そこで、一気に描画するのが良くないと考え、順次描画するように、以下のような順序にした。
- 空のバッファ(TextBuffer)をビュー(TextView)に登録する。
- ページ(ノート)を読み込み、順次バッファ(TextBuffer)に描画オブジェクトを作り上げる。 → GTKは順次描画処理を するはず。
- GTKの処理がアイドルになるまで待つ(GLib.idle_add(スクロール関数, カーソル位置))。
- これはオリジナルのHACK処理にヒントを得た(上を参照)。
- スクロール関数: カーソル位置を復元するため、ビューをスクロールさせる。
駄目元でやったのだが、意外にうまく行った。まだ1日くらいしか試していないが、今までに1度2重表示が起こった(これが気になるが・・・)ものの、カーソル位置は常に復帰できて かなり改善できたし、謎のHACK処理が不要になった。
ただ、変更前はページが順次描画される(例: ノートの先頭から描画されるのが見える)ようになると思って居たが、そうではなく、どういう訳かビューは ずっと空のままで、最後に一気に全体(ビューのうち見える部分)が表示される。
調べていないが、もしかすると、表示に関する もう一個のPageViewというwidget(VBox)とTextViewの関係があるのかも知れない。上述のTextViewとTextBufferのように、最後にTextViewをPageViewに登録しているのだろうか。 ← プログラムを見てみたが、そうではないようだ。TextViewはPageViewの生成時に設定している。
(21:55) 最後に一気に表示されるのが気になってプログラムをチェックしたら想定と違っていて、上の手順の1の空のバッファ(TextBuffer)をビュー(TextView)に登録していなかった。そのために一気に表示されたようだ。
ということは、最初の(抜けのあった)修正でも うまく行ったポイントは何だったのか分からなくなった。: 結局、オリジナルのHACK処理と その実行の仕方が悪かった(「素直」でなかった?)のか。: また あとで考えたい。
それで上の手順になるように修正したら、画像の多いノートでの描画開始までの待ち時間は相変わらず長いものの、描画途中の状態が ちょっと見えてから復元されるべきカーソル位置にスクロールするようになり、想定に合うようになった。
この辺りには まだバグがありそうだ・・・
B. 特定のキーが取れない
動作確認をしていて気付いたのだが、なぜかショートカットのCtrl+5がZimに届かなかった(妙なことに、Ctrl+4まではOKだった)。更に、Alt(Shiftなし)は全滅だった。いろいろ調べたら、FcitxがCtrl+5を取っていた。。。※ AltもFcitxが怪しい(どうしても解決できなかった)。
※なんで そういうテキトーな割り当てをするか不明だが、まあ、そういうプログラムなので仕方ない。他にもムカつくことはあるが、他にないので我慢+可能な修正をして使って居る。
(9/14 19:15) 取れないキーは他にもあった。: Ctrl+0やCtrl+. が取れないことに気付いた。前者は(僕がPythonとGTKに詳しくないため、)プログラムの誤りなのか他に原因があるのか分からないが、プログラムを修正して使えるようにした。 ← その後、また駄目になって居た。全く謎だ。 (19:44) ← その後、Zimを起動するディレクトリによって動作が変わることが分かった。 (20:39)
Zimの開発ディレクトリで起動した場合、その下位に そのキーの機能(Remove heading)を含むメニューバー定義ファイル(menubar.xml)が あってキーを取得するが、そうでないディレクトリからは古い版の定義ファイルしか参照できず、キーが無効になった(この機能は最新版で追加されたようだ)。
定義ファイルにはキー割り当ては書いてないけど なぜか関係しているので、大変分かりにくい。また、新しい定義ファイルでは下の変更は不要だった。
具体的には、下のショートカットキーの定義らしき行で、オリジナルには"accelerator="がないのだが、それを他に合わせて入れたら取れるようになった。ただ、別のキーで それがないものも動いているので、何だか分からない。
@action(_('_Remove Heading'), accelerator='<Primary>7', menuhints='edit')
後者は ひどく、FcitxだけでなくGTKも取っていた。GTKでは絵文字一覧(Emoji chooser)が出てしまう。調べても、設定で出ないようにする方法が分からない(プログラムでの方法は あったが、これは全体的に無効に変更したい)・・・ 杜撰なソフトは多いが、GTKは ひどい。使うための資料も機能も不足している。
残件
まだ結構あり、以下の順序で対応したいと思う(が、疲れたし、充分問題なく使えて居るので面倒だ・・・ → また数年後?)。"[Fix]"は修正、その他は改良である。
- ノートのsuffixの変更
- 今は なぜか"txt"で、普通のテキストファイルと区別できず不便なので変えたい。
- 書式タグと本来の文を競合しにくくする(残り)
- 次が残って居る。: IMAGE(埋め込み画像), LINK(リンク・埋め込みファイル), VERBATIM_BLOCK(複数行のVerbatim), OBJECT(不明), HEADING(見出し), TABLE(テーブル), LINE(水平線)
- 箇条書きの*と同様に、そのままでも良さそうなものがあるので、良く考える必要がある。
- 次が残って居る。: IMAGE(埋め込み画像), LINK(リンク・埋め込みファイル), VERBATIM_BLOCK(複数行のVerbatim), OBJECT(不明), HEADING(見出し), TABLE(テーブル), LINE(水平線)
- クリップボードをplain textとしてペーストする機能の追加, ペーストするフォーマットの順位設定
- ([Fix] ファイルマネージャ(Thunar)からのファイルパスのペーストがリンクになる。)
- 今はxselコマンドを使った外部プログラムで実現している。
- 日付の挿入(Ctrl+D)時、カーソル(上下)で書式を選べるようにする。
- 今はマウスでしか選択できない。
- [Fix] 日英混在の場合(?)、ダブルクリックでの単語の選択が おかしいことがある。
- GTKの問題・設定かも知れない。
- [Fix] カーソル(キャレット)の見栄え
- もう少しなんとかできる?
- [Fix] 書式など(例: 箇条書き)のプルダウンメニューの展開が なぜか遅い。
- ツールバーの箇条書き, 番号付きリスト(, チェックボックス, 見出し)の各要素を独立させて横に並べる。
- 煩雑になりそうなので、要検討。
また、以下は対応困難か実害が少ないので、非対応か保留にしようと思っている。
- 長い行を完全にはラップできず、横スクロールバーが出ることがある。: 頻繁に起こるなら対処する。
- ノート中に画像が多いとメモリを食うのは仕方ないが、適宜解放できるとうれしい。
- 動作を見ると解放していない訳ではないので、Pythonのメモリ管理の問題か、Pythonが解放してもOSが「引き取っていない」のかも知れない(調べたら、良くあるようだ)。
- アプリ終了時に全部の子プロセスを止める。
その他
ここまで読めば分かると思うが、オリジナルのZimは使いものにならないと断言できる(「敢えて言おう、〇〇であると!」レベル)。良く作者は気付かないものだ。そういうのは気にしない人なのか、自分では余り使ってないのか??: ベースが使えるものなので怒りはしないが、プログラムは なかなか几帳面に書かれているだけに理解不能だ。
- 例えば、ZimはPythonで書かれているから、作者は"__init__"のような文字列をZimのノートにペーストしそうだが、ペーストの直後にVerbatimの書式に設定しない限り、下線の書式("init")に変換されてしまう。
- 同様に、Pythonのdocstring('''...''')をペーストしたら、コードブロックに変換されてしまう。
- 結局、普通に書いて そのままだと情報が変わってしまい(上の例では__や'''が失われる)、ノートアプリとしては失格だ。
- そういうのを書くたびに、漏れなくコードの書式に設定しているのだろうか。気にしないのだろうか? それでも、いくらなんでも__は不便だと思う。
感覚や思想の違いだと思うが、Zimは余計(邪魔)な機能が多い割に、基本的なことが欠けている。だから、随分可能性があるのにユーザーが増えないのではないか。
書いたあとでの追加
A. メモリ使用量についての調査・検討 (9/13 15:36)
上に書いたように、Zimがノート中の画像が多いとメモリを食い、しかも増え続けるのが気に入らなかったので少し調べてみた。すると、いろいろなことが分かった。
まず、Zimには意外にもページキャッシュがあることが分かった。どうやらページの表示イメージ(≒ TextBuffer)をそのまま格納しているようで、ページを2回目以降に開く場合は すごく高速になる。※ それは良いが、ページがキャッシュに残って居るうちはメモリ使用量は減らない。しかも、どうやら無限のページ数をキャッシュするようなので、メモリ使用量が増え続けるのが納得できた。
※今までは、僕がZimを使い始めた時にした改造が障害になって高速にならないことが多かった感じだが、この調査のために それを無効にしたら、カイカン的な速さになった。: 画像が多いノートでも一瞬で出る! (その代わり、メモリ・・・)
そこで、キャッシュのエントリ数(保持するページ数)を制限してみた(例: 10)。数が上限に達した場合には、(とりあえず、)最も過去に参照したものを削除するようにした(LRU)。が、それでも減らない感じだった。
更に調べると、ZimはPythonで書かれているため、基本的には、使われなくなった(= 他から参照さなくなった)メモリはGC(ガベージコレクション)で自動的に解放されるが、相互・循環参照※されていると解放できないことがあるそうだ。 (→ 参照: 大雑把にしか読んでないが、随分参考になった。)
※相互・循環参照の例を以下に書く。
-
- 相互参照: 2つ(以上)のオブジェクトの間で参照し合っている。: A → B → A
- 参照が片方向だけなら問題ない。
- 循環参照: 自分で自分を参照している。: A ⇔ A
- 相互参照: 2つ(以上)のオブジェクトの間で参照し合っている。: A → B → A
プログラムを調べてみると、確かに、テキストバッファ(TextBufferクラス)には他のクラス(例: その上位のTextView)のオフジェクトと相互に参照し合っているメンバがあった。そのためにページを切り替えても解放されない可能性がある。
相互参照はオリジナルのプログラムでもあったが、僕が追加した部分が多かった。上のようなことを知らずに、「ちょっと」上位クラスのメンバを参照したくて、テキトーに追加したためである。
そこで、とりあえず、ページをキャッシュから削除する時には もう誰も参照しない時だろうから参照関係を切っても良い(プログラムの動作が破綻しない)と考え、そのような処理※を追加したところ、概ね期待どおりの動作となった。
※具体的には、A → B → Aという参照関係がある場合、BのメンバのA(へのポインタ)にNoneを代入してB → Aを切った。
残念ながらメモリ使用量が減ることはないが、増え続けることもない。: キャッシュ内のノートのサイズの合計が それまでの最大値以内である限りは増えることがないので、プログラム(Python)内でメモリをやり繰り(再利用)できていると考えられる。
減らない理由を想像すると、(上述のように、)Pythonが解放してもOSが受け取らない のではないかと思うが、他に、まだ参照を切るのが甘い可能性もある。
というのは、どこで参照しているのか分からないが、キャッシュから削除する時の参照カウントが70近いからだ。どうしてそんなに参照が増えるのかは謎である。
(9/14 12:39) 参照カウントが随分多いのが気になったので参照元を調べたところ、ページの参照の大半はファイルタイプの定義(例: 'image/png')からのようだった。今回はプログラムは見ていないため、なぜそんなものが他のオブジェクトを参照しているかは不明だが、そういう実態だ。そして、メモリ解放の障害となる相互参照は、上述の「僕が追加した部分」だった。
(9/14 16:33) その後 更に試したところ、キャッシュから古いエントリ(ページ)を削除したあとで強制的にGCを実行すれば、キャッシュの合計サイズが小さくなる場合にメモリ量が減ることが分かった。だから、僕の修正が正しいこととOSは解放されたメモリを回収していることが分かった。
なお、必ずしもGCしなくても良い(GCはPythonに任せる)はずだが、今一つタイミングが遅い感じなので、(気分・好みの問題で)するようにした。
次に、推測ではあるが、画像が多いとメモリを食う理由も分かった。使って居るグラフィック処理系のGTKは、画像をGdkPixbuf(以下、Pixbuf)という形式で保持する。そして、(悪い予想が当たり、)Pixbufは非圧縮のフォーマットしかないため、JPEGやPNGの画像を表示させる場合でも展開するためにメモリ使用量が増大する(BMPみたいな感じ)。そのため、添付画像が多いノートを表示するとメモリ使用量が増大するのだ。
試しに、画像の多いノート※で検討してみた。
※このノートは定期的に同じグラフの画像をキャプチャして貼り付けているので、すべての画像のサイズがほとんど同じで、計算には都合が良い。
- ノート内の埋め込み画像のサイズ: 約
700x450650x390px- → 上のサイズのPixbufの予想メモリ量: 約
923990kB/画像 (700x450x3650x390x4)
- → 上のサイズのPixbufの予想メモリ量: 約
- そのノートを読み込んだ(表示している)時のZimのメモリ使用量の増分: 約68MB
- ノート内の埋め込み画像の数: 73
- → 1画像当たりのメモリ量: 約950kB/画像 (68x1024/73)
- ※このノートのテキストは約
148.4kBと小さいので、ここでは無視した。
- ※このノートのテキストは約
- → 1画像当たりのメモリ量: 約950kB/画像 (68x1024/73)
注: その後、実際のPixbufオブジェクトにはアルファチャネルがあって4チャネルであることと、実際の画像サイズは約650x390pix、テキストサイズが約8.4kBであることが分かったので修正した。 (9/16 11:00) ← どうしてサイズが違ったのか考えたら、元の画像は修正前のサイズだが、表示時に自動でリサイズしているためのようだ。 (9/16 13:29)
Pixbufの仕様から推算した1画像当たりのサイズとメモリ使用量の増分からの1画像当たりのサイズが合ったので、無駄にメモリ使用量が増大しているのでは なさそうなことが分かった。
(9/16 11:10) なお、実際のプログラムで画像のデータサイズを求めたところ、上と合っていた。: Pixbuf.get_byte_length()の値と、get_width()などの画像パラメタから上の方法で計算した画像サイズが一致した。
これを改善するには、TextBuffer(あるいは他のGTKの要素)で画像を圧縮したまま扱えれば(表示・コピー・ペースト・移動など)良いが、少し調べた限りでは できなさそうだ。ブラウザのように表示だけならともかく、編集までできるようにするのは難しい気がする。
ブラウザが どうやっているか分からないが、やっぱりPixbuf(あるいは同様の非圧縮フォーマット)の気がする。だから、あんなにメモリを食う(もちろん、これだけではない)のかも知れない。
(9/16 11:27) 他に、画像を読み込む時にアルファチャネルを削除すればデータサイズは3/4になるだろうが、根本的でない気がする。でも、ちょっとやってみたい。
↓
B. 画像からのアルファチャネルの削除 (9/18 12:40)
苦心惨憺して できた。GTKには なぜかアルファチャネルを削除する処理(API)は なさそうなので、作った。処理自体は簡単なのだが(それでも分からないことが多かったので、楽ではなかった)、Pythonで書いたら遅過ぎた(元々の5倍くらい時間が掛かる)。
処理の概要: Pixbuf(アルファあり) → Bytes → アルファチャネル((インデックス%4)==3)を削除※ → Bytearray → Pixbuf(アルファなし)
※おもしろいことに、調べて見付かった凝ったデータ抽出方法([]内にループを書くようなやつ)より、普通に愚直にループ+配列のslice → extend()で書いたほうが速かった。[]内のループをコンパイルできるなら、速くなるのだろう。
まあ、元々心配していたが、いくらPythonが速くても こういう処理には無理がある。そこで、ネイティブで書かれたライブラリを使うことにし、元々Zimで使われていたので思い出した、PILというライブラリのconvert()という関数を使った。PILも、癖があったり資料が今一つ親切でないので苦労した。
処理の概要: Pixbuf(アルファあり) → Bytes → PILのImage("RGBA") → convert("RGB") → Bytearray → Pixbuf(アルファなし)
GTK→PIL(アルファ削除)→GTKとデータ変換が多くなったが、元々からの速度低下は ほとんどない。ページ読み込み(表示)時間は元々と変わらないのに速くなったように感じる(キャッシュされているページが一瞬で出る)のは、気のせいとかプラシボ効果だろうか。画像のサイズが小さいとかアルファがないために処理が軽くなった可能性もあるが、数値※は同じである。
※数値が変わらないのはキャッシュされていないページを最初に表示する時のことで、キャッシュされているページの表示は本当に速いのかも知れない。
メモリ量は、内部的(ページサイズ)には計算どおり3/4になったものの、OS(Linux)側では従来どおり多目に出る。まあ、仕方ないのだろう。: Pythonがプールして解放していないとか、解放してもLinuxが引き取らないなどかと想像している。
余談: Zimへの寄付について
今までずっと、「ちゃんと使う・使えるようになったら寄付しよう」※と思いつつも迷って居たが、止めた。
※そもそも、(良くあることだが、)PayPalでは日本から寄付できないし、PayPal以外の方法は示されていない。
本文に書いたように、オリジナルのままでは使いものにならないので寄付しないことにしたが、ベース(骨組み)としては随分役に立っており、良くあれだけの内容・量を作ったものだと感心しているので、きっぱり切り捨てられずに今一つモヤモヤしていた。
それで、僕の修正・改良のいくつか(例えば上述のHACKを不要にする方法)をフォーラムに投稿すれば充分に寄付の代わりになりそうだと思い付いたので、もう少し試用して改良が問題ないことが分かったら することにした。*
*が、今までの経験から、投稿しても大抵は無視とか放置になるのが分かっているので、最初は手を掛けないようにしたい。作者が興味を示したら、詳しく教えることにしよう。でも、詳しく書いたら放置や却下もあるので、そもそも手を掛けないようにしよう。
PS. Zimには全く関係ないが、フォーラムやユーザー対応で今までで一番良かった(親切・真摯だった)のはMusicBeeのSteven(うろ覚え)だったかも知れないと、今思い出した。だから僕は翻訳を したくなったのかも知れない。彼は今もMusicBeeの開発を続けているのだろうか?
(9/13 10:11 誤字を修正。: 「解放」と「開放」は難しい・・・, 15:36 「メモリ使用量についての検討」を追加, 19:15, 19:44, 20:39 少し修正・加筆, キーが取れない件に追加; 9/16 11:27 PixmapはPixbufの誤りだったので修正, ページサイズの推算でのパラメータを修正, 画像のデータサイズについてなどを追加; 9/18 12:40 画像からアルファチャネルを削除する処理について追加, 14:59 2重表示について追記)