2017-11-08

スレッドの軽量化

Sagittariusはスレッドの作成が重い。理由は至って簡単でスレッド毎にVMを複製するからである。あまり気にするほどスレッドを使っていなかったのだが、最近(というか昨日)Paellaに非同期的なのを入れた際にこれはまずいと思いだした。何がまずいかというと、Paellaに入れた非同期構造は、サーバーからソケットの管理を外した後にスレッドを作ってその中でリクエストを処理するというものだからだ。つまり、スレッドの作成の重さがそのままボトルネックになる。

スレッドの生成で最も重いのはVMスタックを割り当てる部分だと大まかにあたりをつけてはいた。ついでに、VMスタックをCスタック上におければよくね?とも考えてはいた。ずっと思っていただけで実行には移さなかったのだが、ここに来てちと重要になりそうなのでえいや!っと試してみることにした。

スレッド=VMということはスレッドの寿命=VMの寿命でもあるので、開始時にスレッドのスタックからVMスタックを割り付ければ問題ないはず。ということでそんな感じのコードを書いて適当なベンチマークを行ってみた。以下はベンチマークのコード

(import (rnrs) (srfi :1) (srfi :18) (time))

(define data (iota 10000))

(let ((threads
       (time (map (lambda (i)
                    (thread-start! (make-thread (lambda () i)))) data))))
  (assert (equal? data (map thread-join! threads))))
スレッドの生成時間のみを測りたいので、こんなに単純。っで以下が結果(環境 Ubuntu 16.04 64bit Intel® Core™ i7-6820HQ CPU @ 2.70GHz × 8):
元のコード
$ sash thread-bench.scm

;;  (map (lambda (i) (thread-start! (make-thread (lambda () i)))) data)
;;  2.626753 real    3.012000 user    0.256000 sys
改良版
$ ./build/sagittarius -Dbuild thread-bench.scm

;;  (map (lambda (i) (thread-start! (make-thread (lambda () i)))) data)
;;  0.239091 real    0.120000 user    0.224000 sys
ちょっと出来過ぎな感じもするが、効果はありっぽい。まぁ、生成数をひとけた減らすと3倍程度の改善になるので、メモリの圧迫が減っただけとも言える(それが目的なのではあるが)。

2017-10-29

アクター

メッセージパッシングとも言う。

Sagittariusには(util concurrent)があるのだが、今までこのスタイルのライブラリを追加してなかった。理由としては
  • 実装自体はトリビアルであること
  • ライブラリにするにはアイデアの練りが足りないこと
の二つが主であった。のだが、あまりにも似たようなパターンを書くので重い腰を上げることにした。以下のように使う。
(import (rnrs) (util concurrent))

(define actor
  (make-shared-queue-channel-actor
    (lambda (input-receiver output-sender)
      (let loop ()
        (let ((in (input-receiver)))
          ;; do whatever with the input
          (output-sender (result)) ;; if output should be sent
          (loop))))))

(actor-send-message! actor 'message) ;; send a message to the actor
(actor-receive-message! actor)
わざわざチャンネルに使われているキュー名が入っているのは以下の理由から
  • shared-priority-queueバージョンのアクターもある
  • 入力と出力にどんなキューが使われるのか分からないので、make-actorみたいなのにはできない
二つ目の理由からmake-actor手続きは入力と出力を作る手続きを受け取るようになっている。

これをアクターと呼んでいいのかは正直よくわからないが、広義ではアクターモデルだろうということで。

2017-10-27

バグ取り

Sagittariusには(net server)というライブラリがあって、それを使うと割と簡単にノンブロッキングなサーバーが書けたりする。このライブラリは(paella)の基盤になっていたりするので、個人的に書くWebアプリは意識の外側にくらいの位置ではあるが使われていることになる。そんなライブラリ+news-reader.nlで使われているということもあってそれなりに安定していると盲信していたのだが、実はそんなことなかったという話。

ことの発端は今書いているWebアプリがある程度走るとCPUを100%使いだすというもの。これが続くと嫌だなぁと思って、モニター機能をつけてみたところ、どうも挙動が怪しい。スレッドを50個作っているのに動いているように見えるのは一つしかない。スレッドにソケットを割り当てるために持っている優先度付きキューもなぜか要素が一つしかない。

結論を言うと3つのバグが複合的に働いてCPU使用率を100%にしていた。

【バグ1】
優先度付きキューの比較ロジックがおかしかった。スレッドにソケットを割り当てるようなので、スレッドIDとそのスレッドが保持しているソケットの数をペアにして持っていたのだが、その比較がおかしく、ソケットの数が同一であれば同一の要素とみなしてしまっていた。そうすると初期化時にすべての要素が同一とみなされかつ、追加時に同一要素を削除しているので結果的に一つにしかなっていなかった。
修正はこんな感じ:https://bitbucket.org/ktakashi/sagittarius-scheme/commits/c52fab262da092a1ca676c9e067798a4c60f610f?at=default

【バグ2】
優先度付きキューに要素を足すときにサイズの取得がロック外だったため最大値を設定しているにも関わらずキューのサイズが増えていた。これ自体はCPU使用率100%に直接関係はないのだが、モニターを作ったときに予定外の値が渡されるので気付いた。
修正はこんな感じ:https://bitbucket.org/ktakashi/sagittarius-scheme/commits/a96c51bd93076ddb5454060b82cc1ed087c681f1?at=default

【バグ3】
Paellaが特定の場合にソケットを閉じていなかった。データ読み取りすると空データを返すソケットを閉じていなかった。これが無限ループを起こしてCPU使用率100%になっていた。Linux上でのみ起きるので気付きにくかったというのはある(言い訳)
修正はソケットを閉じるだけ。

実用に耐えているからといってバグがないわけではないという話である。しかし、今までスレッド1個しか働いてなかったのか、ソケット自体は複数個扱えてたのが災いして気付かんかった。

2017-09-28

砂場と模型

Mockの訳が模型でもいいかは自信ない(疑似だと味気なかった)。

プログラミングをしていると、テストをどうするかというので困る時がある。例えばあるコンポーネントのテストをしたいが、それはHTTPサーバを必要とする場合とか。実際に必要となるものによっては簡易版を用意してやるとか手はあるが、ユニットテストレベルでそこまでやる気にならないこともある。そもそも、用意した簡易版の何かしらは正しく動くのかとかも気になる。

Sagittariusではいろいろ黒魔術的な何かを使えばライブラリに束縛された値を上書きすることが可能ではある。っが、これをやるとグローバルに変更されるという問題もある。テストの実行単位が常に単一であるとか、単一スレッドでしか走らないとかにしてしまえばこれだけでも問題はないのだが、数が増えるとそれがボトルネックになるのが見えているのでできれば避けたい。

っと、この辺りまでが最近まで悶々としていた問題。もとになったのは、SchemeでもMockitoみたいなのほしいなぁというもの。そこから可搬性とか考えずに、とりあえず束縛だけ一時的に変更できればなんとかできなくね?というところに至る。っで、悶々としている間になんとかなりそうだなぁと思ったので実装してみた。実装の詳細は面白くもないだろうので割愛。(sagittarius sandbox)というライブラリに実装。使い方は以下:
(import (rnrs) (sagittarius sandbox))

(define s "b")
(define (test) (string-ref s 0))
(with-sandbox
  (lambda ()
    (define-in-sandbox '(rnrs) (string-ref s i) #\a)
    (test)))
;; => #\a

;; (test)
(playground ((string-ref '(rnrs) (lambda (s i) #\a)))
  (test))
;; => #\a
コメントアウトされてるtestのコメント外すと予定と違う値が返ってくるが、既知のバグということで。理由としてはサンドボックス内で作られる束縛は指定されたライブラリのみ作用するけど、上記のtestで使われてるstring-ref(rnrs)が依存しているライブラリの一つで定義されてるので、一回サンドボックス外で実行するとGLOCに置き換わって定義されてるライブラリを直で見に行くから。VM内でGLOCに置き換える部分をもう一段ラップして元の識別子を残すようにすれば解決できるんだけど、性能への影響が怖いのとそこまで需要があるかなぁという気持ちが混じりあってとりあえず保留。性能に影響がでないうまい方法を思いついたらやることにする。

サンドボックスができれば次はモックだということでちゃちゃっと作った。以下のように使える。
(import (rnrs) (srfi :1) (sagittarius mock))

(define (test) (make-list 3 #f))
(let ((status (mock-up ((srfi :1)) 
                (test) (mock-status-of 'make-list))))
  (mock-status-arguments-list status))
;; => ((3 #f))

(mock-up ((srfi :1)) 
  (mock-it '(srfi :1) (make-list . args) '(a a))
  (make-list 3 #f))
;; => (a a)
まだAPIが荒いのでもう少し練らないととは思いつつ、テストに使えそうな感じではある。実装に綺麗い目な黒魔術(絶対にドキュメント化されないという意味で)を多用しているのは、まぁご愛敬ということで。

2017-08-21

浮動小数点とC99とSRFI-144と

Sagittarius 0.8.6でSRFI-144をサポートした。このSRFIをポータブル実装を使ってサポートするとパフォーマンス的に得るものが何もないと思いC側でサポートすることあらかじめ決めていた。ネックになるのはこのSRFIが要求しているのがC99の数学関数であることで、Sagittariusでサポートを明記しているVS2010ではC99がサポートされていない。VS2013からはサポートされているので、4年も前の環境だし上げてもいいかなぁとは思ったが、何かの間違いでこのバージョンを使っている人がソースからコンパイルしているという可能性も0ではないかなぁと思い互換レイヤを書くことにした。

互換レイヤ自体はまぁ、普通の苦労で済んだのだが、そこから先が大変だった。Ubuntu 16.04では動くがTravis上のUbuntu 12.04ではテストがこけるとか、VS2013でのCランタイムではテストがこけるとか完全に環境依存の問題に移行したのである。ちなみに嵌ったのは以下:
  • VS2013以降のtgammaはアンダーフローが起きると0.0を返すが、Linux(glibc)では-0.0が返る(たぶんどっちも仕様上はOK)
  • VS2013以降のynは第二引数が0.0だとNaNを返すが、Linux(glibc)では-inf.0が返る(POSIX見ると第二引数が0.0だとpole errorで-HUGE_VALとあるが、C99が同義かはしらない)
  • glibcの特定のバージョン(2.21以下と思われる)ではremquoとlogbが不正な値を返すことがある。(remquoについてはバグレポートを見つけたが、logbは完全に勘。下手するとeglibc固有の問題化もしれない)
  • fl-leastはDBL_TRUE_MIN(C11)が割り当てられるので、どのバージョンのVSがサポートしてるか不明
とにかく環境がないということの方が辛く、おとなしくポータブル実装を使っておけばよかったという気持ちに何度もさせられた。また、参照実装に付属しているテストが要求する誤差が結構厳しく、適当な実装だと誤差が大きすぎるというのも苦労した(Windows)。

苦労の甲斐があるかはわからないが、Sagittarius上で浮動小数点を扱う際はこのSRFIを使うとCと同等の速度が得られることだけは保証されるようになった。っが、個人的に浮動小数点はあまり使わないので、自分では有難みを享受できないという悲しい話もあったりなかったりする。

2017-07-12

オランダでの収入と支出

ITエンジニアと給料では給料だけにしか触れなかったが、この記事によるとアメリカの一部地域(例:シリコンバレー)では生活コストも高いので世帯年収1200万以下は貧民層となるらしい。生活コストも含めた比較となるとオランダくらいしかできないので、ざっくりとオランダでの生活コストを調べてみた。

生活コスト
日用品等のコストは「Cost of Living in Netherlands」のページが詳しい。基本的には食品はそれなりに安く、家賃は高いという感じらしい。
ざっくりとしたコストとしては「What is the average cost of living in The Netherlands」のページに書いてある。以下は適当な日本語訳したリスト
  • 家賃: €800-1000
  • 電気、水道等: €150
  • インターネット(大抵テレビと電話もついてくる):€30−50
  • 健康保険:€270(3x90 18歳以下の子供は無料)
  • その他保険: €30
  • 交通費: €100
  • 食費:€100週 (€400月)
合わせると€1780−2000くらい。これに交際費等が入ってくるが、感覚的に妥当な数字かもう少しかかるかなぁというところ(上記だと最低限くらいしかない感じ)。

収入
2016年における一人あたりの手取りの平均収入は€2158となっている(参考: Average Salary in European Union 2016)。平均世帯収入は単純に2倍すればいいだろうか?

以下は2017年の額面における手取り額(日本語ただしいか?)
月収(額面)月収(手取り)年収額面
2000169525920
3000222838880
4000273651840
5000324464800
6000372677760
7000417090720
80004614103680
90005058116640
100005502129600
参考:Dutch Income Tax Calculator
年収の算出は月収x12.96になっている(注:オランダでは月8%の休暇手当が義務付けられている)。もちろんボーナスのでる会社もあるので、この年収は月収に対しての最低保証額ということになる。

€2000(上記の生活コスト)+€1000(ある程度の交際費と貯金)くらいを文化的な生活とすると、世帯収入的には年€50000くらいあれば「中の中」くらいだろうか?累進課税がキツ目なので、収入が一人だと年€61000くらいないと厳しい感じである。

現在位置ユーロ130円なので、世帯収入650万円(一人なら793万円)で「中の中」くらいの生活ができるとすれば、アメリカの1200万円で「中の下」と比べるとましなのかね?ただ、年収€61000を新卒(エントリーレベル)で出す会社は今のところオランダでは見たことないので(というか、これくらいだと既にシニアレベル)、やはり給与面ではオランダはアメリカに比べると塩っぱい気がする。少なくともIT産業に於いては。

2017-07-03

引っ越し

7年住んだアパートからの引っ越しが完了した。新居の準備がまだ全部終わっていないが(床のフローリングが一部終了してない等)今週中には終わるだろう。7年も住んでいると普段は気にしなくてもこういう時に物であふれていたんだなぁと気付かされた。引越し業者が荷物の運び出しを完了した後で10箱以上自力で運びだしたりしていた(箱詰めが失敗したとも言えなくないが、細々したものというのは往々にして忘れられがちなのだ)。

オランダのアパートは入る時も出るときも割と面倒である。入るときはだいたい床はコンクリートむき出しの状態、壁紙はあったりなかったり。出るときは以下の状態にして出ろと言われる:
  • 床はコンクリートむき出し
  • 壁は白く塗ってあること
  • 天井も白く塗ってあること
  • 壁に開けた穴は埋めること
まぁ、入った時と同じにしろと言われる。ただ、僕の場合は多少特殊で、入った時は「天井が白ければよい」という条件だったんだけど、途中で大家が変わったので「壁も白くしろ」が追加された。なので、受け取った時は青い壁とか、トイレに模様とかあったんだけど、それらを全て白く塗りつぶせという話になった。納得が行かないが、まぁしょうがない。

偶然にも、新しい居住者が入れ替わりで入ることになりかつその人がフローリング、冷蔵庫、洗濯機、オーブン、ベッドフレームを引き取ると言ったのでそれらの運び出し及び廃棄をする必要がなくなったのは幸運だったのだろう。その人にとってもそれらが無料で手に入るのは悪くない条件だと思う。多少汚れてたりしても…(余談だが、フローリングはアパートが広いこともあって総額で2000ユーロ以上かかってたりする。)

新居の鍵の受け渡しから退去までが一週間しかなく、その間に荷出しとかもあったので、非常に疲れる週であった。連日23時くらいまで運び出しと清掃等を行っていたのだが、22時くらいまでは明るいという非常に幸運な時期に引っ越ししたとも言える。これが冬だったら暗闇の中いろいろやることになっていた。

どうでもいいのだが、敷金が帰ってくるまでに2ヶ月かかるってどういうことなんだろう?どう考えても時間かけ過ぎだと思うだが?