株式会社クリアコード > ククログ

ククログ


RubyKaigi 2019 - Better CSV processing with Ruby 2.6 #rubykaigi

RubyKaigi 2019の2日目にBetter CSV processing with Ruby 2.6という話をした須藤です。今年もクリアコードはシルバースポンサーとしてRubyKaigiを応援しました。

関連リンク:

内容

この1年、 @284km と一緒にcsvを改良していたのでその成果を自慢しました。せっかく2人で話をするので、時間を分割してそれぞれ話をするのではなく、ずっと掛け合いをしながら2人で話をしました。楽しんでもらえたでしょうか?

Ruby 2.6.3に入っているcsvはどんなケースでもRuby 2.5のcsvより高速になっています。この成果を使うためにぜひ最新のRubyにアップグレードしてください。

最後にも少し触れましたが、まだまだcsvやその周辺に改良すべきことがあります。今回の話でcsvの開発に興味がでてきた人は一緒に改良してきましょう。その気になった人はRed Data ToolsのGitterに書き込んでください。どうやって進めるか相談しましょう。

RubyKaigi 2019でまわりの人たちと相談してstrscanのメンテナンスを引き取ることにしたのでそのあたりの改良もできます。

RubyData Workshop

発表の後、RubyData WorkshopでもRed Data Toolsの開発に参加する人を募りました。すでに数人Red Data ToolsのGitterに書き込んでいる人もいます。やったね!

まとめ

RubyKaigi 2019で最近のcsvの開発の成果を自慢しました。今回は2人での発表だったのでやいのやいのした発表をしました。

Red Data Toolsの仲間が増えたのでよいRubyKaigiでした。

タグ: Ruby
2019-04-20

Ruby 2.6.0とtest-unitとデータ駆動テスト

Rubyのbundled gemのtest-unitをメンテナンスしている須藤です。

歴史

test-unitはxUnitスタイルのテスティングフレームワークです。Rubyのテスティングフレームワークの歴史(2014年版)にまとめてある通り、Ruby本体に標準添付されています。

Rubyに標準添付されているライブラリーには実は次の3種類あります。

  • ただの標準添付ライブラリー(例:URI
    • requireするだけで使えるライブラリー
  • default gem(例:csv)
    • requireするだけで使えるライブラリー
    • RubyGemsで更新できる
    • Gemfileでgemを指定しなくても使える
  • bundled gem(例:test-unit)
    • requireするだけで使えるライブラリー
    • RubyGemsで更新できる

どれも標準添付ライブラリーなのでrequireするだけで使えます。違いはRubyGems・Bundlerとの関係です。

ただの標準添付ライブラリーはRubyGemsでアップグレードすることはできませんし、Bundlerで特定のバージョンを指定することもできません。使っているRubyに含まれているものを使うだけです。

default gemはRubyGemsでアップグレードすることもできますし、Bundlerで特定のバージョンを指定することもできます。Bundlerを使っていてgem名を指定しなかった場合は使っているRubyに含まれているものを使います。

bundled gemはRubyGemsでアップグレードすることもできますし、Bundlerで特定のバージョンを指定することもできます。Bundlerを使っていてgem名を指定しなかった場合は使えません。Bundlerを使っていなければrequireするだけで使えます。

Ruby 2.6.0でより高速になったcsvはRuby 2.6.0からdefault gemになっています。

test-unitはRuby 2.2.0で再度標準添付されるようになってからbundled gemになっています。

そんなtest-unitのデータ駆動テスト機能をさらに便利にしたものがRuby 2.6.0に入っています。

データ駆動テスト

データ駆動テストとは同じテスト内容をいろいろなデータで実行するテスト方法です。パラメーター化テストと呼ばれることもあります。いろいろな入力に対するテストを簡潔に書きたいときに便利です。

test-unitでは結構前からデータ駆動テストをサポートしています。

たとえば、正の数同士の足し算と負の数同士の足し算をテストすることを考えます。データ駆動テスト機能を使わない場合は次のようにそれぞれのケースについてテストを作ります。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  def test_positive_positive
    assert_equal(3, my_add(1, 2))
  end

  def test_negative_negative
    assert_equal(-3, my_add(-1, -2))
  end
end

データ駆動テスト機能を使う場合はテストは1つで、テストに使うデータを複数書きます。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  data("positive + positive", [3, 1, 2])
  data("negative + negative", [-3, -1, -2])
  def test_add(data)
    expected, augend, addend = data
    assert_equal(expected, my_add(augend, addend))
  end
end

データが増えてくるほど、データ駆動テスト機能を使った方がテストを書きやすくなります。データを追加するだけで済むからです。ただ、読みやすさは従来のテストの方が上です。テストに使うデータがベタ書きされているからです。

test-unitのデータ駆動テスト機能をもっと知りたくなった人はRuby用単体テストフレームワークtest-unitでのデータ駆動テストの紹介を参照してください。

データ表生成機能

Ruby 2.6.0に入っているtest-unitではデータ駆動テストがさらに便利になっています。

まだなんと呼ぶのがよいか決めかねているのですが、今のところデータ表(data matrix)と呼んでいるものを生成する機能が入っています。

データ表というのは各テストで使うデータをまとめたものです。前述のテストの場合は次のようになります。dataを使う毎に1行増えます。

ラベル expected augend addend
"positive + positive" 3 1 2
"negative + negative" -3 -1 -2

このデータ表をいい感じに生成する機能が入っています。

前述のテストで正の数と負の数を足す場合もテストしたくなったとします。その場合、従来のデータ駆動テスト機能の書き方では次のように書きます。dataを2つ増やしています。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  data("positive + positive", [3, 1, 2])
  data("negative + negative", [-3, -1, -2])
  data("positive + negative", [-1, 1, -2]) # 追加
  data("negative + positive", [1, -1, 2])  # 追加
  def test_add(data)
    expected, augend, addend = data
    assert_equal(expected, my_add(augend, addend))
  end
end

データ表は次のようになります。

ラベル expected augend addend
"positive + positive" 3 1 2
"negative + negative" -3 -1 -2
"positive + negative" -1 1 -2
"negative + positive" 1 -1 2

データ表生成機能を使うと次のように書けます。dataの第一引数にSymbolを指定しているところがポイントです。テストに渡されるデータはHashになっていてキーがシンボルで値が対象データです。

require "test-unit"

class TestAddDataMatrix < Test::Unit::TestCase
  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

これで次のデータ表を生成できます。

ラベル augend addend 備考
"addend: 2, augend: 1" 2 1 正+正
"addend: 2, augend: -1" 2 -1 正+負
"addend: -2, augend: 1" -2 1 負+正
"addend: -2, augend: -1" -2 -1 負+負

期待する結果(expected)は生成できないのでRuby組み込みのInteger#+の結果を使っています。これは実は大事なポイントです。データ表生成機能を使えるのは次の場合だけです。

  • 期待する結果がデータに依らず一意に定まる
  • データから期待する結果を計算できる

今回の場合は期待する結果を計算できるので使えました。

なお、期待する結果は必ずしも正しい結果を返すはずの既存の実装(今回の場合はInteger#+)を使わなくても大丈夫です。次のように「エンコードしてデコードしたら元に戻る」ようなときでもデータ表生成機能を使えます。これは性質をテストしているケースです。(性質をテストすることについてはここを参照してください、とか書いておきたいけど、どこがいいかしら。)

assert_equal(raw_data,
             decode(encode(raw_data)))

この例ではパラメーターはaugendaddendの2つでそれぞれに正と負があるので、4パターンでしたが、パラメーター数が増えたりバリエーションが増えると一気にパターンが増えます。そのときはこのデータ表生成機能が便利です。

なお、この機能はRed Chainer(Rubyだけで実装しているディープラーニングフレームワーク)で使うために作りました。もともとRed Chainerのテスト内でデータ表を生成していたのですがこの機能を使うことでだいぶスッキリしました。

データを使い回す

実はRed Chainerのテストをスッキリさせるためにはデータ表を生成するだけでは機能が足りませんでした。同じデータ表を複数のテストで共有する機能が必要でした。

前述の例で言うと、同じデータ表を足し算のテストでも引き算のテストでも使いたいという感じです。コードで言うと、以下をもっといい感じに書きたいということです。

require "test-unit"

class TestCalc < Test::Unit::TestCase
  data(:number1, [1, -1])
  data(:number2, [2, -2])
  def test_add(data)
    number1 = data[:number1]
    number2 = data[:number2]
    assert_equal(number1 + number2,
                 my_add(number1, number2))
  end

  data(:number1, [1, -1])
  data(:number2, [2, -2])
  def test_subtract(data)
    number1 = data[:number1]
    number2 = data[:number2]
    assert_equal(number1 - number2,
                 my_subtract(number1, number2))
  end
end

そこで、dataメソッドにkeep: trueオプションを追加しました。これで一度dataを書けば後続するテストでも同じデータを使うようになります。

require "test-unit"

class TestCalc < Test::Unit::TestCase
  data(:number1, [1, -1], keep: true) # keep: trueを追加
  data(:number2, [2, -2], keep: true) # keep: trueを追加
  def test_add(data)
    number1 = data[:number1]
    number2 = data[:number2]
    assert_equal(number1 + number2,
                 my_add(number1, number2))
  end

  # ここにdataはいらない
  def test_subtract(data)
    number1 = data[:number1]
    number2 = data[:number2]
    assert_equal(number1 - number2,
                 my_subtract(number1, number2))
  end
end

データ表を複数生成する

実はRed Chainerのテストをスッキリさせるためにはデータを使い回せても機能が足りませんでした。1つのテストに対して複数のデータ表を生成する機能が必要でした。

前述の例で言うと、小さい数同士と大きい数同士で別のデータ表を作りたい、ただし、小さい数と大きい数の組み合わせはいらないという感じです。(わかりにくい。)

データ表で言うと次の2つのデータ表を使う感じです。

小さい数用のデータ表:

内容 augend addend
小さい正 + 小さい正 2 1
小さい正 + 小さい負 2 -1
小さい負 + 小さい正 -2 1
小さい負 + 小さい負 -2 -1

大きい数用のデータ表:

内容 augend addend
大きい正 + 大きい正 20000 10000
大きい正 + 大きい負 20000 -10000
大きい負 + 大きい正 -20000 10000
大きい負 + 大きい負 -20000 -10000

コードで言うと、以下をもっといい感じに書きたいということです。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add_small(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end

  data(:augend, [10000, -10000])
  data(:addend, [20000, -20000])
  def test_add_large(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

そこで、dataメソッドにgroup:オプションを追加しました。同じグループ毎にデータ表を生成します。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  data(:augend, [1, -1], group: :small) # 小さい数用
  data(:addend, [2, -2], group: :small) # 小さい数用
  data(:augend, [10000, -10000], group: :large) # 大きい数用
  data(:addend, [20000, -20000], group: :large) # 大きい数用
  def test_add(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

setupでもデータを参照可能にする

実はRed Chainerのテストをスッキリさせるためにはデータ表を複数作れても機能が足りませんでした。テスト実行中にデータを参照しやすくする機能が必要でした。

従来のデータ駆動テスト機能ではテストメソッドの引数でデータを渡していました。そのため、setup中でデータを参照できませんでした。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  def setup
    # ここでデータを参照できない
  end

  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

Red Chainerのテストではデータを前処理したかったので次のように明示的に前処理メソッドを呼んでいました。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  def my_setup(data)
    # 前処理
  end

  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add(data)
    my_setup(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

これは微妙なのでdataでデータを参照できるようにしました。

require "test-unit"

class TestAdd < Test::Unit::TestCase
  def setup
    p data # データを参照できる!
  end

  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add(data)
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

また、テストでも引数でデータを受け取らなくてもよくなりました。(従来どおり受け取ってもよいです。)

require "test-unit"

class TestAdd < Test::Unit::TestCase
  def setup
    p data # データを参照できる!
  end

  data(:augend, [1, -1])
  data(:addend, [2, -2])
  def test_add # test_add(data)としなくてもよい!
    augend = data[:augend]
    addend = data[:addend]
    assert_equal(augend + addend,
                 my_add(augend, addend))
  end
end

まとめ

Red Chainerのためにtest-unitにデータ表生成機能を追加しました。Ruby 2.6.0にもこの機能を使えるtest-unitが入っています。ぜひ活用してください。

なお、Ruby 2.6.0でなくてもRubyGemsで新しいtest-unit(3.2.9以降)にアップグレードすれば使えます。Red Chainerでもそうやって使っています。

Red Chainerの開発に参加したい人はRed Data Toolsに参加してください。オンラインのチャット東京で毎月開催している開発の集まり(次回は2018年1月22日)でどうやって進めていくか相談しましょう。

タグ: Ruby
2018-12-26

Ruby 2.6.0とより高速なcsv

Rubyの標準添付ライブラリーのcsvをメンテナンスしている須藤です。

歴史

csvは名前の通りCSVを読み書きするための便利ライブラリーです。

もともとRuby本体とは別に開発されていたのですが、Ruby 1.8.0のときにRuby本体にバンドルするようになりました。dRubyやREXMLがRuby本体にバンドルされたのも同じタイミングです。Ruby 1.8.0のときにバンドルするライブラリーをすごく増やしたのです。(その頃の様子がわかるURLをここに置いておきたかったけど見つけられなかった。。。)

Rubyではcsvのようにrequireするだけで使えるライブラリーを「標準添付ライブラリー」と呼んでいます。Stringのようにrequireしなくても使えるライブラリーは。。。なんだろう。組み込みクラスかしら。

その後、Ruby 1.9.0のタイミングで実装をFasterCSVに置き換えました。FasterCSVは名前の通りもともとのcsvよりも速いライブラリーです。もともとのcsvもFasterCSVもRubyだけで実装してあり、Cを使っていません。Rubyで実装したCSVライブラリーでは最速です。今のcsv(FasterCSVベースのcsv)よりも速いといっているCSVライブラリーはCを使っているはずです。

そんなcsvをさらに速くしたものがRuby 2.6.0に入っています。

FasterCSV実装がなぜ速いか

FasterCSVがなぜ速いかというと各行をline.split(",")でパースしているからです。String#splitはCで実装されているので速いのです。

ただ、世の中にはline.split(",")でパースできないCSVがたくさんあります。たとえば、次のようなCSVです。

a,"b,c",d

このCSVではダブルクォートで囲んでいる中にコンマがあるのでline.split(",")では次のようにパースしてしまいます。

[
  "a",
  "\"b",
  "c\"",
  "d",
]

このようなケースにも対応するために、FasterCSVはline.split(",")した後の各要素のダブルクォートの数を数えます。ダブルクォートの数が偶数ならダブルクォートの対応が取れていて、奇数なら取れていないというわけです。ダブルクォートの対応が取れていない場合は後続する要素と連結します。

このようにして速さを維持したまま複雑なCSVもパースできるようになっています。ただ、複雑なCSVをパースするときは速度が落ちてしまいます。次の表はcsvが使っているベンチマークを使ったパース性能の計測結果です。複雑になるほど性能が落ちている(単位時間あたりでのパース回数が減っている)ことがわかります。

100msでのパース回数
ダブルクォートなし 373
ダブルクォートあり 207
ダブルクォート中にコンマあり 140
ダブルクォート中に改行あり 82

Ruby 2.6.0に入っているcsvでは次のようになります。「ダブルクォートあり」の場合は少し性能が落ちています(207から194に減っている)が、ダブルクォート内が複雑になっても「ダブルクォートあり」と性能が変わりません。(「コンマあり」と「改行あり」が193と192で194とほとんど変わらない。)「ダブルクォートなし」の場合は少し性能があがっています。(373から401に増えている)

100msでのパース回数
ダブルクォートなし 401
ダブルクォートあり 194
ダブルクォート中にコンマあり 193
ダブルクォート中に改行あり 192

Ruby 2.6.0のcsvがなぜ速いか

「最速」だったcsvがどうやってさらに速くなったかというとStringScannerを使うようになったからです。StringScannerは標準添付ライブラリーの1つで、正規表現を使って高速に文字列をスキャンできます。

ただ、単にStringScannerを使っても「最速」だったcsvよりも速くはなりません。line.split(",")は強敵です。@284kmが取り組んだ、まずline.split(",")StringScannerに置き換えるpull requestでも全体的に遅くなっています。ただ、これでも高速にするための工夫をした後の結果です。Red Data Toolsの開発の集まりなどで@284kmと一緒に高速にするための書き方を模索していました。その結果、次の知見を得ました。

  • どの正規表現を使ってどの順番でスキャンするかが重要

StringScannerを使ったコードは次のようなコードになります。ポイントは「次はこういう値が来るはず、来なかったらエラー」というのをつなげていくところです。

row = []
scanner = StringScanner.new(line)
column_value = scanner.scan(/[^",\r\n]+/) # カラムの値
raise "no column value" unless column_value
row << column_value
raise "no comma" unless scanner.scan(/,/) # カラムの区切り文字(コンマ)
column_value = scanner.scan(/[^",\r\n]+/) # 次のカラムの値
raise "no column value" unless column_value
row << column_value
raise "no comma" unless scanner.scan(/,/) # カラムの区切り文字(コンマ)
raise "extra data" unless scanner.eos?    # すべてのデータを使ったか
p row

CSVのように複雑なものだと、「次はこういう値が来るはず、来なかったら別のこの値なはず」というようにフォールバックしていきます。たとえば、「ダブルクォートで囲まれていない値があるはず、なかったらダブルクォートで囲まれた値のはず」といった具合です。

line.split(",")を超える性能を出すためにはフォールバックをいかに減らすかが大事になります。フォールバックのオーバーヘッドがあるとline.split(",")に負けてしまうのです。

フォールバックを減らすには「次はこういう値が来るはず」ができるだけ当たるような順番にします。カラムの値の次はコンマがきやすいので、次の2つでは後者の方がフォールバックの回数が減ります。

カラムの値もコンマも並列に扱う(フォールバックが多い):

row = []
column_value = nil
until scanner.eos?
  if scanner.scan(/[^",\r\n]+/) # カラムの値
    column_value = scanner.matched
  elsif scanner.scan(/,/) # コンマ
    row << column_value
    column_value = nil
  else
    raise "invalid"
  end
end
row << column_value if column_value
p row

カラムの値の後はコンマがくるはず(フォールバックが少ない):

row = []
until scanner.eos?
  if (column_value = scanner.scan(/[^",\r\n]+/)) # カラムの値
    if scanner.scan(/,/) or scanner.eos?
      row << column_value
    else
      raise "no comma"
    end
  else
    raise "invalid"
  end
end
p row

line.split(",")に勝つには正規表現のマッチ回数をいかに減らすかを頑張る必要があります。これが基本的なコンセプトです。それではさらに具体的な方法を説明していきます。

行ごとの処理をやめる

line.split(",")ベースのアプローチでは次のようにダブルクォート中が複雑になる処理で性能劣化が大きかったです。

100msでのパース回数
ダブルクォートあり 207
ダブルクォート中にコンマあり 140
ダブルクォート中に改行あり 82

これを解決するために行に分割してから処理することをやめました

行に分割せずに、ダブルクォート中がどうなっていても(たとえば改行文字を含んでいても)統一的に処理することで性能劣化を防ぎました。

100msでのパース回数
ダブルクォートあり 194
ダブルクォート中にコンマあり 193
ダブルクォート中に改行あり 192

これが一番大変でした。というのは、パースするロジックをすべてStringScannerらしく書き換える必要があるからです。

書き換えた後は次のようなコードになりました。すっきりですね。

row = []
while true
  value = scanner.scan(/[^",\r\n]+/)
  if scanner.scan(/,/)
    row << value
  elsif scanner.scan(/\r\n/)
    row << value
    p row
    row = []
  elsif scanner.eos?
    row << value
    p row
    return
  else
    raise "invalid"
  end
end

これでダブルクォートを使っていても性能劣化しなくまりました。(ダブルクォート中に改行がある方が速くなっているのはなぜだ。。。)

100msでのパース回数
ダブルクォートあり 165
ダブルクォート中にコンマあり 160
ダブルクォート中に改行あり 187

これを実現することによりコードをメンテナンスしやすくなり、最適化や機能追加をしやすくなりました。line.split(",")ベースのコードも200行未満の実装なのでそんなに長すぎるわけではないのですが、状態が多くて適切な場所に適切な処理を入れるのが難しかったのです。

以前と同じくらいの性能にできればStringScannerベースのパーサーで開発を進められます。

loopwhile trueにする

性能改善の大きなポイントは正規表現のマッチ回数を減らすことですが、それ以外の部分でも少しずつ性能改善できます。その1つでやりやすいものがloop do ... endではなくwhile true ... endでループするようにすることです。

Rubyを使っている場合はdo ... endを使いたいので私は普段は次のようにループを書きます。

loop do
  # ...
end

しかし、今回のように性能改善したいケースでは次のようにwhile true ... endを使った方が高速です。これはloopだとdo ... endの中でスコープが変わるのでその準備をしないといけないのに対し、whileはスコープが変わらないのでその準備が必要ないからです。

while true
  # ...
end

csvのケースではwhile trueの方が15%ほど高速です。

100msでのパース回数:

ダブルクォートなし ダブルクォートあり
loop 377 166
while 401 192
string[start...end]string[start, end - start]にする

Stringには文字列データの一部を共有する機能があるため、既存のStringの一部で必要なStringを作れる場合は共有機能を使うことで高速になります。

文字列データを共有するにはString#[]を使います。String#[]は便利なメソッドでいろんな引数を受けつけます。たとえば、次の2つは同じ結果を返します。

string[1...6]
string[1, 5]

ただし、string[1, 5]の方が速いです。これは、引数が2つの場合は特別扱いされているためです。

csvの場合、string[1, 5]のスタイルを使った場合の性能改善の度合いは軽微です。

100msでのパース回数
string[1...6] 405
string[1, 5] 409
必要になるまで処理を遅らせる

みなさんはCSV#lineというメソッドがあるのを知っていますか?私は知りませんでした。このメソッドは最後に処理した行そのもの(パース前の行)を返します。普通はパース結果だけを使うので、この処理のために通常のパース処理が遅くなるのは微妙です。そのため、このための情報を必要になったときにはじめて取得するように変更しました。

100msでのパース回数
#line用のデータを逐次処理 409
#line用のデータを遅延処理 416

微妙に速くなっています。

通常は必要ない処理を必要になるまで処理しないことによる高速化はCSVの書き込み処理で顕著です。

CSVはCSVを読み書きできるのでインスタンスを作るときに読み書き両方用の初期化をしていました。そのため、CSVを読むだけ、書くだけのときに余計な処理をしていました。

この読む用の初期化・書く用の初期化を必要になるまで遅延するようにしました。これにより、読むだけのときは書く用の初期化を一切しなくなり、高速になります。

以下は書き込み処理のベンチマーク結果です。

100msでの処理回数:

CSV.generate_line CSV#<<
読む用の初期化を毎回実行 350 2177
読む用の初期化を遅延実行 850 3506

2倍ほど速くなっています。CSV.generate_lineCSVオブジェクトを作らずに1行生成する便利機能ですが、たくさんの行を生成する時はCSVオブジェクトを作った方が高速です。これは、CSV.generate_lineの場合は1行生成する度に書く用の初期化を毎回しなければいけないためです。

つまり、次のように書くのは遅いということです。

rows.each |row|
  puts CSV.generate_line(row)
end

それよりは次のように書いた方が速いです。

output = ""
csv = CSV.new(output)
rows.each |row|
  csv << row
end
puts output
String#each_charではなくString#indexを使う

読む用の初期化時に改行文字を自動検出しているのですが、そこも高速化できた処理でした。

従来はString#each_charで一文字ずつ確認していたのですが、そこをString#indexを使って書き換えました。次のような感じです。

cr_index = sample.index("\r")
lf_index = sample.index("\n")
if cr_index and lf_index
  if cr_index + 1 == lf_index
    "\r\n"
  elsif cr_index < lf_index
    "\r"
  else
    "\n"
  end
elsif cr_index
  "\r"
elsif lf_index
  "\n"
else
  :auto
end

String#indexを使うとCレベルで文字チェックをできるので速くなりました。(後でどのくらい速くなったか追記できるといいな。)

なんか野暮ったいコードなのでもう少しシュッとできるといいですね。

効果がなかった高速化

これで速くなるんじゃないかと試したものの逆に遅くなったアイディアもありました。

特化メソッドを持つモジュールをextend

csvにはまじめにパースするモードとゆるくパースするモードがあります。従来はメソッド内でifで分岐していました。インスタンス作成時にモードにあわせたモジュールをextendしてパース時はメソッド内のifを減らすと速くなるのではないか、という案です。こんな感じです。

module StrictMode
  def parse
    # ...
  end
end

module LiberalMode
  def parse
    # ...
  end
end

class CSV::Parser
  def initialize(options)
    if @options[:liberal_mode]
      extend LiberalMode
    else
      extend StrictMode
    end
  end
end

CSV::Parser.new(:liberal_mode).parse # LiberalMode#parse

実際にやってみてところむしろ遅くなりました。メソッド内でifで分岐する方が速かったです。

さらに速いCSVパーサー

csvはRubyレベルで実装してあるCSVパーサーでは最速です。さらに速くするにはCで実装する必要があります。たとえば、Cを使っているfastest-csvはcsvよりも数倍高速です。

100msでのパース回数
csv 16
fastest-csv 76

なお、私のオススメはApache Arrowです。Apache ArrowはCSV用のライブラリーではありませんが、CSVパーサーもついています。Apache ArrowのCSVパーサーを使うとfastest-csvよりもさらに数倍高速です。

100msでのパース回数
csv 16
fastest-csv 76
Apache Arrow 223

使い方も簡単です。次のコードでCSVをロードできます。

require "arrow"
Arrow::Table.load("/tmp/a.csv")

参考:Apache Arrowの最新情報(2018年9月版)

今後

コードを整理でき、最適化・機能拡張の準備ができました。たとえば、次のような改良をしていきたいです。興味がある人は一緒に開発しましょう。

  • バックスラッシュでダブルクォートをエスケープ #61
  • クォート文字を指定しなかったらline.split(",")を使う高速化 #56
  • ヘッダーがあるときの高速化 #59

まとめ

Ruby 2.6.0にあわせてcsvのコードを整理して高速化しました。より開発しやすいコードベースになったので一緒に開発していきましょう。

リリース直前にいろいろ変更をぶちこんでごめんなさい。

タグ: Ruby
2018-12-25

RubyData Tokyo Meetup - Apache Arrow #RubyData_tokyo

Apache ArrowのC・Ruby・パッケージ関連を主に開発している須藤です。

RubyData Tokyo MeetupでApache ArrowのRubyまわりの最新情報を紹介しました。

関連リンク:

内容

(いつ頃か忘れましたが)前にApache ArrowのRubyまわりを紹介した時はデータ交換まわりの話がメインでした。それは、データ交換まわりの実装しかなかったからです。

しかし、最近はデータ処理まわりの実装も進んできたので、そのあたりのことも盛り込みました。たとえば、素のRubyの機能で数値演算する場合と、Numo::NArrayを使って数値演算する場合と、Gandiva(Apache Arrowの式処理モジュール)を使って数値演算する場合のコードとベンチマーク結果を紹介しました。

私のマシンで計測したところNumo::NArrayが一番高速でした。Numo::NArrayすごい!発表中、@sonotsさんがNumPyの方がさらに速いと思うけどねーと言いながら同じパターンをNumPyでも計測していました。計測したところ、NumPyよりもNumo::NArrayの方が速く、@naitohさんもその場で計測したところ、確かに速かったです。この内容はその後の@naitohさんの発表に盛り込まれています。発表をきっかけに新たな事実の発見が進むなんていい集まりですね!

他には最近Apache Arrowで実装が進んでいるCSVパーサーが速いよ!ということを自慢したりしました。

集まりに関して

今回の集まりはとてもいい集まりだなぁと思えるいい集まりでした。

@mrknさんがポジティブな話をするようになっていたのもよかったですし、Juliaバックエンド案は面白いなぁと思いました。

@shiro615さんのOSS GateワークショップでOSSの開発に参加しはじめて、Red Data Toolsで継続的にApache Arrowの開発に参加し続けて、この間コミッターになった、という話は感慨深かったです。OSS GateもRed Data Toolsもはじめてよかったな。

@hatappiさんがイベント中にRed ChainerのCumo対応ブランチをマージしていたのもよかったです。@sonotsさんの発表で変更の概要を聞いて、発表の後のコード懇親会で直接相談しながらマージ作業を進めていました。開発が進むなんて、なんていい集まりなんでしょう。

@sonotsさんはこのイベントがあったからCumo対応プルリクエストを作ったと言っていました。開発が進む集まり!

@colspanさんのMenoh-RubyとFluentdを使って推論サーバーを作る話は面白いなぁと思いました。なるほどなぁ。

Red Data ToolsとしてもMenohとMenoh-Rubyを応援していきたいので、いい感じに協力できないか少し相談しました。11月20日(火)の夜のOSS Gate東京ミートアップ for Red Data Tools in Speeeで続きを相談できそうです。

@v0droさんの発表でXND関連の理解が深まりました。調べないとなぁと思っていたんですよねぇ。型を文字列で定義するのは、いいのかな、悪いのかな。まだ判断できないんですが、面白いアプローチだなぁとは思いました。

Red Data ToolsとしてもXND関連の開発に協力していきたいな。

まとめ

2018年11月17日にRubyData Tokyo Meetupという開発が進むいい集まりがありました。

Rubyでもっといい感じにデータ処理できるようになるといいなぁ思った人は次のアクションとして以下を検討してみてください。

タグ: Ruby
2018-11-20

Windows Subsystem for LinuxでプレゼンツールのRabbitを動かす

皆さんはRabbitというプレゼンテーションツールをご存じでしょうか。2018年のRubyKaigiでのMatz氏によるKeynoteでも使われており、「名前は知らないが見た事はある」という方もいらっしゃるかもしれません。

RabbitはRubyで開発されており、Git等でバージョン管理しやすいMarkdown形式やRD形式などのプレーンテキスト形式でスライドを記述できたり、「スライドの進行割合」を表すウサギと「時間の経過度合い」を表すカメのアイコンでスライドの進行状況を把握できたりと、痒い所に手が届くツールです。

RabbitはWindowsでも動作するのですが、開発は主にLinux上で行われているため、Windows上では動作が不安定だったり表示が崩れたりと、期待通りの動作結果にならない場合があります。このような場面に出くわした場合、Windows上での動作を改善するためのフィードバックやパッチの作成に挑戦してみるいい機会なのですが、発表の日程が差し迫っているためそこに時間をかけられない、というような場合もあるでしょう。

一般的に、有志の個人開発者によって開発・メンテナンスされているフリーソフトウェアは、開発者自身が日常的に使っている環境に近い環境で最も安定して動作します。そういう意味では、仮想マシンを用意したり、使用していないPCを用意したりしてLinuxディストリビューションをインストールしたりという形をとるのが常道なのですが、この変形として、Windows 10であればWSL(Windows Subsystem for Linux)とWindows用のXサーバーを組み合わせるという方法が使えます。

以下、本記事での解説は、WSLのLinuxディストリビューションとしてUbuntuを使用している状況を前提とする事にします。

WSLでRabbitが動く理由

WSLは、Windowsのカーネルに対して、Linuxカーネルのように振る舞うための層を被せることで、UbuntuやFedoraなどのディストリビューションで提供されているビルド済みバイナリをそのまま動作させられるようにする技術です(動作イメージ、導入手順はまんがでわかるWSLなどをご覧ください)。

画面描画の基盤技術はWindowsとLinuxで顕著に異なる部分であるため、Windows向けにRabbitやその依存ライブラリ群を完全対応するのは非常に大変な作業です。しかし、WSLであれば基盤部分はLinux用の物がそのまま使われるため、Rabbitのように主にLinux上で開発されているGUIアプリは、「Windows用の移植版」を動かすよりも「WSL上でLinux用のオリジナル版」を動かした方が良い結果を得られる場合がままあります。

ただし、そのために1つだけ欠かせない物があります。それがWindows用のXサーバーです。

Windows Subsystem for Linuxで使えるXサーバー

X(X Window System)は、現在多くのLinuxディストリビューションで一般的に使われている、GUIを実現するための最も重要な基盤技術です。「物理的な画面をキャンバスとして管理し、各ウィンドウを描画する」「ユーザーのクリック操作やキー入力などの操作を一元的に受け付けて、情報を各アプリケーションに引き渡す」といった事を行う物なのですが、現在の所WSL上ではXサーバーは動作しません。何故かというと、WindowsはWindowsでXとは別の画面描画の仕組みを持っており、両方が同時に動作するとリソースの奪い合いになってしまう(マウスやキーの入力をWindowsが受け取ればXが受け取れなくなるし、Xがそれらを受け取ればWindowsが受け取れなくなる)からです。

そこで登場するのがWindows用のXサーバーです。これはWindowsアプリケーションの1つとして振る舞いつつ、一般的なXサーバーと同じ機能を提供する(アプリケーションがXに対して指示した位置にWindowsのウィンドウを開き、そのウィンドウに対して操作が行われた場合はXが操作を受け付けたものとしてアプリケーションに情報を引き渡す、という働きをする)物です。VcXsrv(※リンク先はダウンロードページ)はその代表的な例で、VcXsrvを起動しておき、WSL上のアプリケーションに対してVcXsrvをXサーバーとして使うよう指示する事により、WSL上のGUIアプリケーションがWindows上でも動作するようになります。

という事で、まずはリンク先のページのダウンロード用ボタンからVcXsvrのインストーラをダウンロードして、インストールしましょう。

VcXsrvを初めて起動すると、「Display settings」というタイトルのウィザードが表示されます。ここではとりあえず以下のように設定して下さい。

  1. 「Multiple Windows」を選んで、「次へ」を押す。
  2. 「Start no client」を選んで、「次へ」を押す。
  3. オプションは変えず、「次へ」を押す。
  4. 「完了」を押す。
  5. WindowsのファイアウォールがVcXsvrによるネットワーク接続に対して警告を出すので、接続を許可する。

初期設定を終えると、VcXsrvのアイコンがタスクトレイに表示されます。VcXsvrを使わないときは、このタスクトレイ上のアイコンから終了させておくと良いでしょう。

VcXsvrのアイコンがタスクトレイに表れている様子

Windows上のXサーバーを使うように、WSLの環境を設定する

Windows上でXサーバーが動作しているだけでは、WSL上のGUIアプリケーションは動作しません。WSL上のGUIアプリケーションに対して、Windows上のXサーバーを使うように指示する必要があります。これは以下のようにすれば実現できます。

  1. WSLのUbuntuが提供するBashを起動する。
  2. echo 'export DISPLAY=localhost:0.0' >> ~/.bashrc と実行する。
  3. exitでWSL上のBashを終了し、再度WSL上のBashを起動する。

この操作により、Bash起動時に読み込まれる初期化用の設定ファイルに、入出力用の画面としてWindows上のXサーバーが提供する仮想的な画面を使うように指示するための指定が追記され、それが反映された状態でBashが起動します。

Rabbitのインストール

Xサーバーの準備ができたら、いよいよRabbitのインストールです。Rabbitは、プラットフォームのパッケージ群と、RubyGemsのパッケージ群の2段階に分けてインストールする必要があります。

  1. Rabbitの実行に必要なパッケージ群をインストールする。 sudo apt install rubygems ruby-dev build-essential fonts-ipafont で一通りインストールされる。
    (フォントはfonts-notoなど他の選択肢もありますが、行の高さの違いが原因で表示が崩れてしまう事があるので、安全のためにはfonts-ipafontをインストールする事をお勧めします。)
  2. Rabbitをインストールする。 sudo gem install rake rabbit で必要なGemパッケージ群が一通りインストールされる。
    • この時、Windowsのファイアウォールがgemコマンドによるネットワークへの接続に対して警告を出すので、明示的に許可を与える。
    • 2019年4月11日追記:新しい環境で試行した際に、何故か依存パッケージがインストールされず、Markdown形式のファイルを開こうとすると[警告] サポートしていない形式です。(サポートしている形式: [Wiki, PDF, image, RD])というエラーが表示される結果となる事がありました。RabbitでMarkdown形式を取り扱うためにはkramdownkramdown-parser-gfmの両方が必要なので、どちらかがインストールされていないとこのエラーが表示されますので、パッケージの有無を確認し、無ければ追加でインストールして下さい。

以上でRabbitのインストールは完了です。

スライドの作成と実行

Rabbit用のスライドは、前述したとおりRDやMarkdownなどの形式で作成できます。試しに、以下のような内容でファイルを作成し、Windowsのデスクトップ上にsample.mdのような名前で保存して下さい。文字エンコーディングや改行コードは自動判別されますが、安全のためにはLinux上で一般的な「UTF-8(BOM無し)、改行コードLF」を使用する事をお勧めします。

# Rabbitでプレゼン

subtitle
:   WSL上のRabbitで表示

author
:   自分の名前

institution
:   所属会社

allotted_time
:   45m

# 準備

 * Xサーバーのインストール
 * Rabbitのインストール

# おわり

ご静聴ありがとうございます

Markdownファイルができたら、ファイルを置いたディレクトリーに移動し、ファイル名を引数に指定してrabbitコマンドを実行しましょう。RabbitのウィンドウがWindows上で開かれ、スライドの1ページ目が表示されます。

$ cd /mnt/c/Users/(ログオン中のユーザーアカウント名)/Desktop
$ rabbit ./sample.md

WSL上のRabbitのウィンドウがWindowsのウィンドウとして表示されている様子

Rabbitのスライドは、Enterキーで次のページに進み、BackSpaceキーで前のページに戻ります。サンプルを見ると分かる通り、最大レベルの見出しがそのままスライドの各ページのタイトルになります。詳しくはMarkdown形式でのスライドの書き方を参照してください。

ウィンドウの最下部のウサギは現在スライドの何ページ目が表示されているかを示しています。allotted_timeで時間を指定してある場合(この例では「45分」という意味になります)、その時間に合わせてウィンドウの最下部をカメが進んでいくようになります。カメが先行していれば進行が遅れ気味、ウサギが先行していれば逆に走り気味という事になります。

Rabbitのスライド下部に表示されているウサギとカメ

まとめ

以上、Windows上でWSL経由でRabbitを動作させる手順をご紹介しました。

Rabbitのサイトでは、Rabbitで作成された様々なスライドの例が公開されています。どんなスライドを作れるのか、ぜひ参考にしてみて下さい。

タグ: Ruby
2018-07-27

RubyKaigi 2018 - My way with Ruby #rubykaigi

RubyKaigiの2日目のキーノートスピーカーとして話した須藤です。今年もクリアコードはシルバースポンサーとしてRubyKaigiを応援しました。

関連リンク:

なお、RubyKaigi 2018に合わせてRabbit Slide Showをレスポンシブ対応したので、画面が小さな端末でも見やすくなりました。

内容

例年の内容よりキーノートっぽい内容にできるといいなぁと思って内容を考えました。最初は「インターフェイス」というテーマでまとめていたのですが、うまくまとまりませんでした。そのため、他の人と違う活動という観点でまとめてみました。その結果、「Rubyでできることを増やす」・「ライブラリーをメンテナンスする」という内容になりました。キーノートっぽかったでしょ?

この話を聞いて、私と同じように「Rubyでできることを増やす」・「ライブラリーをメンテナンスする」に取り組む人が増えるといいなぁと思ってこんな内容になりました。その取り組みの中で、Ruby本体をよくする機会もでてくるとさらにいいなぁと思っています。その気になった人はぜひ取り組んでみてください。

やりたいけどどこから始めればいいんだろうという人はRed Data Toolsに参加するのがよいでしょう。まずはチャットで相談したり東京での毎月の開発イベントに参加してください。

やりたくてお金はあるんだけど技術が足りない・時間が足りないという会社の人は、クリアコードにお仕事として発注してください。この話を聞いた人ならクリアコードに頼めば安心だと思ってくれるはず!ご相談は問い合わせフォームからどうぞ。

参考情報:

やりたいんだけど時間が足りないという人はクリアコードに入社して仕事としてやるのを検討するのはどうでしょうか。ただ、そういう仕事がないと仕事の時間ではできないですし、そもそも仕事がないと給料を払うのが難しいです。そういうことも考えた上でまだ選択肢としてよさそうならまずは会社説明会に申し込んでください。(このページの内容は少し古くなっているので更新しないといけない。。。。)

あ、そうだ、「クリアコードをいい感じにする人」として入社して、私が「Rubyでできることを増やす」・「ライブラリーをメンテナンスする」に使える時間を増やすという方法もあるかも。うーん、間接的すぎて微妙かな。。。

RubyData Workshop

RubyKaigi 2017に引き続き、RubyKaigi 2018でもRubyData Workshop(Data Science in RubyRed Data Tools Lightning Talks)を開催しました。Rubyでデータ処理したくなったでしょ?その気になった人はRed Data Toolsに参加して一緒に取り組んでいきましょう。

Data Science in Rubyの資料はRubyData/rubykaigi2018にあります。

Red Data Tools Lighting Talksの資料(の一部)は以下にあります。

なお、ワークショップのおやつのどら焼きはエス・エム・エスさんから提供してもらいました。ありがとうございます。

コード懇親会

Rubyは楽しくプログラムを書けるように設計されています。実際、RubyKaigiに参加するような人たちはRubyで楽しくプログラムを書いています。だったら、Rubyでコードを書く懇親会は楽しいんじゃない?というアイディアを思いつきました。それを実現する企画が「コード懇親会」です。実現にあたりSpeeeさんと楽天 仙台支社さんに協力してもらいました。ありがとうございます。

Speeeさんには運営や飲食物の提供などイベント開催のもろもろ、楽天さんには会場提供で協力してもらいました。参加者多数のため急遽定員を増やしたのですが、それにはSpeeeさんの飲食物の追加、楽天さんの机・椅子の追加がなければ実現できませんでした。

参加したみなさんは楽しんでくれたようです。興味がある人はアンケート結果を見てみてください。

参考情報:

懇親会の様子:

来年もあるかどうかはまだわかりません。「今回よかった!」と思った人はぜひインターネット上に思ったことなどをまとめてみてください。

今回はSpeeeさんに協力してもらいましたが、いろんなスポンサーが開催するようになるといいなぁと思っています。やりたい人・やりたい企業の方はぜひやってみてください。コード懇親会のリポジトリーのREADMEに説明がありますし、声をかけてもらえれば相談にのります。Speeeさんのコード懇親会レポートも参考にしてください。

なお、「コード懇親会」という企画をSpeeeさんが独占するよりもみんなで共有する方がSpeeeさんにとってメリットがあります。「最初に開催したのはSpeee」ということで名声が広まるからです。よさそう!と思った人はどんどん「コード懇親会」を開催してください。そのまま真似してもいいですし、アレンジを加えながら開催してもよいです。今回の実装を自由に使ってください。

リーダブルコードサイン会

3日目のAfternoon Breakのときにジュンク堂さんがサイン会をやっていました。通りかかったら長田さんに声をかけてもらったのでサイン会に混ぜてもらってリーダブルコードの解説にサインしていました。4,5冊売れました。「すでに持っている」という人の方が多かった気がします。いい本だから何冊あってもいいよね!

まとめ

RubyKaigi 2018でキーノートスピーカーとして話をしてきました。クリアコードは今年もシルバースポンサーとしてRubyKaigiを応援しました。

RubyData Workshop・コード懇親会・リーダブルコードサイン会のこともまとめました。

コード懇親会の進行のこともまとめようと思ったのですが、力尽きました。いつか、機会があれば。。。

タグ: Ruby
2018-06-04

GObject Introspectionを使ったRubyバインディングの開発方法

日本ではだいぶGObject Introspectionに詳しい方だと思っている須藤です。

バインディングの開発には5年くらい前からバインディングの開発にGOject Introspectionが有用だと思っています。

2013年には各種バインディング開発方法についてまとめたりGObject Introspection対応ライブラリーの開発方法を導入部分だけ説明したりしました。

2016年にはRubyKaigi 2016で各種バインディング開発方法を紹介しました。

2017年には名古屋Ruby会議03でGObject Introspectionを使ったバインディングの開発方法のRubyレベルの部分だけを紹介しました。

そして今年、1からGObject Introspection対応ライブラリーを開発する方法を1つずつ説明する文書をまとめました!OpenCVをGObject Introspectionに対応させています。GObject Introspection対応ライブラリーを開発するための日本語の文書としては一番よい文書になっているはずです。

この文書はRubyDataのリポジトリーで管理しています。RubyDataというのはSpeee@mrknさんが始めた取り組みです。Ruby用のデータ処理ツールを開発する人たちとそのツールを使う人たちを増やすことを目指しています。

これまで、RubyKaigi 2017でワークショップを開催したり、サイトで関連情報をまとめたりしていました。RubyKaigi 2018でもワークショップを開催する予定です。

Rubyで使えるデータ処理関連のライブラリーが増えるとRubyでできることが増えます。バインディングの開発はデータ処理関連のライブラリーを増やす1つのやり方です。GObject Introspectionが有用なケースもあるはずです。ぜひ、この文書を活用してRubyで使えるデータ処理関連のライブラリーを増やしていきましょう。

興味のある人はRed Data Toolsチャットルーム(オンライン)や東京で開催している開発イベント(オフライン)にどうぞ!

タグ: Ruby
2018-03-28

沖縄Ruby会議02 - Red Data Tools #okrk02

沖縄から東京へ帰る飛行機を待っている須藤です。25分遅延しているのでこれを書いています。

沖縄Ruby会議02でゲストスピーカーの1人としてRed Data Toolsの話をしました。

関連リンク:

内容

Red Data Toolsに参加する人(Rubyでもっと便利にデータ処理できるようになるために取り組む人)が増えるといいなぁと思って次のことを紹介しました。

  • Red Data Toolsのポリシー

    • こういうポリシーなら一緒に開発したい!と思ってもらえるかも!
  • Red Data Toolsで開発しているコード

    • こういうコードを一緒に開発したい!と思ってもらえるかも!

私達はインターネットを通じてやりとりできます。場所も時間も超えて一緒に開発できます。まずはチャットでなにに取り組むか相談するところから始めましょう。沖縄Ruby会議02に参加したみなさん、待っていますよ!!!

スライド中で紹介したコードや省略したコードは前述の「リポジトリー」の中にちゃんと動くコードとしてまとまっています。コードに興味がでてきた人はリポジトリーの中ものぞいてみてください。

スライドサイズ

会場が大学だったのでプロジェクターの出力の縦横比は4:3だと思っていたんですが、現地について確認してみたら16:9でした。琉球大学すごい!

Rabbit 2.2.2(未リリース)ではスライドの縦横比が16:9でもいい感じになるようになっています。Rabbit Slide Showgem pushすればスライドを公開できるかっこいいスライド共有サービス)も16:9に対応していて、前述の埋め込んだスライドも16:9になっています。

Rabbitは内容(ソース)と描画(テーマ・描画システム)を分離する設計になっています。(現実では内容の中に描画のための情報をちらちら入れちゃうけど。)

そのため、スライドの縦横比を4:3から16:9に変更したい場合はrabbitコマンドの引数に--size 800,450を指定するだけです。

% rabbit --size 800,450 red-data-tools.rab

あとはその縦横比に合わせてRabbitがいい感じにレイアウトを調整します。便利ですね!

まとめ

沖縄Ruby会議02でRed Data Toolsに参加しようぜ!という話をしました。

Rabbitの最近の新機能を自慢しました。

タグ: Ruby
2018-03-11

Rubyとクリアコード #ruby25th

これはRuby25周年へのメッセージです。

クリアコードの社長の須藤です。そんなにコミットしていないのであんまり自分から言わないのですが、Rubyのコミット権を持っています。

同い年のRubyコミッター8人の中では一番最初にコミット権をもらいました。2004年の1月のことなので、なんともう14年前!当時は大学生だったのですが、すごくドキドキしたことを覚えています。私がコミット権をもらったのは自分が作っているライブラリーがRuby本体に取り込まれたからなんですが、会ったこともない人がしっくりくると推薦してくれたことがうれしかったです。Rubyが使いやすいなぁと思って使っていたので、他のRubyistからしっくりくると思ってもらえる使い勝手のライブラリーを作れているのがわかったのが嬉しかったんでしょうねぇ。

クリアコードが始まったのは私が社会人になった2006年の7月で、私は(たしか)9月からクリアコードに合流しました。私の社会人歴のほぼすべてはクリアコードでのものです。

クリアコードはフリーソフトウェアを推進するのが一番大事な会社であって、Rubyを応援するのが一番大事な会社ではないので、クリアコードのメンバーみんながRubyistというわけではありません。クリアコードを始めた当時、Rubyistは私だけでした。

私はフリーソフトウェアも推進したいしRubyも応援したかったので、なにかしらRubyを活かせる場所をみつけてRubyを活用していました。たとえば、独立行政法人情報処理推進機構(IPA)平成20年度オープンソフトウェア利用促進事業(リンク切れ)が「迷惑メール対策でなにか」みたいなテーマで募集していたときは、「Rubyを組み込んだ迷惑メール対策システム」を応募しました。それに採択されてお金をもらって開発したのがmilter managerというフリーソフトウェアです。「Rubyを組み込むと動的にいろいろできて捗るよ!」というようにRubyを活かす場所を考えました。

milter managerの開発を始めたのが10年前なのですが、実は、milter managerきっかけでいくつかRubyをよくしたことがあります。1つがメモリーリークの修正で、もう1つが拡張ライブラリーのメモリー使用量を抑えやすくするAPIの追加です。

前者は再現スクリプトを作るのに数週間とか使った気がする(もちろん業務時間内でやっていた)ので、なかなか大変だったなぁという記憶があります。作業していたのは私ではないですが。

後者はmilter managerを開発し始めてから8年後の東京Ruby会議11での発表がきっかけで話が進みました。なにがつながるかわからないものですね。

直接お金を稼いでいるわけではないですが、仕事ですごくRubyを活用している例があります。それはRabbitという私がRubyを使って開発しているプレゼンテーションツールです。

クリアコードは「お客さんを探す」ではなく「お客さんに見つけてもらう」という仕事の探し方をしているので、クリアコードがいろいろ情報発信をすることはお金を稼ぐためにとても大事なことです。ここにいろいろ記事を書くこともそうですし、イベントで発表することもそうです。そして、イベントを発表するときに役立つのがRabbitです。

Rabbitは私が大学にいたときに研究関係の発表をするために作り始めたものです。私にとってはRubyで作ることが大事だったので、プレゼンテーションツールを作るためにRubyでできないことがあれば、それらをRubyでできるようにしながら作ってきました。たとえば、PDF出力機能GUI・画像処理・マルチメディア機能などです。これらの機能があるからRubyを使っているという人がいるといいな。

Rabbitはすごくヒットしているツールではありません。RubyのイベントでもRabbitを使っている人は極少数派です。ただ、まつもとさんがMagicPointからRabbitに乗り換えたので、ヒットしていなくても私は満足です。みんながまつもとさんのいい話を聴けるのは私のおかげでもあるはず!(RubyKaigi 2017でまつもとさんに教えてもらったRabbitの問題の修正は25周年イベントには間に合わなかったなぁ。残念。)

本当のところを言うと、私は自分が使うために作っているのでユーザーが私だけでも満足だったりします。言い方を変えると、ヒットしていようがしていまいが私は別にどうでもいいです。そういえば、ここ数年、なぜか私以外のクリアコードのメンバーもRabbitを使っているのが不思議です。特に強制していないはずなんですが。。。

Ruby25周年ということで、クリアコードでのRubyの関わり方を一部紹介しました。Rubyを積極的に使っていく(人がいる)し、Rubyを使う中で得られた知見はRuby本体にフィードバックするし、まつもとさんのプレゼンをツールでサポートする、とかやっています。そうそう、RubyKaigiのスポンサーとしてお金を出すというのもやっていました。

これからも引き続き同じような感じでRubyと関わっていくつもりです。近い将来、Red Data Tools関連のことでも稼げるようになるといいなぁと思っています。今はあまり稼げていませんが、開発中に気づいたKeyErrorの改良案をRuby 2.6に入れたり、Rubyのcsvをよくしたり、Rubyでできることを増やしたり、といった点でRubyをよくすることはでき始めています。

タグ: Ruby
2018-02-23

RubyKaigi 2017 - Improve extension API: C++ as better language for extension #rubykaigi

RubyKaigi 2017で拡張ライブラリー関連の話をしてきた須藤です。クリアコードはシルバースポンサーとしてRubyKaigi 2017を応援しました。

関連リンク:

内容

C++11を活用するともっと拡張ライブラリーを書きやすくなるよ、という内容でした。詳細は事前情報を読んでください。

個人的には今後の拡張ライブラリー開発にプラスになるとても実用的な話をしたつもりだったのですが、あまり反響がなかったので、よさを伝えきれなかったのだと思います。残念。

誰も質問してくれなかったので付録の生のC API以外で拡張ライブラリーを書く方法の比較はお蔵入りになりました。聞きたい人はなにかのイベントに呼んでください。

Red Data Tools

発表の反響はあまりなかったですが、Red Data Toolsの反響はありました。

RubyData Workshop in RubyKaigi 2017の1つとしてSpeee@hatappiさんとRed Data Toolsの紹介をしました。(@hatappiさんがメインで説明・進行をして、私はたまに補足するスタイル。)

来週の火曜日(9月26日)の夜にSpeeeさんで開催するRed Data Toolsの開発イベントOSS Gate東京ミートアップ for Red Data Tools in Speeeの参加者が増えました。オンラインで相談する場所はGitterのred-data-tools/jaにあるので、開発イベントに参加できない人も一緒に開発しましょう!

Rubyでデータ処理できるようにしたいみなさん、一緒に開発していきましょう!

自分達は開発できない・開発する時間がないけどお金は出せるという場合は、クリアコードに開発の仕事を依頼するというやり方があるのでお問い合わせください。

OSS Gate

Red Data Toolsと同じようにOSS Gateも反響がありました。RubyKaigi 2017 前夜祭安川さんが紹介してくれたのと、Speeeさん・永和システムマネジメントさん・ドリコムさん・ピクシブさんのブースにチラシを置いてもらったのが大きいです。ありがとうございました!

おかげで広島でもOSS Gateの活動を始められそうです。Gitterのoss-gate/hiroshimaで相談しているので、広島でもOSSの開発に参加する人が増えるとうれしい人は参加してください。

全国のOSS Gateワークショップ開催情報は次の通りです。近隣で開催している場合はぜひビギナー・サポーターとして参加してください。

まとめ

RubyKaigi 2017の発表内容と成果を紹介しました。

タグ: Ruby
2017-09-22

タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|