gruff のメンテナーになりました

RMagick が ImageMagick 7 に対応できたので、今度は RMagick を使用しているライブラリを見ていこうかとおもい、gruff のオーナーに連絡したら Contributor として招待していただきました。

とりあえず Rubocop を導入したので調教したりしながら、気長にやっていこうと思います。

ImageMagick 7 に対応した RMagick 4.1.0 への移行方法

年始に転職して、現在は Repro株式会社 に所属しています。

昨日 ImageMagick 6 & 7 に対応した RMagick 4.1.0 をリリースしましたので、簡単に移行手順を書きます。

はじめに

ImageMagick 6 と ImageMagick 7 が提供する異なる API インタフェースの差分を吸収するために、作業に取りかかった初期の頃から RMagick のインタフェースを変えないと対応が困難だと判明しておりました。

そのため、RMagick 3.2.0 から RMagick 4.0.0 へバージョンを上げた際に事前にインタフェースのみ変え、RMagick 4.1.0 は内部的な変更のみで ImageMagick 6 と 7 に対応したため、このようなバージョニングになっております。

RMagick 4.1.0 へ移行する

ver 4.0.0 未満をアプリで使用されている場合には、まず RMagick 3.2.0 へバージョンを上げてテストを実行してみてください。

warning: Image#combine is deprecated; use ImageList#combine.

ver 4.0.0 で廃止されるメソッドを使われている場合には上記のような warning メッセージが表示されるので、メッセージに沿って修正が必要となります。

warning メッセージが表示されない場合は、そのまま ver 4.0.0 と ver 4.1.0 へスムーズにバージョンをあげることができるはずです。テストはそれぞれの段階で十分に実施してください。

ImageMagick 7 へ移行する

ImageMagick 7 へ移行しても同様の画像が出力されるようにいくつかバグを直してもらっていたり、メモリ使用量を改善するためのAPI を追加してもらっていたりするため、ImageMagick 7 の最新版を使用されることを推奨します。 (#1160, #905, #894, #891, #817

また、ImageMagick 7 では High dynamic-range imaging (HDRI) というものがデフォルトで有効になっています。詳細はよくわかっていないのですが、色深度を変えようとしたりすると ImageMagick 6 と 7 では差がでますのでご注意ください #892

imagemagick formula のアップデート方法(備忘録)

RMagick が ImageMagick 7 に対応しそうなのだが Homebrew で提供されているバージョンだとビルドに失敗するので、アップデートをリクエストした。

やっていることは、ほぼ imagemagick@6 formula のアップデート方法(備忘録) - @watson1978 の日記 とおなじ。

$ export IMAGEMAGICK_VERSION="7.0.9-26"
$ curl -O https://imagemagick.org/download/releases/ImageMagick-$IMAGEMAGICK_VERSION.tar.xz
$ export SHASUM256=`shasum -a 256 ImageMagick-$IMAGEMAGICK_VERSION.tar.xz | awk '{print $1}'`

$ cd $(brew --repository homebrew/core)
$ brew bump-formula-pr imagemagick --strict \
--url=https://dl.bintray.com/homebrew/mirror/ImageMagick-$IMAGEMAGICK_VERSION.tar.xz \
--mirror=https://www.imagemagick.org/download/releases/ImageMagick-$IMAGEMAGICK_VERSION.tar.xz \
--sha256=$SHASUM256

imagemagick@6 formula のアップデート方法(備忘録)

ImageMagick がちょいちょい細かくバージョンを上げてくれるので、自分が使いたいバージョンにあげたときの作業ログです。 私が作業したのは https://github.com/Homebrew/homebrew-core/pull/40921 です。

Pull Request を作成するところで hub が使われるのでインストールしておく。

$ brew install hub

Pull Request を送れるように、手元の Repository に https://github.com/Linuxbrew/brew/blob/master/docs/How-To-Open-a-Homebrew-Pull-Request.md#formulae-related-pull-request に従い Repository を fork し remote を指定しておきます。

$ cd $(brew --repository homebrew/core)
$ git remote add <YOUR_USERNAME> https://github.com/<YOUR_USERNAME>/homebrew-core.git

環境変数 HOMEBREW_GITHUB_API_TOKEN で GitHub の API トークンを設定しておく必要がありました。

あとは、以下の様に bump-formula-pr コマンドを実行すると、テスト実行やら Pull Request の作成まで自動でやってくれます。

$ export IMAGEMAGICK_VERSION="6.9.10-96"
$ curl -O https://imagemagick.org/download/releases/ImageMagick-$IMAGEMAGICK_VERSION.tar.xz
$ export SHASUM256=`shasum -a 256 ImageMagick-$IMAGEMAGICK_VERSION.tar.xz | awk '{print $1}'`

$ cd $(brew --repository homebrew/core)
$ brew bump-formula-pr imagemagick@6 --strict \
--url=https://dl.bintray.com/homebrew/mirror/imagemagick%406-$IMAGEMAGICK_VERSION.tar.xz \
--mirror=https://www.imagemagick.org/download/releases/ImageMagick-$IMAGEMAGICK_VERSION.tar.xz \
--sha256=$SHASUM256

どうも ImageMagick が特殊らしく、Pull Request がマージされると --url で指定した URL へ --mirror からコピーされるようです。ImageMagick 公式サイトでは最新の tar ball しか保持していないからいろいろ都合が悪いのでしょうか。

そのせいか、本来は --sha256 を指定しなければ自動計算されるらしいのですが、いちいち指定しないと駄目っぽい。(sha256 の自動取得の際に --url の URL が使われるのだがそこにはまだファイルが存在していないため。) という作法を知らなかったため、ずいぶんハマりました(・ω・)

RMagick のメモリ使用量を改善した

【追記 2019/06/17】パッチを含んだ RMagick v3.2.0 がリリースされました。

RMagick を使用すると馬鹿みたいにメモリ使用量があがると言われ続けてましたが、修正方法の見込みがたちようやく直しました。

何が起きていたか

RubyKaigi 2019 で登壇した際にも話しましたが、RMagick が引き起こしていた多数のメモリリークは修正済み だったので、Ruby の GC が動作すると抱え込んでいる不要なメモリ領域は解放されていました。

問題は Ruby の GC がなかなか動作してくれないことにありました。例えば RMagick で画像用のオブジェクトを生成しても、サイズはたかだか 40 bytes しかありません。

require 'rmagick'
require 'objspace'

img = Magick::Image.new(1000, 1000)
puts ObjectSpace.memsize_of(img) #=> 40 bytes

Magick::Image.new() の引数の値を変更してもサイズが変わることなく、巨大な画像を生成しても Ruby は 40 bytes 確保されたんだとしか認識してくれませんでした。実際はそんなことはなく、RMagick が ImageMagick に対して指定のサイズで画像用の領域をを確保しろと指示するため ImageMagick 内で大量のメモリが消費されていました。

require 'rmagick'

1000.times do |i|
  img = Magick::Image.new(1000, 1000)
end

のようなコードを実行すると、以下のグラフのようにメモリ使用量が線形的に増加していました。

f:id:Watson:20190615171348p:plain

画像用のオブジェクトに関連するメモリ領域を ImageMagick が大量に抱え込んでいても、Ruby から見るとたかだか 40 bytes ずつしか使用量が増えてないため、まだまだ GC を実行しなくても平気だろうと認識され、このようなグラフが描かれます。

解決案

  1. Ruby には rb_gc_adjust_memory_usage() という API があり、ImageMagick が確保したメモリ使用量を通知すれば、Ruby の GC が適切に動きそう。(参照:Ruby 2.4の新機能:rb_gc_adjust_memory_usage() - バインディングとGCの関係を改善するAPI
  2. ImageMagick がメモリを確保・解放するときに Ruby の xmalloc / xfree を使ってくれれば、ImageMagick を含めた全体のメモリ使用量を Ruby が把握できる。

幸い、ImageMagick にはユーザー定義のメモリアロケータをセットできる API が用意されていたので、方法 2 を採用しました。 (https://github.com/rmagick/rmagick/blob/9a704f18f3688a7bb98cad2264681cfb1519103c/ext/RMagick/rmmain.c#L110)

(内部でどれだけメモリを確保したかを教えてくれる API が ImageMagick には無かったため 1 の方法は無理そうだった)

ImageMagick の修正

ImageMagick でユーザー定義のメモリアロケータを使わずに直接 malloc を使っている箇所がありました。その部分でのメモリ確保量が結構大きいらしく xmalloc / xfree を使うようにしても、まだ期待通りに GC が動いてくれませんでした...

github.com

パッチはすでに取り込まれているため、Ruby の GC を適切に動作させるのに必要な情報はとれるようになりました。

結果

ImageMagick と RMagick を修正しました。これにより、以前は不要なオブジェクトがたかだか 40 bytes しかメモリを使ってないからまだ GC を動かすのを止められていたのが、オブジェクトに関連するメモリ領域を ImageMagick が大量に抱え込んでいるのも把握できるようになったので Ruby の GC が適切に動作するようにしました。

修正パッチが含まれている ImageMagick が ver. 6.9.10-49 と ver. 7.0.8-49 としてリリースされています。そのバージョンを使用すると、以下のグラフの赤線のようなメモリ使用量に落ち着きます。

f:id:Watson:20190615173713p:plain

RMagick の修正はこちら。もうすこしで RMagick v3.2.0 としてリリースできそうです。

github.com

ImageMagick の ver. 6.9.10-49 と ver. 7.0.8-49 以降と組み合わせて使うと効果は絶大ですが、古い ImageMagick でも多少は効果がありそうな感じでした。

RubyKaigi 2019 で登壇しました。

RubyKaigi 2019 CFP を書いた - @watson1978 の日記 で応募した CFP が無事採択されたので、RubyKaigi 2019 で登壇してきました。CFP を採択、運営に携わったスタッフの皆さんに感謝です。

CFP にはエモいことを書いたりしましたが、RMagick がどう改善されていてどういう方向性で進むのかがみなさん興味あるでしょうから、それを全力で話したつもりです。至らない点があったら申し訳ない。

スライドの英語を添削してくれた RMagick Team メンバーにも感謝です。

以下は自分の印象に残ったことを。

0 日目

「RubyKaigi 2019 前夜祭 - Ippudo Party!! -」に参加いたしました。ペパボさんで「大名エンジニアカレッジ」というのを始めるそうで、igaigaさんが大名に任命されてた。おめでとうございます(\( ⁰⊖⁰)/)

1 日目

いよいよ Ruby 3.0 が現実的になってきたなと。キーワード引数に非互換な変更がRuby 3.0に入るそう。Ruby 3.0で動かないコードをRuby 2.7で警告するようになるそうなので、あらかじめ潰しておかないと 2020 年に痛い目にあいそうです。

通訳さん大変ですよね。事前にどんな発表するか原稿っぽいのを日本語で書いてたのをスライドに付けて提出していたのですが、打ち合わせ開始時に雑に質問した結果、

そりゃそうだとか思った。距離感は縮まったと思う。

2 日目

大体パフォーマンスが絡むトークに出ていました。「Six Years of Ruby Performance: A History」で Rails アプリが Ruby 2.0 から 2.6 で 172 % ほどパフォーマンス向上しているとか。スライドを見直したいので資料公開されてないだろうか・・・。

RubyData Workshop に参加してきました。定期的に「OSS Gate東京ミートアップ for Red Data Tools」に参加させていただいているのですが、Workshopで普段どんな活動をしているか軽く話させていただきました。メンバー増えるかな?

Vladimir さんのトークを聞くのは2度目なのですが、Ruby の JIT でいきなりネイティブバイナリにする代わりに MIR という中間層を入れたいみたいな話が変態過ぎてすごかった。

3 日目

MessagePack の最適化がヤバいし、トークが終わったあとに質問から突然の開発者会議っぽいことを arm社の方々が繰り広げ始めてやばさしか無かった。

ActiveRecord に Apache Arrow を組み込む話は未来を感じたので是非実現させたい。

卜部さんの最適化に関する話は職人的なものを感じた。すごさしかない。

Closing keynoteの最適化の話は Ruby のコードをどう組み直すと何パーセント早くなったか、今回の RubyKaigi で私が一番多く Ruby のコードを見たトークでした。実際のアプリで全ての技をつぎ込むのは厳しいというかメンテナンス困難になりそうだったが、frozen string literal とか簡単なところからはじめて行きたい。

Xcode を使って Ruby C 拡張ライブラリをデバッグ

RMagick のテストが通らないものを調べる際にどのようにデバッグしようかと思ったのですが、Debugging Ruby C Extensions in XCode - Emil Soman's blog に有益なことが書かれておりました。元の記事では Xcode 5.1 が使われていたので最新の Xcode のスクリーンショットを交えつつ手順を書いてみます。

この記事では Ruby C 拡張ライブラリの C 言語で書かれたコードをデバッグする手法を記述します。

デバッグ対象のコードを入手

$ git clone https://github.com/rmagick/rmagick.git

Makefile を作成

Ruby C 拡張ライブラリの C 言語のコードは ext ディレクトリ配下にあります。RMagick では ext/RMagick となります。そこへ移動し Makefile を作成します。デバッグしやすいように CFLAGS を通じて -O0-ggdb3 を指定します。

$ cd ext/RMagick
$ CFLAGS="-O0 -ggdb3" LDFLAGS="-L`brew --prefix gmp`/lib" ruby extconf.rb

Xcode から make すると gmp のライブラリが見つからない旨のエラーが出てしまったので、ここでは LDFLAGS の設定も行っています。

Xcode のプロジェクトを作成

Xcode 起動時に表示される Welcome 画面からですと、赤枠で囲んだ部分をクリックして新規プロジェクトを作成します。 f:id:Watson:20190202070015p:plain

Cross-Platform タブに表示される External Build System というテンプレートを選択します。 f:id:Watson:20190202070232p:plain

プロジェクト名などは適当に埋めておきます。 f:id:Watson:20190202070415p:plain

プロジェクトの保存場所はわかりやすい場所にしておくと良いでしょう。

External Build Tool Configuration

make コマンドをどのディレクトリで実行するか指定します。ext/RMagick を指定します。 f:id:Watson:20190202070943p:plain

Ruby の実行ファイルを準備

Xcode で Ruby の実行ファイルを簡単に指定できるように準備します。ターミナルで以下のコマンドを実行します。

$ ln -sf `rbenv which ruby`

これで rbenv でインストールした Ruby の実行ファイルのシンボリックリンクができました。

Ruby の設定

Xcode で Edit Scheme... をクリックします。 f:id:Watson:20190202071712p:plain

Executable のところで Other... を選択し、先ほど準備した Ruby の実行ファイルのシンボリックリンクを選択します。 f:id:Watson:20190202071918p:plain

次に作業ディレクトリの設定を設定します。Options のタブを選択し Working Directory のところを設定します。 f:id:Watson:20190202072453p:plain

次に Arguments のタブに移動し、テスト用の Ruby ファイルを指定します。 f:id:Watson:20190202072709p:plain

デバッガが起動される際に実行される際に渡され、以下の様な感じで展開されます。

lldb -- ruby test.rb

テスト用のファイルには以下の様に、デバッグしたい内容に応じて書いて ext/RMagick に置いておきます。

require './RMagick2.so'

image = Magick::Image.new(20, 20) {
  self.background_color = 'gray50'
}

p image.gray?

ソースコードを追加

Xcode 左下にある + ボタンをクリックし Add Files to.... を選択します。 f:id:Watson:20190202073209p:plain

ext/RMagick 内の C 言語ファイルを追加します。 f:id:Watson:20190202073327p:plain

以上でデバッグの準備が完了です。お疲れ様です。

デバッグ

デバッグしたい箇所に Xcode でブレークポイントを設置し、▶ボタンをクリック (or command + R)で実行します。 f:id:Watson:20190202073523p:plain

あとは Xcode を操作したり、lldb のコマンドを実行したりしながらデバッグを進めて行きます。

まとめ

準備が若干面倒ですが、Xcode のインタフェースでデバッグできるようになって便利です。