AutoHotKeyでWindows7のキー操作をカスタマイズする

先日パソコンを買ったのですが、Windows7は思ったより快適に動いてくれます。ただ、何点か困ったことがありまして、その一つが秀Capsが正常に動作しないことがあるということです。(こちらのスレッドにユーザーからの報告があります。)

秀CapsというのはWindowsのキーボード操作をいろいろと使いやすくしてくれるソフトで、WindowsXPではとてもお世話になっていました。僕が使っていたのは、

  • 変換キーが押されたら必ずIMEをonにし、無変換キーが押されたら必ずoffにする(トグル操作でない)
  • シフトキーを押さなくてもアンダースコアが入力されるようにする

という二つの機能で、前者はGoogle IMEのキー設定でなんとかなったのですが、後者の方はどうにもなりません。プログラミングではアンダースコアは入力する頻度がとても高いので、いったんシフトなしで入力するのに慣れてしまうともう戻れないです。

いろいろなツールを試してみたのですが、結局AutoHotKeyというツールを用いてカスタマイズすることにしました。AutoHotKeyというのは、何と形容すればよいかわからないくらいいろいろなことができるソフトなのですが、要するにいろんなキー操作にフックして何かをするソフトです。例えばWindowsキーとgキーを押せばブラウザでGoogleを開く、というようなこともできます。設定ファイルが非常に複雑なのが難点ですが、それさえ乗り越えればかなり強力です。そしてこれが重要なのですが、64bit版を含むWindows7で問題なく動作します。

設定ファイルの例などは、こちらのサイトがとても参考になりました。

さて、「シフトキーを押さなくてもアンダースコアが入力されるようにする」を実現する方法なのですが、これは要するに日本語キーボードの「ろ」のキーが押された状態をフックして、_が入力されたことにすればよい、ということです。AutoHotKeyの設定ファイルでは、例えば@キーが押されたのを_に置き換えるのであれば、

@::_

でOKです。コロン二つの前にフック対象を書いて、その後ろに置き換え対象を書きます。

ただ、少し厄介なのは、日本語キーボードの「ろ」は、押すと円マークが入力されるのですが、これをフック対象にするとバックスペースの左のキーが押されたことになってしまうということです。これを回避するには、本家にも解説がある通り、AutoHotKeyの機能で対象のキー固有のコード(スキャンコード)を調べて、それを直接指定してやる必要があります。結論としては、日本語キーボードの「ろ」はSC073というスキャンコードになりますので、

SC073::_

でいけました。

Windows7はキー入力のフックの仕組みがかなり変わったらしく、従来のソフトが動かなくなることは多いようですが、AutoHotKeyがあればたいていのことは実現できそうです。

STORMのBTOでFF14向けPCを組んでみました

物欲というのは不思議なもので、全然欲しいと思わなかったWindowsのデスクトップPCが、最近欲しくなってきました。きっかけはFF14が出るということです。FF11がとてもよくできたゲームであるということを常々聞いていましたので、FF14はこのタイミングでぜひ始めてみたいと思ったのですが、その要求スペックが凄まじいのです。公式ベンチマークの結果から、FF14を快適に遊ぶためには以下のようなスペックのPCが必要ということがわかってきました。

CPU
物理4コアが望ましい。Hyper-Threadingによる論理コアの拡張はあまり効かない。ということで、Core i7Core i5の7XX系が必要ということになる。
メモリ
4GBあれば大丈夫。
ビデオカード
Radeon系が有利。できればHD5850かHD5870が欲しい。

もちろんこれを満たしていなくてもゲームはできるのですが、新しくPCを組むのであれば、できるだけ快適にプレイできる環境にしたいものです。

こういう情報が明らかになってきて、実際にどんなPCを買うのか検討し始めたのが7月初めごろ。そこからさらに情報を集めていくと、ベータテストをやっている人たちの中では、どうやらRadeon系が必ずしも有利とは言えない、ということになっていることがわかりました。公式ベンチマークで動いているプログラムは、どうやら現在動いているものとは大分異なるもののようなのです。

迷ってしまって決められれないところに、7月後半になって、GeForceの新しいGPUであるGTX 460がFF14の推奨GPUになるという発表がありました。GTX 460というGPUは、PC Watchの記事ベンチマーク結果などを見てもかなり素性の良いGPUのようなので、ビデオカードはこれに決めて買ってしまうことにしました。

といっても、GTX 460を搭載したPCは現状ほとんどありません。お店で売っているようなPCは全滅ですし、DELLやHPのBTOでも選択することができません。選択肢としては、マウスコンピュータか、もしくはドスパラなどのショップブランドPCということになります。マウスコンピュータで先に挙げたスペックのPCを買うとだいたい11万円くらいなので、これで良いかなと思っていたのですが、電源が500Wというところが心配になり、かと言ってその上を選択するとランクが上がってしまって+2万円を覚悟しなければならない、ということで、あまり買う気が起こらなくなってしまいました。

そこでいろいろ他のを探すうちに見つけたのが、STORMというBTOのお店です。STORMはBTOというよりはパソコン自作代行というような趣旨のお店で、CPUやグラフィクスカードはもちろん、ケースやマザーボードまでカスタマイズすることができます*1。CPUファンや電源も選択することができて、静音化で気をつけることができるのも嬉しいところ。

上記スペックを満たしつつなるべく静音で、かつ値段も抑えるということから、以下のような構成にしてみました。(一応Storm Power Speedという機体がベースなのですが、ほぼ原型を留めなくなっています。)

ケース:[http
//kakaku.com/item/K0000019019/:title=Cooler Master Sileo 500]:ケースはPCのエクステリアを決めると同時に、冷却と静音化の要でもあります。GTX 460というハイスペックなGPUを積むわけですから、ある程度冷却にも気を使わないといけませんし、かと言ってうるさいPCになってしまうと台無しです。今回は、静音ケースとしては定番のANTEC SOLOと迷ったのですが、前方と後方に12インチのファンがあるということを重視してSileoにしました。
電源:[http
//kakaku.com/item/K0000020936/:title=ENERMAX ECO80+ EES620AWT]:電源はPCの安定性を担保する上で非常に重要なパーツです。また、静音性や電力効率にも関係します。今回は、少し値段は高いのですが、自作では定番のENERMAXにしました。620Wあれば、GTX 460のシングルなら問題なく動作するはずです。
マザーボード:[http
//kakaku.com/item/K0000117679/:title=MSI P55A-G55]:マザーボードはパソコンの機能の大部分を決めてしまうパーツです。定番のASUSにするという手もあったのですが、値段が少し安かったのと、PCI Expressのx8スロットがひとつ空いているということを重視してこちらにしました。ただ後で書きますが、これは少し失敗だったかもしれません。
CPU:[http
//kakaku.com/item/K0000132929/:title=Core i5 760]:本当はCore i7 860か870にしたかったのですが、860と値段の差が5000円近くあったのでこちらにしました。760と860の違いはHyper-Threadingの有無だけなので、今回のスペックは760でも満たせます。
CPUクーラー:[http
//kakaku.com/item/K0000062800/:title=Scythe MUGEN∞2(無限2)リビジョンB SCMG-2100]:あまり目立たないのですが、CPUクーラーも静音のためには重要なパーツです。CPUクーラーの選択肢がここまであるBTOはなかなかないです。今回は静音かつ冷えることで評判のよかった、無限2にしました。背の高いCPUクーラーなので、うまくケースに収まるかどうかが不安点ではあります。(一応スペックを見る限りでは大丈夫そうでしたが…)
メモリ:4GB(2GBx2)
製造元を指定するオプションもあったのですが、今回は値段重視で無指定としました。
ビデオカードGeForce GTX460 1GB
これは要求スペック通りです。ビデオカードの型番まで指定できるとよかったのですが、残念ながら今回はできませんでした。
ストレージ:[http
//kakaku.com/item/K0000067169/:title=HGST HDS721050CLA362 (500GB SATA300 7200)]:これも低発熱・静音で定番のパーツです。本当はSSDにできたらよかったのですが、ゲーム用途だとどのくらいのサイズにしたらよいのかわからなかったのと、値段の関係上、今回は見送りました。

他にDVD Super Multiドライブ、16メディアカードリーダー、Windows 7 Home Premium 64bit DSPなども付けています。値段は送料込みで12万円ちょっとでした。マウスコンピュータで買うより少し高くなってしまいましたが、電源が良いものなので妥当なのではないかと思います。

注文してすぐにメールが来て、一応3営業日出荷可能ということだったのですが、少し遅れて5営業日で出荷ということになりました。BTOといってもほとんど一から組むのと変わらない構成になっているので、遅れたのは仕方ないと思います。というかきちんと対応して仕上げるのはさすがプロだと思います。


こんな感じの箱に入ってきます。PCはさらにケースの箱に入ってました。マザーボード等の箱もそのまま送られてきます。

中身はこんな感じ。CPUクーラーが巨大です。メモリモジュールが完全に隠れているので、メモリを増設するときはクーラーを外さないといけないっぽいです。ケースの下にしいてある黒いものは、静音化のための防音材です。

ケースのカバーにもみっちり防音材が貼ってあります。

ビデオカードGALAXYというメーカーのものでしたが、残念ながらこれはGTX460の中では音が大きい方のカードだったようです。ショップの方も今在庫があるものを使うでしょうし、今回はカードの型番を指定できなかったので仕方ないですね…。

フロントファンは前方下部にあり、そのすぐ後ろにハードディスクのマウンタがあります。ハードディスクをフルに積んでしまうとフロントファンの風を遮ってしまうのはこのケースの弱点ですね。5インチベイは豊富にありますので、マウンタを使って5インチベイの方に積むなどの工夫が必要かもしれません。

前方からはこんな感じです。主張しすぎなくて良いですね。ちなみにディスクドライブはLGのものでした。

説明書や余ったパーツ・ケーブル類はまとめて袋に入れてくれます。これは嬉しいです。

結構どきどきしながら電源を入れたのですが、当然ながら特に問題なく動作しました。自作をやったことがある人ならわかると思うのですが、最初に電源を入れたときにうまく起動するかというのが自作の最大の難関なんですよね。そこをお店できちんと確認してくれるというのは、かなりありがたいです。

ちなみにWindowsエクスペリアンスインデックスは、プロセッサ7.3、メモリ7.5、グラフィック7.4、ゲーム用グラフィックス7.4、ハードディスク5.9でした。この構成のPCとしては妥当な線だと思います。

静音性については、Sileoの二つの12インチファンがそこまで静音ではないのと、やはりビデオカードの音があるので、少し音は聞こえます。ただ通常使用では気になるレベルではないですし、ベンチマーク等を動かしていても、サウンドが鳴っていれば気にならなそうです。

マザーボードのところで少し失敗したかもと書いたのは、MSIマザーボードでCPUファンのアクティブな回転数制御がうまくできなかったからです。説明書にはPCのControl Centerというソフトでやると書いてあるのですが、設定してもファンの回転数が全然変わらない…。仕方ないので、アクティブな回転数制御ではなく、「CPUが50度になるまでは50%で回す、それを超えたら全開」というような設定にしています。ASUSならQ-Fanという仕組みで全自動の制御ができるみたいです。

というわけで、静音性についてはグラフィックスカードとCPUファンの制御という二点で問題が残ってしまったのですが、今後何らかの方法で問題を解決できるかもしれませんし、全体的な出来には満足しています。

STORMのBTOは、「パーツについてはある程度知っているが、組み立てるのはリスクがあるし大変」という人にはかなり良いサービスだと思います。サポートは例えばDELLやHPとは比べるべくもありませんので、自分で問題を解決する覚悟は必要ですが、逆にいうと手を入れやすくて良いのではないでしょうか。

*1:ケースやマザーボードが変わったらそれはもう別物なので、カスタマイズとは言わない気がしますが…

require高速化:基準となるベンチマーク

先日の記事でrequireの高速化をしたいと書いてから、ずいぶん時間が空いてしまいました。遅々とした進み具合で申し訳ないのですが、まずは高速化の基準となるベンチマークを取ってみました。基準点がなければ、具体的にどのくらい高速化したかわからないためです。

ベンチマークを行った環境

まずベンチマークを行った環境なのですが、「Windowsで遅い」と言われる問題の検証ですので、比較対象としてMacでも同じベンチマークを行うことにしました。マシンはいわゆるLate 2008 MacBook、スペックは以下の通りです。

CPU 2.4GHz Intel Core 2 Duo
メモリ 4GB 1067MHz DDR3
ハードディスク TOSHIBA MK3253GSX 320GB 5400RPM 8MB Buffer

また、WindowsのシステムはBootCampで起動したWindowsXP Home SP3、ファイルシステムはNTFSです。MacSnow Leopardファイルシステムは HFS+です。全く同じマシンで動作させていますので、システムの違いだけが出てくるはずです。

rubyのバージョンについては、基本的にはリリース済みの最新バージョンである1.8.7p249と1.9.1p378を用いています。Windowsではコンパイルの仕方がいろいろあるのですが、最もよく使われているmswin32(VC6でコンパイルされたもの)を用いています。ただしMacでは、MacPortsでインストールした1.8.7p249がうまく動作しなかったため、Snow Leopardに標準でインストールされている1.8.7p173を使用しました。

注意すべき点として、WindowsMacも、「ディスクキャッシュ」という仕組みがあります。これは一度ハードディスクから読み込んだファイルをメモリ上にキャッシュしておき、もう一度読み込んだときにはハードディスクからではなくメモリから読み出すものです。ディスクキャッシュがあるため、rubyスクリプトの起動は、1回目よりも2回目以降の方が断然早くなります。普通rubyで開発を行うときは、requireされるスクリプトのファイルはほぼディスクキャッシュに載った状態になっていると思われますので、今回はベンチマークを何度か実行し、ディスクキャッシュが有効になった状態で計測を行いました。

各ライブラリはどのくらいの数のファイルをrequireしているのか?

まず手始めに、時間を計るのではなくて、requireを行ったときにrubyのライブラリがどのくらいの数のファイルにアクセスするのかを調べました。これを調べておくことで、後からファイルアクセスに関して調査を行う際の基準になります。
これは、rubyのバージョンが同じならWindowsでもMacでも違いはないはずですので、Windowsでのみ調査しています。

調査に使用したスクリプトは以下のようなものです。requireがfalseを返した場合は、そのファイルを既にロードしているということなので数えていません。

alias :require_original :require

$require_success_files = []

def require(feature)
    ret = require_original(feature)
    if ret
        $require_success_files << feature
    end
    ret
end

require 'rexml/document'  # この行を調べたいライブラリに変更

p $require_success_files.size

ただし、rubygemsでインストールしたライブラリの場合は、rubygemsがrequireを書き換えますので、一番最初に先にrequire 'rubygems'を行っておく必要があります。

結果は以下のようになりました。(gem)と書かれているのは、rubygemsでインストールしたライブラリです。

ライブラリ名 1.8.7で読み込んだファイル数 1.9.1で読み込んだファイル数
rexml/document 35 35
rubygems 35 0
sqlite3-ruby(gem) 11 12
oauth(gem) 60 59
twitter(gem) 125 135

1.8.7と1.9.1では、rubygems以外はそれほど違いは出ていません。1.9.1でrubygemsが0になったのは、システムで組み込みになったためです。twitter gemの起動が重いというのは前から感じていたのですが、実際に100以上のファイルへのアクセスをしていたというのは驚きました。

各ライブラリをrequireするのにどのくらい時間がかかるか?

それでは次に、本題であるrequireの時間を計ってみます。

モジュール名 Windows 1.8.7 Mac 1.8.7 Windows 1.9.1 Mac 1.9.1
rexml/document 0.140625 0.029783 0.281250 0.058118
rubygems 0.171875 0.092663 0.000000 0.000113
sqlite3-ruby(gem) 0.031250 0.034831 0.187500 0.031970
oauth(gem) 0.187500 0.060517 0.406250 0.088203
twitter(gem) 0.546875 0.158961 0.921875 0.244374

1.8.7同士で比べると、ライブラリによってばらつきがあるのですが、確かにWindowsでは、Macの4倍以上の時間がかかることがあるようです。1.9.1では全体的に1.8.7より時間がかかるのですが、これはVM中間言語へのコンパイルなど、プログラムを実行する際の処理が複雑になったためで、全体的な速度向上とのトレードオフです。

ただ、さらによく見ると、1.8.7と1.9.1を比較したときに、Windowsの方がより遅くなる傾向があることがわかります。Macの場合は1〜2倍というところですが、Windowsの場合は6倍以上遅くなっているものもあります。これがなぜなのかも調べる必要がありそうです。

requireの個別のプロセスを計測する

requireがWindowsで遅いのはわかったのですが、requireはいろいろなプロセスを含んでいますので、Windowsでなぜ遅いのか調べるには、その個別のプロセスについて計測を行う必要があります。

requireのプロセスは、大まかに言って以下のとおりです。

  1. $LOAD_PATHに入っているパスのリストの中に、指定されたライブラリ名に.rbか.soを付けたファイルがないか調べる
  2. ファイルを見つけたらそれを読み込む
  3. 読み込んだプログラムを解析し、ロードする

遅い原因はこの中にあるはずです。さらに問題を分割すると、以下のような可能性があります。

  • ファイルが存在するかどうかのチェックが遅い。これはファイルを探す処理のスピードに効いてくる。
  • ファイルをオープンし、読み込むのが遅い。ディスクキャッシュが効いているはずだが、うまく効かないことがあるかもしれない。
  • プログラムの解析・ロードが重い。ただ、これはOSとほぼ関係がない、純粋なプログラム処理なので、これが遅いというのは、普通にプログラムの実行が遅いということになる。コンパイラの違いの可能性もある。

この推測を元に、requireに関するベンチマークプログラムを作成しました。プログラムはgithubで公開しています。

http://github.com/ashel/ruby-require-benchmark

使い方は、git cloneした後に

% ruby create_testdata.rb
% ruby run_benchmarks.rb

とします。今のところ11個のベンチマークがあり、それぞれbenchmark_*というファイルに記述されています。run_benchmarks.rbはそれらをまとめて実行するためのランチャです。なお、run_benchmarks.rbに引数を渡すと、その引数のコマンドでベンチマークを起動します。例えばrubyの1.9系をruby1.9というコマンド名で実行したい場合は、

% ruby1.9 create_testdata.rb
% ruby1.9 run_benchmarks.rb ruby1.9

とすればOKです。

今回作成したベンチマークは以下の通りです。

require_empty
空のファイル(実際にはmagic commentのみ含まれます)を100個requireします。空のファイル1つをrequireするのにかかる時間を求めます。
require_class
require_emptyと似ていますが、空のファイルではなく、よくあるクラスの定義1つ(数十程度のメソッド、定数を含むクラス)を含んだファイル100個をrequireします。equire_emptyと比較すると、ファイルの読み込みやプログラムのロードにかかる時間を推測できます。
require_class_onefile
require_classでrequireしたのと同じ構成のクラス100個が全て含まれるファイル1つをrequireします。require_classと比較することにより、多くのファイルを扱うのがどのくらい処理負荷になるのかを推測できます。
read_empty
require_emptyと似ていますが、requireするのではなく単に文字列としてreadします。requireのうちの「ファイルから読み込む」部分のみのコストを計測するためのベンチマークです。
read_class
require_classと似ていますが、requireするのではなく単に文字列としてreadします。
read_class_onefile
require_class_onefileと似ていますが、requireするのではなく単に文字列としてreadします。
read_class_onefile_binmode
read_class_onefileとほぼ同じですが、文字列で読み込む際にバイナリモードで読み込みます。Macでは違いはありませんが、Windowsでは文字列としての処理をするか否かで違いが出ます。
read_class_onefile_windows31j
read_class_onefileとほぼ同じですが、文字列で読み込む際のエンコーディングWindows-31Jを指定します。これはruby1.9のみのテストです。Macでの文字列のエンコーディング処理をWindowsと揃えるために作りました。
file_exist
ファイルが存在するかどうかを調べるメソッド(File.exist?)の速度を調べるためのベンチマークです。100個の別々のファイルに対するFile.exist?を20セット繰り返します。
compile_class_onefile
これはruby1.9のみのテストです。require_class_onefileで使ったのと同じファイルを使用して、RubyVM::InstructionSequence.compileでのコンパイルのみを計測します。これはほぼOSの機能とは関係ないプログラム処理なので、これに違いが出るということは、コンパイラの違い等の理由から「単純にプログラムの実行速度が遅い」ということになります。
file_exist_load_path
$LOAD_PATH(requireする際に検索すべきパスが入ったグローバル変数)からファイルを検索するところまでを100回行うベンチマークです。$LOAD_PATHは環境によって異なりますので、プラットフォーム毎の比較は行えません。参考として行いました。

各プラットフォームでの結果は以下のようになりました。

テスト内容 Win 1.8.7 Mac 1.8.7 Win 1.9.1 Mac 1.9.1
require_empty 0.156250 0.015123 0.234375 0.041115
require_class 0.218750 0.049868 0.406250 0.095429
require_class_onefile 0.062500 0.033033 0.171875 0.054280
read_empty 0.015625 0.002333 0.015625 0.003199
read_class 0.015625 0.002755 0.109375 0.003580
read_class_onefile 0.000000 0.000480 0.093750 0.000375
read_class_onefile_binmode 0.000000 0.000468 0.000000 0.000364
read_class_onefile_windows31j 0.093750 0.001068
file_exist 0.093750 0.012620 0.062500 0.014248
compile_class_onefile 0.062500 0.045660
file_exist_load_path 0.031250 0.005939 0.078125 0.019137

各プラットフォームの違いで目に付くのは、まず1.8.7で比較すると、WindowsMacでは、空のファイル一つをrequireするためにかかる時間に10倍の差があるということです。一方、require_class_onefileのような、ファイルアクセスがほぼ関係しない処理ではそこまでの差はありません。(それでも2倍近く差が付いていますが…。)require_classのように実際のrequireに近い処理では、これらが混ざった結果、WindowsMacの4倍時間がかかるという結果に至っています。これは、上で行った実際のライブラリをrequireしたときの差とほとんど同程度です。

それではなぜ、require_emptyでそこまでの差が出てしまうのでしょうか?read_emptyの結果を見ると、WindowsMacでは差があるのですが、かかる時間が相対的に少ないことがわかります。よって、ファイル1個の読み出しにそれほど時間がかかるわけではありません。file_existやfile_exist_load_pathの結果を見ると、むしろ時間がかかっているのは、requireすべきファイルを探す処理であることがわかります。

さらに1.9.1に目を移すと、不自然な点が見つかります。Windowsの1.8.7では、read_emptyとread_classにほとんど差がないのですが、1.9.1では明らかにread_classが遅いです。さらに、read_class_onefileでも同じくらいの時間がかかってしまいます。つまりWindowsの1.9.1では、ファイルの内容に比例して時間がかかる現象が起こっていることになります。Macではこのような違いはありません。

このような現象が起こるのは一体なぜなのでしょうか?理由は、read_class_onefile_binmodeを見るとすぐに分かります。バイナリモードでは、Windowsの1.8.7と1.9.1は違いがないのです。バイナリモードとテキストモードの違いは、文字列のエンコード関係の処理を行うか否かですので、これにより、Windowsではファイル読み込み時のエンコード処理に時間がかかることがわかります。

なお、この処理コストは、Windowsでファイルを読み込むときに標準で行われる、Windows-31Jへのエンコードが原因ではないかと思い、read_class_onefile_windows31jでMacでファイルをWindows-31Jとして読み込むベンチマークを行ってみたのですが、特に遅くありませんでした。Windowsでは、はやり何か特殊な事情があるようです。

なお、compile_class_onefileを見ると、単なるプログラム処理でもMacの方が1.37倍速いことがわかります。VC6はかなり昔のコンパイラですので、最新のgccと比較するとやはり歩が悪いようです。

まとめ

以上の計測から以下のようなことがわかります。

  • 1.8.7では、Windowsのrequireは、Macと比較して2〜4倍時間がかかる。
  • Windowsが遅い原因は、ファイルが存在するかどうか判定する処理(File.exist?)とファイルを一つ読み混むための処理(おそらくopen,read,closeの一連の処理)に時間がかかることに起因する。
  • さらにWindowsの1.9.1では、ファイルを読み込む際のエンコード処理にも時間がかかる。
  • 1.9でプログラムをVMの命令列へコンパイルする処理のみを比較しても、WindowsMacの1.4倍時間がかかる。これは主にコンパイラの違いに起因すると思われる。

今後のこと

今回わかったことを見ると、「RubyVMの機能を利用してプリコンパイルしたソースをrequireする」という手法はrequireが遅い原因をある程度解消できそうです。ただし、「requireすべきファイルを探すのに時間がかかる」ということを考えると、単にプリコンパイルするだけではなくて、requireしたファイルからさらにrequireしているファイルをまとめて一つのファイルにする処理も行った方が良いように思います。

完成まではまだ遠そうですが、少しずつやっていきたいと思います。