YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

Goオールスターズでpackage managementについて話してきました

はじめに

こんにちは。Gopherファンクラブ会員番号3番です。去る、10月11日にdots.さん主催の「Goオールスターズ」で登壇してGoでのpackage managementについて話してきました。

ツイートのまとめや他の登壇者の方の資料はこちらです。

資料

資料はこちらです。

大体の流れはこんな感じです。

  • 当面はGo本体では当面は「ソースコードの明示性」「下位互換性」を保つためにgoツールでパッケージのバージョン管理をすることはしない
  • 代わりにGo1.5ではvendoringを「実験的に」サポート
  • すでにGo 1.4でもgb, godepなどvendoringをサポートする3rd partyツールがあるが、ディレクトリの切り方とか見るとgbが筋が良さそう

真のオールスターズ、あるいは #mattncon について

今回は「Goオールスターズ」というタイトルのイベントで登壇したわけですが、大変こっ恥ずかしい思いで登壇したわけです。というか、 @moriyoshit が一般参加で聞いている場で発表するというのは大変つらい。

で、ビアバッシュのあとで登壇者+αで、都内のエンジニアが大変お世話になっている渋谷の北海道で2次会をしたわですが、mattnさんが来てない時点で永遠に「オールスターズ」は開催されないという結論にいたり、 #mattncon 開催の機運が高まりました。

自分は途中で2次会を抜けてしまいましたが、その後もTwitter上では #mattncon について熱いツイートがされていたので、皆のテンションは高いままだったのでしょう。実現のために個々人に何ができるか考えていくことが大事だなと思いました。

イタリア旅行記

はじめに

こんにちは。気がつけばまたブログの更新が滞っていました。7月最終週〜8月第一週にかけてイタリアに旅行に行ってきたので、旅行記を残しておこうと思います。

旅程

今回の旅行ではイタリアの中央の3都市(フィレンツェベネチア、ローマ)を巡る旅行をしてきました。日程は7/28-8/5の7泊9日。事前情報でフィレンツェベネチアは街が小さいと聞いていたので、この2都市では2泊、ローマでは3泊の予定としました。各都市の移動は高速鉄道でした。

都市 日程
フィレンツェ 7/28-7/30
ベネチア 7/30-8/1
ローマ 8/1-8/4

またガイドブックは次のものを購入した。どれでも良かったけれど、立ち寄る3都市の情報が程よく均等に載っていたので。現地でもレストラン選びなどの時に役に立った。

イタリア (ララチッタ)

イタリア (ララチッタ)

気付き

色々と細かく書けると思うのですが、まずは旅程全体を通してと各都市での気付きを箇条書きで列挙。写真を見てからだとよりわかってもらえるかもしれないので置いときます。

全体として

  • イタリアではクレジットカード決済ではPIN番号を聞かれ、サインでの決済はほぼできない。おそらく偽装カード対策か。また現金払いのみのところも多い。
  • 英語は案外通じるが、イタリア語を幾つか覚えていったほうが当然楽しい。
    • 看板や印刷物などに対してはGoogle翻訳がものすごく便利。(後述)
  • 今回の日程(夏期)では日没は20:30前後で、街は日没に合わせて夜が始まる感じ。飲食店も日付が変わるまで開いている所も多い。
  • どの展示施設も事前にインターネット予約しておくべき。(後述)
    • 特にバチカン博物館は当日2時間近く炎天下で待つ人もいた。
  • 寺院などを巡る際は服装や荷物の大きさに注意。
    • 肌の露出があると入場拒否される可能性があるので、男性でも大きめのストールなどを持って行くと便利。(腰にまいて足全体を隠せる)
    • かばんもレンズ一本が入るカメラバック程度の大きさにおさえておかないと、クロークに預けなければならなかったり、下手をするとクロークすら無いので注意。
  • とにかく日差しが強く直射日光を浴びていると非常に暑い。一旦日陰などに入ると涼しい。帽子は持っていった方がいい。(自分は持って行かなかった)
  • 上記の状況なので水をとにかく飲んでいた。観光地の露店などでは500mlで€1とか取られるけど、スーパーなどにいけば1.5Lで€0.5とかで買える。
    • 何も言わないと炭酸入りが出てくる可能性もあるので、炭酸水(伊: frizzante)か自然水(伊: naturale)か表記を確認する。
    • 飲食店でも水は有料。1-1.5Lで€1-2くらい。その時も "still water" など水の種類を言うこと。
    • 街中に泉があり、飲用可のところが多数。水質は硬い。(後述)
  • 街中にトイレが少ない。
    • 食事のときや施設に行ったときトイレを都度借りたほうが良い。
    • 夏は特に水を四六時中飲んでいるのでトイレ探しは結構大事な要素。
    • 公衆トイレもあることもあるがたいてい有料。(ベネチアでは1回€1.5だった)
  • 公共WIfiはそんなにない。出来ることならSIMカードを買おう。(後述)
    • 飲食店でも「Free Wifi」の看板を掲げて入るものの、店員にいちいち聞くのは面倒。
    • 訪れた3都市では4Gが入りました。
  • 飲食店に飛び込みで入らないように注意。ボッタクリに合わないための対応。
    • 店に入る前にTripAdvisorやGoogle Placesで評価を確認。星の数が高くても「5つ星かつコメントなし」の評価ばかりのところは怪しい。日本語のレビューで高評価のところは結構信頼できる。
    • 疲れきっていたときにこれをしないで飛び込んだ店で見事にぼられた。(後述)
  • Google Mapsは最高に役に立った。
    • 周辺情報(レストラン、スーパー、薬局、観光スポット等々)
    • ルート検索での公共交通機関情報(バスのルート番号、水上バスの番号等々)
  • チップは本当にサービスに満足した時に支払う、という文化を感じた。
    • 向こうが欲しいと思っている場合はサービス料を予め上乗せしていたり、席料(コペルト)を取っていることも多い。

フィレンツェ

  • ルネサンス文化が色濃く残る街。街もルネサンス時代から残る建物が多く、景観の保存にも力を入れている印象。
  • 完全な観光都市というわけではなく、地元の人は観光以外の産業でも生活している印象。
    • 街も観光のみではなく、生活に必要な施設(スーパー、百貨店等々)も数多く見られる。
    • 飲食店・商品の価格も観光価格という印象はそこまで受けなかった。
  • 食事はトスカーナ料理なので肉が多い。
  • 主要箇所は全部徒歩で移動可能。むしろタクシーを捕まえるほうが難しい。
  • ウフィッツィ美術館をしっかり見たければ3時間以上は確保しておきたい。

ベネチア

  • 街並みは綺麗だが、水路は結構汚い。ただ臭くはなかった。路地は迷路のようで非常に楽しい。フィレンツェよりは広いものの、頑張れば全部徒歩で回れる広さ。
  • ご飯は他の都市に比べて2割ほど値段が高い。
    • ぼったくりも多い。旅行中唯一ぼったくられたのがベネチア
  • バーカロという立ち飲み居酒屋が最高に良い。ベネチアに行ったら食事の時間は気にせず、小腹が空いたらバーカロで軽く飲んで移動、というのが良いかもしれない。
  • 水上バスは時間帯によっては非常に混む。
  • ゴンドラは値段交渉をしようと思ったが軒並み公定料金で自分がまわったところでは交渉ができるところがなかった。
    • 値段は30分で€80と割高。二度と来ることもないと思ったので乗った。
    • ゴンドラに乗らなくても十分ベネチア観光は楽しめるので、乗らないという選択肢もあり。
  • サンマルコ広場は夜に行くと人も落ち着いていて綺麗。
    • サンマルコ寺院は荷物を預けないと入れない場合がある。
    • 満潮時に広場が浸水することがある。今回はたまたま少しだけ浸水した様子を見ることができた。
  • 完全な観光都市なので客引きなどは多い。

ローマ

  • 泉が街の至る所にある。
  • タクシーは色が白いものは安全。
  • カラカラ浴場跡での野外オペラの帰りはタクシーが必須。カーテンコール前に席を立てばすぐにタクシーに乗れる。
    • タクシー会社がタクシー待ちの列を整理していて、ボッタクリの心配もない。ホテルの人が「スペイン広場まで€13以上請求してくるようならすぐにぼったくりだと訴えろ」と事前に忠告してくれたが、見事€13で到着した。
  • スリが結構いる。細心の注意を。財布をポケットに入れておくなんて「スってくれ」と言っているようなもの。
  • 結構スーパーが多い。
  • 当たり前のように古代ローマ時代の遺跡が街中にある。とにかくスケールが大きく圧倒される。
  • バス網が発達していて非常に便利。地下鉄もそこそこ便利。
    • バス、地下鉄が時間を守ることはまずない。時間前に出発するとか当たり前。
    • バスではチケットを確認していない。適当。
  • ローマ市内から空港まではタクシーでのボッタクリが横行したため、最近は固定料金を設定することが多いとのこと。

細かなTipsなど

箇条書きで(後述)としたところに関して追記していきます。

インターネット予約

とにかく並びまくるので、出来る限りインターネットでチケットを予約していったほうが良い。またもし突発的にどこかの施設に行きたくなったとしても、携帯からインターネット予約でチケットが買えるならそのほうがよいこともある。とにかく炎天下長時間並ぶのは本当に疲れる。自分が予約していった施設は次。

Google翻訳

Word Lensを統合してから海外旅行に欠かせないアプリケーションになりました。人だったら英語でコミュニケーションをすることも可能ですが、掲示物・印刷物はGoogle翻訳がないと内容の確認が不可能です。試しにベネチア水上バスのチケットで見てみましょう。こちらがオリジナル。イタリア語だけでまったくわかりません。

これをGoogle翻訳にかけるとこうなります。

f:id:ymotongpoo:20150809210318p:plain

なんとなく分かる!

他にも店舗の営業時間やお店のメニューの解読にも役に立ちます。(後述)

プリペイドSIMカード

この時代なので旅行中に現地で情報を集めることはあたりまえだと思う。海外用のWifiルーターを借りてもいいんだろうけど、割高だし余計な荷物が増えるのは面倒。イタリアではVodafone、TIM、3IT、Windなどいくつかのキャリアがありますが、Vodafoneの情報が多かったのと、最初に泊まる都市のフィレンツェVodafoneのショップがあったので、そこで購入しました。

購入したのは「Vodafone Holiday」というプランのプリペイドSIM。300分の通話、SMS300通、4G回線の接続で2GBまでのデータ通信がついて€30という良心的な価格設定。

購入は簡単で、Vodafoneの店舗へ行き、「プリペイドSIMをくれ」と言ってパスポートを提示しただけです。するとユニバーサルSIMをくれるので裏面のスクラッチを削ってPIN番号を取得。

自分が使っている携帯はSIMフリー端末なので、挿して再起動したらPIN番号を聞かれるので入力したら使えました。注意事項として、SIMを挿してからアクティベーションまでに2時間は携帯の電源をオンにしてはいけないとのこと。その間に電源をオンにすると、SIMのサーバ側での設定がおかしくなることがあるそうです。というか自分がそうなりました。

ぼったくりについて

ぼったくりには遭わないように気をつけていたのですが、ベネチアで歩きまわりすぎて疲れてたので、レビューを調べもせずに適当に入ったトラットリアでふっかけられて口論になりました。値下げはしてもらったものの予算より1500円ほど多く取られました。こちらのサイト にあるところの重量制というやつです。「おすすめですよ」と言われたので「お腹は空いていないから、1人分を2人で分けるけどいいかな」と聞いたら「もちろんOK」と言われたのでそのまま注文。(その時は重量制であることを知らず、ただ具材についての説明を受けただけ。)

食べ終わって会計が出てきたら表示の価格€9に対しての4倍の€36という値段。これはおかしいとメインのウェイターを呼ぶと

  • ウェイター「これは重量制なのは当たり前だ」
  • 私「中身について聞いた時に何も言わなかっただろ」
  • ウェイター「それは中身についてしか聞かれてないからだ」
  • 私「特筆すべきことは無いかと聞いただろ」
  • ウェイター「俺の仕事は中身を説明することだけだ」
  • 私「中身の説明にグラム数が入ってないだろ!」
  • ウェイター「グラム数を説明する義務はない」
  • 私「言い値じゃないか。大体お腹いっぱいだから1人前の量だと言ったのに、他のパスタも400gもだすのか。」
  • ウェイター「それは違う。これだけ特別」
  • 私「あきらかに値段ふっかけてるじゃないか」

と押し問答の末、€20になりました。とはいえ、まずくはなかったものの、そこまで美味しいかったわけでもなく...完全なぼったくりではなかったものの、良い勉強になりました。

そもそもこれはメニューをもらった時点でGoogle翻訳にかけておけばよかったのですが、他のページのメニューはすべて英語が表記してあったので油断してました。こういうこともあって、それ以降は特に慎重にお店に入る前にかならずTripAdvisorやGoogle Placesでレビューをきちんと確認するようになりました。

泉について

まさしく回復の泉の街のあちこちにある。24時間出続けていて冷たい。タオルを濡らして涼んだりすることもできる。硬水だけど、そのまま飲んだりすることも可能。

参考

旅行の際に参考にしたサイトや書籍など。

「逆引きGolang」で気になったところ

はじめに

こんにちは、タゾチャイティーラテです。最近急に蒸し暑くなったり、寒かったり中途半端な天気が多いですね。逆引きGolang というサイトが公開されてて面白いなあと思って見てたんですが、僕だったらこう書くなというものがいくつかあったので覚書きです。

文字列

部分文字列を置き換える

これは無理に strings 使わないほうが楽なんじゃないかと思いますがどうでしょう。自分で関数定義するのは面倒かもしれませんが。

package main

import "fmt"

func main() {
    s := "Apple Banana Orange"
    r1 := replace(0, 5, []rune(s), []rune("Vine"))
    fmt.Println(string(r1))
    r2 := replace(5, 6, r1, []rune("Lemon"))
    fmt.Println(string(r2))
}

func replace(start, length int, orig, target []rune) []rune {
    ret := make([]rune, len(orig)+len(target)-length)
    copy(ret, orig[0:start])
    copy(ret[start:], target)
    copy(ret[start+len(target):], orig[start+length:])
    return ret
}

16進文字列を整数に変換する

コメントに "0xを含んではいけない" とありますが、含んでも大丈夫です。その場合はbaseを0にします。baseを0にした場合は文字列の見て0が先頭にあれば8進数、0xであれば16進数と判断します。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "0xff"
    sh, _ := strconv.ParseInt(s, 0, 64)
    fmt.Println(sh)
}

漢字コードを変換する

たぶん書きかけなんだろうけど、直接 EUCJP は呼べないので japanese.EUCJP.NewEncoder() としないと動かない。

日付と時刻

これは別に本文と関係ないけど、 time パッケージにはRFCとかで定義されてるフォーマットは定数で事前定義されてるっていうのをFYI。

const (
        ANSIC       = "Mon Jan _2 15:04:05 2006"
        UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
        RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
        RFC822      = "02 Jan 06 15:04 MST"
        RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
        RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
        RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
        RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
        RFC3339     = "2006-01-02T15:04:05Z07:00"
        RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
        Kitchen     = "3:04PM"
        // Handy time stamps.
        Stamp      = "Jan _2 15:04:05"
        StampMilli = "Jan _2 15:04:05.000"
        StampMicro = "Jan _2 15:04:05.000000"
        StampNano  = "Jan _2 15:04:05.000000000"
)

配列

配列に要素を追加する

append 使えばいいんですが、あんまり回数が多いと効率が悪いので、長さがわかってるならばバッファを確保したほうがよいと思う。

一致する要素を全て取り除く

まず全然関係ないけど、全体的にアンダースコア区切りの関数名がよく見られるけど、Goの慣習的には変数名も関数名もキャメルケースです。詳しくは ここ 参照。あと複数パッケージimportするときは括弧で囲ったほうが読みやすいかなあ。

deleteStrings()はまっすぐ書いたほうがスッキリ書けそう。

package main

import "fmt"

func main() {
    a := []string{"apple", "orange", "lemon", "apple", "vine"}

    str, a, err := deleteStrings(a, "apple")
    fmt.Println(str) // => "apple"
    fmt.Println(a)   // => "[orange lemon vine]"
    fmt.Println(err) // => "<nil>"

    str, a, err = deleteStrings(a, "apple")
    fmt.Println(str) // => ""
    fmt.Println(a)   // => "[orange lemon vine]"
    fmt.Println(err) // => "Couldn't find"
}

func deleteStrings(slice []string, s string) (string, []string, error) {
    ret := make([]string, len(slice))
    i := 0
    for _, x := range slice {
        if s != x {
            ret[i] = x
            i++
        }
    }
    if len(ret[:i]) == len(slice) {
        return "", slice, fmt.Errorf("Couldn't find")
    }
    return s, ret[:i], nil
}

おわりに

公式ドキュメントでは文法などはわかりやすく書いてあって、あのサイトだけひと通り読んでれば普通に書けるようになるのですが、やはり逆引き的にスニペットがあると便利ですね。これからも更新頑張ってください。

Ubuntu 15.04のサーバをWake on LANできるようにした

はじめに

最近、とある用途のために眠ってたマシンを引っ張り出してきた。こいつがまたいい働きをしてるんだけど、いくらファンが静か目とはいえうるさい。ということでWoLの設定をして、いい感じに必要なときだけ起動できる下地を作っておこうと思った。

BIOSの設定

まずBIOSの設定だけど当然使ってるマザーボードによって設定は違う。俺のおんぼろマザボ785GM-P45) の設定だと Power Management Setup → Wake Up Event Setup → Resume By PCI-E Device で Enable の設定をするだけ。

OSの設定

Ubuntu 15.04の方での設定は ethtool で実施。apt-getしようと思ったらすでに入ってた。

% sudo ethtool -s p5p1 wol g
% sudo ethtool p5p1
Settings for p5p1:
    Supported ports: [ TP ]
    Supported link modes:   10baseT/Half 10baseT/Full
                            100baseT/Half 100baseT/Full
                            1000baseT/Full
    Supported pause frame use: No
    Supports auto-negotiation: Yes
    Advertised link modes:  Not reported
    Advertised pause frame use: No
    Advertised auto-negotiation: Yes
    Speed: 1000Mb/s
    Duplex: Full
    Port: Twisted Pair
    PHYAD: 0
    Transceiver: internal
    Auto-negotiation: on
    MDI-X: Unknown
    Supports Wake-on: pg
    Wake-on: g
    Current message level: 0x0000003f (63)
                   drv probe link timer ifdown ifup

Wake-onが g になってるのを確認。このままだと再起動したら設定消えちゃうので、initスクリプトを新規作成。

% sudo vim /etc/init.d/wakeonlanconfig
#!/bin/bash
### BEGIN INIT INFO
# Provides:          wakeonlanconfig
# Required-Start:    $local_fs
# Required-Stop:     $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: wakeonlanconfig
### END INIT INFO
ethtool -s p5p1 wol g
exit
% sudo chmod +x /etc/init.d/wakeonlanconfig

これで再起動して起動後にちゃんとWake on LANが有効になってるか確認。

% sudo reboot
% sudo ethtool p5p1
Settings for p5p1:
...
    Wake-on: g
...

テストのために終了する。

% sudo shutdown -h now

MacWake on LANのクライアントの設定

MacPortsを使ってるんで探してみると便利なパッケージがあった。

% port info wol
wol @0.7.1 (net)
Variants:             universal

Description:          wol can send a Wake-On-Lan magic packet to a target ethernet address
Homepage:             http://wake-on-lan.sourceforge.net

Build Dependencies:   autoconf, automake, libtool
Platforms:            darwin, freebsd
License:              GPL-2+
Maintainers:          jeremyhu@macports.org, openmaintainer@macports.org

これをインストールして、試してみる。

% sudo port install wol
% wol <UbuntuマシンのMACアドレス>
Waking up XX:XX:XX:XX:XX:XX....

Ubuntuのマシンが起動した。便利。

Raspberry PiのWake on LANのクライアントの設定

Raspbianを使ってるんでaptで探してみると発見。

$ apt-cache show wakeonlan
Package: wakeonlan
Version: 0.41-11
Installed-Size: 56
Maintainer: Thijs Kinkhorst <thijs@debian.org>
Architecture: all
Depends: perl, perl-modules
Size: 11510
SHA256: d93c21fe7023fd98ec9461047a67f1099addae6a46278e2fb0aea97d55d73f60
SHA1: 988ce3c2f1c6d04f715da153b8a7bfc74dac895a
MD5sum: 6d4c609468083aeedc329a2df9b77079
Description: Sends 'magic packets' to wake-on-LAN enabled ethernet adapters
Homepage: http://gsd.di.uminho.pt/jpo/software/wakeonlan/
Description-md5: 1f4cb6ce85d821307a46719513c54d04
Tag: admin::boot, implemented-in::perl, interface::commandline,
 network::configuration, protocol::ethernet, protocol::udp,
 role::program, scope::utility, use::transmission
Section: net
Priority: optional
Filename: pool/main/w/wakeonlan/wakeonlan_0.41-11_all.deb

これをインストールして試してみる。

% sudo apt-get install wakeonlan
% wakeonlan <UbuntuマシンのMACアドレス>
Sending magic packet to 255.255.255.255:9 with XX:XX:XX:XX:XX:XX

起動した。

結論

便利なのでWoLをもっと気軽に使えるシステムを作りたい。

Goで再帰使うと遅くなりますがそれが何だ

はじめに

こんにちは、Go界のうまい棒です。昼間にTwitter眺めてたら次のような記事を見かけました。

結果はあくまでフィボナッチ数列をナイーブに実装した場合なんで、まあ明らかに遅くなるよなあと予想通りの実行結果でした。

件のプログラム

ナイーブにフィボナッチ数列を実装してますね。

package main

import "fmt"

func fib(n int) int {
    if n < 2 {
        return n
    }
    return fib(n-2) + fib(n-1)
}

func main() {
    fmt.Println(fib(42))
}

これを実際にビルドして実行するとどれくらいかかるかというと、だいたい手元で2.5秒以上かかってますね。

% time go run fib1.go
267914296
go run fib1.go  2.55s user 0.06s system 94% cpu 2.763 total

なんで遅いのか

先のコードは最後が末尾再帰でない再帰呼び出しなので、コールスタックがめっちゃ積まれそうです。Goが再帰呼び出しが得意でないのは、Goランタイムのスタックサイズがデフォルトが小さく、かつスタックサイズの大きさを最小にするような最適化を行わないからです。さらにいうと、Goでは末尾再帰ですら最適化されません。

困ったらgo toolを使ってアセンブリを見てみると良いです。

$ go tool 6g -S fib.go > fib.asm

再帰の場合、途中のいろいろは抜かして関係しそうなところだけ抜粋。

"".fib t=1 size=128 value=0 args=0x10 locals=0x18
        0x0000 00000 (fib1.go:5)        TEXT    "".fib+0(SB),$24-16
        ...
        0x000f 00015 (fib1.go:5)        CALL    ,runtime.morestack_noctxt(SB)
        ...
        0x001f 00031 (fib1.go:6)        CMPQ    AX,$2
        ...
        0x002e 00046 (fib1.go:7)        RET     ,
        ...
        0x003a 00058 (fib1.go:9)        CALL    ,"".fib(SB)
        ...
        0x0055 00085 (fib1.go:9)        CALL    ,"".fib(SB)
        ...
        0x0070 00112 (fib1.go:9)        RET     ,

今回のコードは普通の再帰ですが、見ての通りfibがどんどん呼ばれてコールスタックが深くなっていきます。これは末尾再帰のコード書いても同様のアセンブリになります。階乗の計算をするコードを書いてみます。

package main

import "fmt"

func fact(n, p int) int {
        p = p * n
        if n == 1 {
                return p
        }
        return fact(n-1, p)
}

func main() {
        fmt.Println(fact(10, 1))
}

このアセンブリを見てみます。

"".fact t=1 size=96 value=0 args=0x18 locals=0x18
        0x0000 00000 (tailrec.go:5)     TEXT    "".fact+0(SB),$24-24
        ...
        0x000f 00015 (tailrec.go:5)     CALL    ,runtime.morestack_noctxt(SB)
        ...
        0x0024 00036 (tailrec.go:6)     IMULQ   AX,CX
        0x0028 00040 (tailrec.go:7)     CMPQ    AX,$1
        ...
        0x0037 00055 (tailrec.go:8)     RET     ,
        ...
        0x004c 00076 (tailrec.go:10)    CALL    ,"".fact(SB)
        ... 
        0x005f 00095 (tailrec.go:10)    RET     ,

fibと同様のアセンブリコードになってますね。

Goという言語

@mayahjp さんとも話してましたが、そもそも「Goが速い」というのが間違いで、たとえば「Goはビルドは遅くない」「普通にやってもまあ遅くないしLLよりは速く書けがち」「並行プログラミングが楽にできる」というところがいいのであって、システムプログラミングなどには向いてますが、数値計算などには向いてませんし、そういったものをするために開発された言語ではありません。(ただ先のエントリのベンチマークを見ても最適化を掛けないCよりは実行速度速くなってますよ。)

今回のフィボナッチ数列でのベンチマークはやってる処理自体はごくごく小さな演算で、パフォーマンスに寄与するのは

  1. 関数呼び出し自体のパフォーマンス
  2. コールスタックの最適化

です。そういった意味でGoは

  1. 関数呼び出し自体はgoroutineの管理などもあり、そもそもがgoroutineを大量に呼べるようにスタックサイズがデフォルトで小さく設定されていて、かつ自動で管理するようにランタイムで調整が入る。
  2. Goではスタックトレースで表示されることが大事なので、敢えて最適化していない*1 (2015.02.23 訂正。鵜飼さんご指摘ありがとうございます。)

という状況なので仕方ないかなと思います。

アルゴリズムでの最適化

もし今回のプログラムをアルゴリズムで最適化するのであればメモ化をするのが手っ取り早いでしょう。O(n)になります。

package main

import "fmt"

var mem = make(map[int]int, 100)

func fib(n int) int {
        if n < 2 {
                return n
        }
        if m, ok := mem[n]; ok {
                return m
        }
        m := fib(n-2) + fib(n-1)
        mem[n] = m
        return m
}

func main() {
        fmt.Println(fib(42))
}

これを実行すると0.12秒になりました。

% time go run fib4.go
267914296
go run fib4.go  0.12s user 0.04s system 89% cpu 0.178 total

ちなみにPythonで適当に書いても速いです。

def fib(n, mem):
    if n < 2:
        return n
    if n in mem:
        return mem[n]
    m = fib(n-2, mem) + fib(n-1, mem)
    mem[n] = m
    return m

if __name__ == "__main__":
    print(fib(42,{}))

実行すると0.01秒ですよ。十分速い。

% time python fib.py
267914296
python fib.py  0.01s user 0.01s system 92% cpu 0.024 total

おわりに

先のエントリの最後にもちょろっと書いてますが、マイクロベンチマークはそのベンチマークに特化した結果しか出ないので、あくまでマイクロベンチマークはマイクロベンチマークです。何が言いたいかというと、Nim使いたくなるようにその言語が得意とする領域のもっと良いサンプルで比較してほしいな、ということでした。おわり。

参考