読み書きプログラミング

日常のプログラミングで気づいたことを綴っています

Collection Typeを使う重い計算にSwiftを使うにはまだ時期が早い?

アルゴリズムに専念できる「高級」言語でコンピュータ囲碁をやってみたいと思い、Swiftを選択してみました。
原始モンテカルロ碁を実装してみて、結論として、残念ながらSwift 2ではまだこういう計算は大変という結論を得ました。

以下、ボトルネックとなったところ。

// reference : Mr. Yamashita's sample code in lectures in Dentsu University

let dir4 = [1, WIDTH, -1, -WIDTH]

func count_liberty_sub(tz: Int, color: Int, inout liberty: Int, inout stone: Int) {
    ...
    for dir in dir4 {
        ....
        if board[z] == color {
            count_liberty_sub(z, color: color, liberty: &liberty, stone: &stone)
        }
    }
}

このコードが重いのです。dir4はグローバルな定数ですが、関数count_liberty_subで使う時、Swiftは礼儀正しく、swift_bridgeObjectRetainを実行し、その中で_swift_retain_を実行します。
そして関数が戻る時には_swift_release_を実行。
このretainとreleaseがそれぞれ1-2msかかるので、他の処理にほとんど時間がかからなくても重い処理になります。

(試しにdir4に配列リテラルを直接描いてみたら、配列リテラルの生成とリリースが都度発生して悪化しました。)

グローバルな定数配列でARCをしても仕方ないと思うのですが、どういうものなのでしょう?

ちなみに、Optimization LevelはFast, Whole Module Optimizationです。

Swiftベンチマークでいい数字出しているようですが、全体に、ArrayやSetなどCollection Typeを使うとまだスクリプト言語並みに重いという印象です。

cloneメソッド

最近、Swiftを始めました。Swiftはいいとこ取りの言語で、どこかで見た良さげな言語仕様が採用されているので書きやすいです。
でも、インスタンスをコピーするcloneメソッドで苦労しました。

事実上finalのクラスに単にcloneメソッドを実装する分には何の問題もないのですが、スーパークラスになることを前提に、しかもインスタンス変数を増やさないサブクラスでも動作するようなcloneメソッドの定義は大変でした。

以下が苦労した結果の集大成です。

class SuperClass {
    var value: SomeValueType // for simplicity of sample

    init() {
        self.value = SomeValueType()
    }
    
    required init(clone: SuperClass) {
        self.value = clone.value
    }

    func clone() -> Self {
        return self.dynamicType.init(clone: self)
    }
}

インスタンス変数はValue Typeとしました。もしReference Typeを使いたいなら、init(clone:)がReference Typeのcloneを使うように変更する必要があるでしょう。

重要なポイントは以下の2点です。
#コピーの実装部分にinitを使うこと
#コピーコンストラクタにrequiredをつけること
#cloneメソッドの戻り値の型にSelfを指定すること

これで、インスタンス変数を増やさないサブクラスは、何もしなくてもcloneメソッドが使えます。
インスタンス変数を増やした場合には、もちろん、init(clone:)をoverrideしてください。(overrideというキーワードは使わず、requiredでオーバーライドします)

cloneをcomputed propertyにしたかったのですが、Swift 2では型としてSelfを指定できるのは戻り値だけなのでできないようです。

Bootstrapのドロップダウンメニューのhover効果をiOS Safariで改善する

Bootstrap(3.3.5)は様々な場所でCSSのhoverを利用しています。
残念ながらiOS Safariではhoverに該当する状態がなく、ドロップダウンメニューでどれをタッチしたかわかりにくいなどという問題が発生します。

検索すると、hover対応の凝ったJavaScriptコードがたくさん見つかりますが、Bootstrapに限ればそう大層なことをしなくても改善できます。
例えば、Bootstrapのドロップダウンメニューはhoverとfocusに同じ設定がされているため、以下の5行でOK。

$(document).ready(function() {
    $('.dropdown-menu>li>a').on('touchstart', function() {
        $(this).focus();
    });
});

これだけ。touchstartと同時にfocusさせると、hoverと同じ色になってくれるので、どのメニューをタッチしたかがわかるようになります。

TAPi18nのページロード時の振る舞いの改善

Meteor + Iron Routerの国際化にiron-router-i18nとTAPi18nを使っています。
TAPi18nはなぜかデフォルト言語として英語を読み込み表示、その後setLangaugeで言語が設定されるとその言語に切り替えると仕組みになっています。
なので、ネットワーク遅延の目立つ環境では、最初英語版のページが表示され、その後所望の言語に切り替わります。ユーザーの立場から見ると国際化とはわかっても途中でページをすり替えているように気分はあまり良くないですね。

そこで、英語の状態のページの表示を抑制するコードを書きました。
まずbodyをデフォルトで非表示にします。

<body style="visibility: hidden;">
{{> Router}}
</body>

クライアント側の言語設定とbodyを表示にするコードを追加します。

# client

I18NConf.onLanguageChange (oldLang, newLang) ->
    TAPi18n.setLanguage(newLang).done ->
        document.body.style.visibility = 'visible'
        return
    return

やっぱりちらりと英語が見えることがない方が気持ちがいいですね。

ファイルの文字コードの自動判別

AtomシフトJISのファイルを扱うことがあって、自動判別忘れに苦しみました。
ググるとちゃんと解決してくれている人がいました。

ATOM でファイルを開いたら自動文字コード判定を行う

ところが、これだと手動でファイルを開いた時にはOKですが、フォルダ指定してatomを起動した時に、以前に開いていたファイルに関しては自動判別してくれません。
Atom discussionで聞いてみたらあっさり解決。onDidOpenの代わりにobserveTextEditorsを使えばいいとのこと。

fs = require('fs')

atom.workspace.observeTextEditors (editor) ->
  try
    filePath = editor.getPath()
  catch error
    return
  return unless fs.existsSync(filePath)

  jschardet = require 'jschardet'
  iconv = require 'iconv-lite'
  fs.readFile filePath, (error, buffer) =>
    return if error?
    {encoding} = jschardet.detect(buffer) ? {}
    encoding = 'utf8' if encoding is 'ascii' or encoding is 'windows-1252'
    return unless iconv.encodingExists(encoding)

    encoding = encoding.toLowerCase().replace(/[^0-9a-z]|:\d{4}$/g, '')
    editor.setEncoding(encoding)
  return

エンコードの自動判別でwindows-1252にフォールバックする場合もあるので、その場合もUTF-8にするようにしました。
とても快適です。

コードがencoding-selector packageのコード丸写しなのは、encoding-selectorのメソッドが公開されていないからですが、元々encoding-selectorが自動判別機能を持つことが筋が悪い。text-bufferが持つべきなのでissueを上げていますが、果たして取り上げてもらえるかどうか。

https://github.com/atom/text-buffer/issues/86

レスポンシブなページネーション

Bootstrapのページネーションボタン、長すぎると困ります。
で、レスポンシブにしてくれるjQueryプラグインがありました。

http://auxiliary.github.io/rpage/

素晴らしい。
ただページネーションの隣にインライン要素があると動かなかったり、コードが少しあれだったりしたので、CoffeeScriptで書き直してみました。

https://github.com/y-ich/rpage

検討時間的に一番頑張ったのは、ページネーションボタンの折り返しをなくして、widthの計算をボタンひとつひとつの和ではなくてページネーションそのもののwidthとしたところ。CSSの勉強になりました。

でもこれ、テストにある通りの複数のページネーションのresize時とても遅いんですよねぇ。どこかに変なタイマー入っているのかと思うほど遅い。DOMの計算の重さを実感しました。

Iron Routerのフックの覚書

  • Iron Routerには以下の5つのフックが定義できるようになっている。
  • onRun
  • onRerun
  • onBeforeAction
  • onAfterAction
  • onStop

その他に呼び出しのタイミングを考慮する上で、

  • subscriptions
  • waitOn

オプションがある。

ここまでで1つ覚えておくことは、onRun, onStop以外はリアクティブに再計算されるということ。

次に、呼び出される順序は、通常(waitOnで返すハンドルのready()が一旦はfalseを返すような場合)

  1. subscriptions #1
  2. waitOn #1
  3. onRun #1
  4. onAfterAction #1
  5. subscriptions #2
  6. waitOn #2
  7. onRerun #1
  8. onBeforeAction #1
  9. action(というよりpage rendering) #1
  10. onAfterAction #2
  11. onStop #1

これは解説が必要で、

  1. まずsubscriptions/waitOnがコールされる
  2. onRunがコールされる
  3. waitOnの戻り値のready()がfalseなら、onBeforeActionとactionが見送られて、onAfterActionがコールされる。
  4. Iron Router 1.0の仕様ではどうも、waitOnの戻り値のready()がtrueになるとリアクティブな再計算とみなされるようで、subscriptions/waitOnがコールされる(注1)
  5. 再計算とみなされているのでonRerunがコールされる
  6. onBeforeAction, action, onAfterActionの順にコールされる。

(注1) subscribeは重複してコールしても動作上無視するようなので問題なさそうだが、お手製のready()ハンドルを作るとここで色々困る。github レポジトリのイシューに上がっている。

で、各フックの使い方ですが、

  • リアクティブでなくていいものはonRun
  • subscriptions/waitOnに依存しないものはonAfterAction
  • ページレンダリングの前に実行したいものはonBeforeAction
  • ページレンダリングの後に実行したいものはonAfterActionでthis.ready()がtrueの時

onAfterActionの実装が名前のイメージと違ってしまっているおかげでonRerunは使い道がなさそうです。

このタイミング仕様はわかりにくいし使いにくいので近いうちに変わるような気がします。
ただ、Iron Router開発が活発とは言えなくなっていて…
Flow Routerにi18nパッケージができたらみんなそちらに乗り換えてしまうかもしれませんね。

がんばれ、Iron Router!