Ruby での統計処理は R より遅いものの結構速い

Rails アプリでまとまった数の DB のレコードをロードし、統計処理をして結果を DB に記録するということをしたところ、完了するまで10時間以上かかってしまいかなり遅いと感じた。 改善したいと思いコードを見直そうとしたのだが、これを R で実装したらかなり改善できるのではないかと思った。 既存のコードを R に移植するのはそこそこ時間がかかりそうだったので、簡略化した処理で実行時間の比較をし、

比較方法

  • DB から 10 万件のレコードを読み、単純移動平均 (n = 25) を計算して DB に記録する
  • 実行時間は time コマンドの結果とする

環境および実装

DB

テストデータのテーブル定義は以下

-- 統計対象
CREATE TABLE `test_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `value` float NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

-- 単純移動平均の結果
CREATE TABLE `smas` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `value` float NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Ruby
require "active_record"
require "activerecord-import"

class TestData < ActiveRecord::Base
end

class SMA < ActiveRecord::Base
  def self.calculate(test_data, n)
    0.upto(test_data.size - 1 - n).map do |i|
      [test_data[i, n].sum(&:value) / n]
    end
  end
end

ActiveRecord::Base.establish_connection(adapter: "mysql2", database: "testdb", username: "user", password: "password")

columns = %w(value)
test_data = TestData.all
sma_values = SMA.calculate(test_data, 25).compact
SMA.import(columns, sma_values, validate: false)

R

  • R 3.5.1
  • RMySQL
library(RMySQL)

conn <- dbConnect(dbDriver("MySQL"), dbname = "testdb", user = "user", password = "password")
dat <- dbReadTable(conn, "test_data")
sma <- na.omit(filter(dat$value, rep(1:1, 25)))

valuesSQL <- paste(
  "(NULL, ",
  sma,
  ")",
  sep = "",
  collapse = ","
)
insertSQL <- paste(
  "INSERT INTO `smas` (`id`, `value`) VALUES",
  valuesSQL
)

dbSendQuery(conn, insertSQL)

dbDisconnect(conn)

実行結果

今回試した環境では、Ruby での実装は R の 3.5 倍ほどの時間がかかることがわかった。

> time bundle exec ruby calculate.rb 

real    0m4.210s
user    0m3.175s
sys     0m0.129s
> time Rscript --vanilla calculate.R 
 要求されたパッケージ DBI をロード中です 
<MySQLResult:-1925505208,0,1>
[1] TRUE
 警告メッセージ: 
Closing open result sets 

real    0m1.273s
user    0m0.576s
sys     0m0.031s

なお Ruby においては bundle exec ruby -r active_record -r activerecord-import -e "" だけでも 0.5 秒程度かかるので、実質的な差はより少ないと考えられる。

まとめ

Ruby による実装では R よりも時間がかかったものの、R の 3.5 倍程度の差にとどまるということがわかった。 これは処理対象となるレコードが 10 万件のものなので、レコード数や性能要件によっては Ruby で実装しても問題ないと判断することも多いと思われる。

また Ruby でも ActiveRecord モデルを使用せずレコードのデータを Array の Array でロード・処理することでより高速化はできるかもしれない。

なおここで使用したコードは GitHub に push した (nowlinuxing/activerecord-vs-R) ので興味のある方はどうぞ。

久しぶりにPCを自作した

直近2年ほどはメインマシンとしてDELLのノートPC xps13を使っていた。しかしメモリ8GB、CPUコア2つというスペックでは仮想マシンを起動するとパフォーマンスにかなり不満を感じるようになったのと、AMDRyzenによるCPUの多コア戦争勃発により自作PCにかなり魅力を感じるようになってきたので作ってみた。

要件

PCに求めるものは以下の通り。

  • Rails、Dockerによる開発がメイン
    • 普段は長時間軽負荷 + 短時間高負荷と思われるのでIdle時を省電力に
  • IntelAMDの競争が激化し陳腐化が進みそうなので、ハイエンドよりはコストパフォーマンス重視(特にCPUとマザーボード)
  • 仮想マシンを複数起動しても困らないようメモリ多め
  • コンパクト
    • GPUは当面不要だし、ストレージや光学ドライブなどは必要に応じてUSB接続するつもりなので容積は必要最小限
  • VGA出力は4k2つ以上。そのうち少なくとも1つはHDMI
    • できればHDCP対応
  • WindowsなしでBIOSアップデートできること
    • Linuxで運用を想定
  • ギガビットLAN

PCの構成

まずCPUを選択する。 シングルスレッド性能は当面それほど必要なさそうなのでTDPは65w以下で検討。RyzenはIdle時の消費電力が低くならないようなのでパス。 これらの中からコスト・ワットパフォーマンスに優れたCPUということでCore i5を使用することにした。 他のパーツは適当に。

OSはUbuntu 18.04。 ディスプレイ、マウス、キーボードは手持ちのものを流用。

結果

ベンチマークなどは用意していないので、わかる範囲で。

パフォーマンス

ruby-build で2.6.0-preview1 をインストールしてみた結果

$ time rbenv install 2.6.0-preview1
Downloading ruby-2.6.0-preview1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.0-preview1.tar.bz2
Installing ruby-2.6.0-preview1...
Installed ruby-2.6.0-preview1 to /home/masaoo/.rbenv/versions/2.6.0-preview1


real    1m54.284s
user    4m10.769s
sys 0m26.207s
消費電力
  • 起動時は 25w ほど
  • サスペンド時は 2w
  • OS 起動後の Idle では 15~16w
  • Ruby 2.6.0-preview1 インストール時では、一瞬 57w ほどになるもののそれ以外では 25w 程度

Ubuntu のインストール時になぜかフリーズしてしまう現象が発生したので、電力不足を疑いTurbo BoostをDisableにしたら無事完了した。 上記計測中もこの設定はそのまま。

これ以外の設定もいじればもっと省電力にできるかもしれないが、構成を考える際の参考にした5chのスレではZ370M-ITX/acの省電力設定には限界がありそうという考察もあるので難しいかも。

おわりに

費用は合計で11万円ほどだったと思う。お手頃な価格でそれなりにパワフルなPCができた。

私が自作PCに魅力を感じるのはパーツ交換の自由度が高い、削るところと集中するところを選べる(事務用PCとゲーミングPCの二択みたいなことにならない)というのもあるが、割と大きいのはLinuxをインストールしたときのトラブルが少ない、ということだろうか。 というのも以前使用していたノートPCではVGAやACPI、Wifiあたりに問題が出ることが多く、ドライバの差し替えや設定の調整などでかなり面倒な作業が必要だった。

その点今回のPCはUbuntuインストーラで全てが問題なく動作したため、特別な設定はほとんど必要なくとても楽だった。 (SSD用の設定変更はしたがこれは自作、ノート関係なく必要なので例外とする) PCの自作なんて10数年ぶり(先代の自作機はDuron)だったのでキャッチアップがそこそこ必要だったけど、それもまた楽しかった。

VAIO F の HDD を交換した

2010 年のモデルの VAIO を使用しているのですが、随分前から HDD が手狭になってきたところに Windows 7 が起動しなくなってしまうという状況に陥ってしまったため、リカバリついでに HDD を大容量のものに交換しました。
交換したのは HGST の 1.5GB モデル。

さて、交換後に DVD でリカバリを行うと、完了後の Windows 7 再起動時に「このコンピュータのハードウェアで動作するようにwindowsを構成できませんでした」と表示され、それ以上進めることができません。
HDD は 2011 年頃から AFT に切り替わってきたそうで、非 AFT なシステムからアクセスするためにはドライバが対応している必要があるようですがこの VAIO F のリカバリ DVD で復元される Windows 7 はおそらく AFT 非対応なため、HDD にアクセスできなくなるという状態になるようです。
この対策を調べたところ、以下のページを見つけました。

価格.com - 『旧型VaioのHDD交換方法案 4Kバイトセクタ問題対策(参考まで)』 SONY VAIO Fシリーズ VPCF128FJ/B のクチコミ掲示板

この方法に従い

  1. リカバリー DVD でリカバリ
  2. 再起動後、Ubuntu の DVD で起動 (Windows を起動させてしまうとこれ以降の対策が無効になってしまうため注意)
  3. ドライバを差し替えるため、Windows パーティションの \Windows\System32\drivers\iaStor.sys を Download Intel® Rapid Storage Technology (RAID) for Legacy Intel® Desktop Boards からダウンロードできる STOR_all64_f6flpy_9.6.0.1014_PV.zip の ioStor.sys で上書き
  4. Ubuntu を終了しリカバリを継続

で無事リカバリすることができました。

VAIO FのUbuntuを13.04から13.10にした

アップグレードはいつも通りdo-release-upgradeでOKなのですが、輝度調整ができなくなる現象が再発。
そこでNVIDIAのドライバをインストールしなおそうとしたところ、エラーでインストールできない現象が発生。調べてみるとドライバのコンパイルが失敗しているらしいことが判明。
この辺を参照してまた輝度調整できるようにできました。

Ubuntu 13.04 をインストールした VAIO F で輝度を調整できるようにする

VAIO F (VPCF118FJ) にUbuntu 13.04をインストールして使用しています。
VPCF118FJにはNVIDIAGeForce 310Mが使用されているのですが、NVIDIAのドライバをインストールするとX Windowで輝度調整ができなくなり、最大輝度になってしまいます。
一応、nvidia-settingsという設定ツールを使えば調整できますが、ファンクションキーで調整するのに比べると面倒です。
そこで、nvidiablを使用して調整できるようにしてみました。
快適。

mrubyのdeb Packageを作ってみた

今でこそUbuntuを使っていますが、それまではずっとRedHat系のディストリビューションを使っていたので、RPMを作ったことはあったけどdeb Packageを作ったことはありませんでした。
そんなところ、つい先日、mrubyがGitHubに登録されました。
まだ日が浅く、deb Packageも存在していなさそうだったので、ここを参照しながら試しに作ってみました。

$ git clone https://github.com/mruby/mruby.git
$ mv mruby mruby-1.0.0
$ cd mruby-1.0.0
$ cat $PATCH
diff --git a/Makefile b/Makefile
index 5598868..b6f1ec1 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,10 @@ endif
 ALL_CFLAGS = -Wall -Werror-implicit-function-declaration $(CFLAGS)
 MAKE_FLAGS = --no-print-directory CC=$(CC) LL=$(LL)

+# installer
+BINDIR = $(DESTDIR)/usr/bin
+INSTALL = /usr/bin/install
+
 ##############################
 # generic build targets, rules

@@ -109,6 +113,11 @@ $(EXTS) : $(EXTRB)
 $(OBJM) : $(MSRC)
        $(CC) $(ALL_CFLAGS) -MMD $(INCLUDES) -c $(MSRC) -o $(OBJM)

+# install
+install :
+       [ -d $(BINDIR) ] || mkdir -p $(BINDIR)
+       $(INSTALL) -m 0755 bin/mruby bin/mrbc $(BINDIR)
+
 # clean up
 .PHONY : clean
 clean :
$ patch -p1 < $PATCH
$ dh_make -e [メールアドレス] -p mruby_1.0.0 -s --createorig
$ rm debian/*.ex debian/*.EX
$ dh_installdirs
$ dh_installdocs
$ dh_installinfo
$ debuild

結構簡単にできるんでびっくり。