よく行うのでメモ。

マスタとスレーブのホスト名を以下と仮定する。

マスタ: masterdb スレーブ: slavedb

また、スレーブ対象のDBは以下とする。

some_db_production

マスタ上のMySQLでの作業

レプリケーション用ユーザーの作成

まだ無ければ作っておく。この例だとどのホストからでも接続できるので、 必要に応じてIPアドレスでの制限をかけること。

1
2
mysql> GRANT REPLICATION SLAVE ON *.* TO repl_user@"%" IDENTIFIED BY 'hogehoge';
mysql> FLUSH PRIVILEGES;

スナップショットの作成(取得しつつスレーブへ送る)

ダンプを取りながら圧縮しスレーブDBに送り込む。データが多いと数時間かかる。

ここでは転送速度を優先させるため、暗号化方式を軽量のものしている。

大容量ファイルのSCP転送を高速にする方法 – 元RX-7乗りの適当な日々

1
mysqldump some_db_production --master-data --single-transaction -uroot -phogehoge | gzip | ssh -c arcfour128 hoge@slavedb 'cat > /home/hoge/db_snapshot.gz'

スレーブでの作業

スナップショットのインポート

解凍しつつインポートする。スナップショットの作成よりもさらに時間がかかる。

1
gunzip < /home/hoge/db_snapshot.gz | mysql -u root -p some_db_production

スレーブ上のMySQLでの作業(インポート後)

マスタ情報の確認

後ほど、slaveに設定する情報。lessでgzの中を直接確認できる。

1
less /home/hoge/db_snapshot.gz

こんな感じで出力されているはず。

1
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.0123', MASTER_LOG_POS=9876;

master情報書換

スレーブDBのmysqlにログインし、以下のコマンドを実行。 MASTER_LOG_FILEとMASTER_LOG_POSは先に調べた情報に書き換える。

1
2
3
4
5
6
7
mysql> RESET SLAVE;
mysql> CHANGE MASTER TO
mysql> MASTER_HOST = 'masterdb',
mysql> MASTER_USER = 'repl_user',
mysql> MASTER_PASSWORD = 'hogehoge',
mysql> MASTER_LOG_FILE = 'mysql-bin.0123',
mysql> MASTER_LOG_POS = 9876;

スレーブ再開

同じくスレーブのmysqlでコマンド実行

1
mysql> start slave;

時間のかかる処理を複数実行して、最後に各処理の結果をがっちゃんこしたい。 そのまま行うと各処理の実行時間の合計だけかかってしまうので、 golangで並列で実行する場合のサンプルを書いてみた。

goroutineについてはこちらを参考にさせていただいた。

Go の並行処理 – Block Rockin’ Codes

コード

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
        "fmt"
        "time"
)

func wait(waitSec time.Duration) <-chan float64 {
        ch := make(chan float64)
        go func() {
                start := time.Now()
                time.Sleep(waitSec * time.Second)
                end := time.Now()
                ch <- end.Sub(start).Seconds()
        }()
        return ch
}

func main() {

        cpus := runtime.NumCPU()
        runtime.GOMAXPROCS(cpus)

        start := time.Now()

        ch1 := wait(5)
        ch2 := wait(10)
        ch3 := wait(3)

        ret1 := <-ch1
        ret2 := <-ch2
        ret3 := <-ch3

        fmt.Printf("各処理時間合計 %f sec\n", ret1+ret2+ret3)
        end := time.Now()

        fmt.Printf("実時間 %f sec \n", end.Sub(start).Seconds())
}

channelからの結果取り出しでブロックされてるけど、重い処理自体はブロックされずに 別goroutineで実行されているので、今回の目的ならこれで問題ないはず。

実行結果

1
2
3
$ go run main.go
各処理時間合計 18.002096 sec
実時間 10.000841 sec

指定秒数だけ待つ関数を呼んでいる。それぞれ5秒、10秒、3秒なので、 普通に実行すれば全体で18秒かかるけど、実際には10秒で終わっている。 今回はSleepしてるだけだからあまり関係ないけど、実際に重い計算をさせる場合は、 マルチコア環境なら並行に処理が行われて、CPUリソースを活かせる。 こんなにお手軽に並列処理ができて、goroutineは面白い。

最近、色々API化したいものがあって、何で書けばいいかなぁと考えている。 とにかく速く、複数のDBから並列でデータをもってきて集約して返す、ようなのを作りたい。 そんな事考えているとき、こんなブログをみつけた。

Go as an alternative to Node.js for Very Fast Servers | Safari Flow Blog

なかなか興味深いので、GoとNode.jsの他にも、気になっていたFinagleとかも入れて、 自分も同じようにベンチマークを取ってみた。


disclaimer(いいわけ)

ベンチマークのとり方ははそんなに厳密にとっているわけでありません。カジュアルな感じで。 サーバもクライアント(Apache Bench)も同一マシンで実行しちゃってます。 もっとこうすべきとかあれば教えてください。 あと、計測結果は長くなりすぎたので、最後に置いています。


測定について

実行方法

元ブログと同じように、httpで1MBのコンテンツを返すだけのAPIを作り、 Apache Benchで測定する。測定結果は5回連続実行後の最後のものを使用。 メモリの使用量を見るため、procfsで 最大物理メモリ使用量も見る。

マシンスペック

CPU  Core i7-2600
メモリ  8GB
OS  Ubuntu 13.10 64bit

バージョン等

言語・FW名 バージョン
Go  go1.2 linux/amd64
Node.js  v0.10.26
Scala  2.10.3
Finagle  6.12.1
Spray  1.1.0

登場選手たち

Go

最近噂のGoogle生まれのあいつ。C++の代替を狙っていたのに、 なぜかPythonistaやRubyistからばかり注目されてるとか。 小さな言語仕様、お手軽な並列処理などが特徴。

※テストコードは元ブログのコードをそのまま流用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "net/http"

func main() {
  bytes := make([]byte, 1024*1024)
  for i := 0; i < len(bytes); i++ {
      bytes[i] = 100
  }

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      w.Write(bytes)
  })
  http.ListenAndServe(":8000", nil)
}

Go/Martini

気になっていたGo用軽量WEBフレームワーク。ついでに試してみた。 Sinatra風味。なんせ公式サイトがかっこいい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
  "github.com/codegangsta/martini"
  "net/http"
)

func main() {
  bytes := make([]byte, 1024*1024)
  for i := 0; i < len(bytes); i++ {
      bytes[i] = 100
  }

  m := martini.Classic()
  m.Get("/", func() []byte {
      return bytes
  })
  //m.Run() これで起動するとポートは3000
  http.ListenAndServe(":8000", m) //ポートを指定したい場合はこっち
}

Node.js

ブラウザ界の住人だったはずのあいつが、サーバサイドにもやってきた。 自分はJavaScriptは苦手だしあまり好きじゃないと思っていたけれど、少し触ってみたらなかなか楽しい。 ブラウザ側でJavaScriptを極めた方々からすれば、 サーバサイドまで全て同一言語で完結できるのはまさに夢のようだろうなぁと思う。

※テストコードは元ブログのコードをそのまま流用。

1
2
3
4
5
6
7
8
9
10
http = require('http')
Buffer = require('buffer').Buffer;
n = 1024*1024;
b = new Buffer(n);
for (var i = 0; i < n; i++) b[i] = 100;

http.createServer(function (req, res) {
  res.writeHead(200);
  res.end(b);
}).listen(8000);

Scala/Finagle

FinagleはTwitterのバックエンドで使われてるすごいやつらしい。

Finagle: A Protocol-Agnostic RPC System | Twitter Blogs

The Twitter stack – Braindump

あのサイトのトラフィックをさばいてるんだからさぞかし凄いんだろう。 ZooKeeprと連携したり、各種Metricsを取れるようになってたり、ただものではない感がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package jp.sudix.finagle.benchmark

import com.twitter.finagle.Service
import com.twitter.finagle.http.Http
import com.twitter.util.Future
import com.twitter.finagle.builder.ServerBuilder
import org.jboss.netty.handler.codec.http.{DefaultHttpResponse, HttpVersion, HttpResponseStatus, HttpRequest, HttpResponse}
import org.jboss.netty.buffer.ChannelBuffers.copiedBuffer
import java.net.{SocketAddress, InetSocketAddress}

object Main {

  def main(args: Array[String]) {

    val bytes: Array[Byte] = Array.fill(1024*1024)(100:Byte)
    val cb = copiedBuffer(bytes)
    val response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                           HttpResponseStatus.OK)
    response.setContent(cb)

    val service = new Service[HttpRequest, HttpResponse] {
      def apply(request: HttpRequest) = {
        Future.value(response)
      }
    }

    val address: SocketAddress = new InetSocketAddress(8000)
    ServerBuilder()
      .codec(Http())
      .bindTo(address)
      .name("HttpServer")
      .build(service)
  }
}

Scala/Spray

これもScala用フレームワーク。AkkaというActorを使った非同期フレームワーク。 Finagleのなんでもできるよ感と違って、REST APIを作るのに特化してるっぽい。

*サンプルプロジェクトをちょっといじっただけ

1
2
3
4
5
6
7
8
9
10
11
package com.example

import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http

object Boot extends App {
  implicit val system = ActorSystem("on-spray-can")
  val service = system.actorOf(Props[MyServiceActor], "demo-service")
  IO(Http) ! Http.Bind(service, interface = "localhost", port = 8000)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example

import akka.actor.Actor
import spray.routing._
import spray.http._
import MediaTypes._

class MyServiceActor extends Actor with MyService {
  def actorRefFactory = context
  def receive = runRoute(myRoute)
}

trait MyService extends HttpService {
  val bytes: Array[Byte] = Array.fill(1024*1024)(100:Byte)
  val myRoute =
    path("") {
      get {
        respondWithMediaType(`text/html`) {
          complete {
            bytes
          }
        }
      }
    }
}

結果一覧

下記のコマンドを実行した結果

1
$ ab -c 100 -n 10000 http://localhost:8000/
言語・FW 実行時間(秒) 処理数/秒 応答時間(ミリ秒) 物理メモリ使用量(KB)
Go  4.312 2319.10 43.120 8084
Go/Martini  4.757 2102.27 45.475 7928
Node.js  4.346 2300.74 43.464 56892
Scala/Finagle  7.682 1301.69 76.823 807656
Scala/Spray  6.184 1146.31 61.843 833040

感想

GoやNode.jsと比べるとScala勢が遅い感じがするけど、FinagleやSprayは重量級で機能豊富なフレームワークだと思うので、 同じ土俵で比べるのは酷だった気がする。実際はDBからデータ取ってきたりというステップが入るので、 速度差は埋まっていくだろうし、性能的にはどれ使っても問題無い気がしている。 Scalaのメモリ使用量が多すぎる気がするけど、JVMアプリのメモリ測定方法は、今回のでは正確に出ないのかも。 この程度のベンチマークでどれを採用するかまでは決められないけれど、Goのメモリフットプリントの少なさはいいなと思う。 どれか一つ選べと言われたら、Go/Martiniあたりがいいかなぁ。 なんかGoはサクサク書けて気持ちいい。


結果詳細

Go

結果

ab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ ab -c 100 -n 10000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        1048576 bytes

Concurrency Level:      100
Time taken for tests:   4.312 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      10486730000 bytes
HTML transferred:       10485760000 bytes
Requests per second:    2319.10 [#/sec] (mean)
Time per request:       43.120 [ms] (mean)
Time per request:       0.431 [ms] (mean, across all concurrent requests)
Transfer rate:          2374973.45 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.5      1      11
Processing:    13   42   2.6     42      74
Waiting:        0    1   2.2      1      33
Total:         14   43   2.7     43      76

Percentage of the requests served within a certain time (ms)
  50%     43
  66%     43
  75%     43
  80%     44
  90%     44
  95%     45
  98%     46
  99%     48
 100%     76 (longest request)

procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /proc/`pgrep -lf 'go_server' | awk '{print $1}'`/status
Name: go_server
State:    S (sleeping)
Tgid: 14952
Pid:  14952
PPid: 6925
TracerPid:    0
Uid:  1000    1000    1000    1000
Gid:  1000    1000    1000    1000
FDSize:   256
Groups:   4 24 27 30 46 108 109 1000
VmPeak:     239304 kB
VmSize:     185236 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:        8084 kB
VmRSS:        8084 kB
VmData:     174376 kB
VmStk:         140 kB
VmExe:        1452 kB
VmLib:        2012 kB
VmPTE:          80 kB
VmSwap:          0 kB
Threads:  5
SigQ: 0/63671
SigPnd:   0000000000000000
ShdPnd:   0000000000000000
SigBlk:   0000000000000000
SigIgn:   0000000000000000
SigCgt:   ffffffffffc1feff
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   0000001fffffffff
Seccomp:  0
Cpus_allowed: ffffffff,ffffffff
Cpus_allowed_list:    0-63
Mems_allowed: 00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  9951
nonvoluntary_ctxt_switches:   7772

Go/Martini

結果

ab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ ab -c 100 -n 10000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        1048576 bytes

Concurrency Level:      100
Time taken for tests:   4.757 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      10486730000 bytes
HTML transferred:       10485760000 bytes
Requests per second:    2102.27 [#/sec] (mean)
Time per request:       47.568 [ms] (mean)
Time per request:       0.476 [ms] (mean, across all concurrent requests)
Transfer rate:          2152920.42 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.5      1       6
Processing:     5   47   8.5     47      88
Waiting:        0   14  14.6      8      54
Total:          8   47   8.5     48      88

Percentage of the requests served within a certain time (ms)
  50%     48
  66%     50
  75%     52
  80%     53
  90%     57
  95%     62
  98%     67
  99%     71
 100%     88 (longest request)

procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /proc/`pgrep -lf 'martini_server' | awk '{print $1}'`/status
Name: martini_server
State:    S (sleeping)
Tgid: 14745
Pid:  14745
PPid: 6925
TracerPid:    0
Uid:  1000    1000    1000    1000
Gid:  1000    1000    1000    1000
FDSize:   256
Groups:   4 24 27 30 46 108 109 1000
VmPeak:     242100 kB
VmSize:     185852 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:        9064 kB
VmRSS:        9064 kB
VmData:     174376 kB
VmStk:         140 kB
VmExe:        1632 kB
VmLib:        2012 kB
VmPTE:          80 kB
VmSwap:          0 kB
Threads:  5
SigQ: 0/63671
SigPnd:   0000000000000000
ShdPnd:   0000000000000000
SigBlk:   0000000000000000
SigIgn:   0000000000000000
SigCgt:   ffffffffffc1feff
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   0000001fffffffff
Seccomp:  0
Cpus_allowed: ffffffff,ffffffff
Cpus_allowed_list:    0-63
Mems_allowed: 00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  985
nonvoluntary_ctxt_switches:   17704

Node.js

結果

ab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
$ ab -c 100 -n 10000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        1048576 bytes

Concurrency Level:      100
Time taken for tests:   4.346 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      10486510000 bytes
HTML transferred:       10485760000 bytes
Requests per second:    2300.74 [#/sec] (mean)
Time per request:       43.464 [ms] (mean)
Time per request:       0.435 [ms] (mean, across all concurrent requests)
Transfer rate:          2356128.80 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      1       5
Processing:    16   43   6.2     45      60
Waiting:        0   14  12.9     13      41
Total:         17   43   6.5     46      60
ERROR: The median and mean for the initial connection time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%     46
  66%     49
  75%     49
  80%     49
  90%     50
  95%     51
  98%     54
  99%     55
 100%     60 (longest request)

procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /proc/`pgrep -lf 'nodejs node_server.js' | awk '{print $1}'`/status
Name: nodejs
State:    S (sleeping)
Tgid: 14860
Pid:  14860
PPid: 6925
TracerPid:    0
Uid:  1000    1000    1000    1000
Gid:  1000    1000    1000    1000
FDSize:   256
Groups:   4 24 27 30 46 108 109 1000
VmPeak:     720076 kB
VmSize:     672132 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:       56892 kB
VmRSS:       56892 kB
VmData:     645248 kB
VmStk:         140 kB
VmExe:        8132 kB
VmLib:        4168 kB
VmPTE:         264 kB
VmSwap:          0 kB
Threads:  2
SigQ: 0/63671
SigPnd:   0000000000000000
ShdPnd:   0000000000000000
SigBlk:   0000000000000000
SigIgn:   0000000000001000
SigCgt:   0000000188004202
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   0000001fffffffff
Seccomp:  0
Cpus_allowed: ffffffff,ffffffff
Cpus_allowed_list:    0-63
Mems_allowed: 00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  6389
nonvoluntary_ctxt_switches:   7180

Scala/Finagle

結果

起動スクリプト

sbt-assemblyで固めて実行。

1
java -jar ./target/scala-2.10/FinageBenchmark-assembly-1.0.jar

ab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ ab -c 100 -n 10000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        1048576 bytes

Concurrency Level:      100
Time taken for tests:   7.682 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      10485950000 bytes
HTML transferred:       10485760000 bytes
Requests per second:    1301.69 [#/sec] (mean)
Time per request:       76.823 [ms] (mean)
Time per request:       0.768 [ms] (mean, across all concurrent requests)
Transfer rate:          1332958.99 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.5      1       5
Processing:     7   75   5.9     75     153
Waiting:        1    3   4.4      2      82
Total:         11   77   5.9     76     155

Percentage of the requests served within a certain time (ms)
  50%     76
  66%     77
  75%     78
  80%     79
  90%     80
  95%     82
  98%     86
  99%     92
 100%    155 (longest request)

procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /proc/`pgrep -lf 'FinageBenchmark-assembly-1.0.jar' | awk '{print $1}'`/status
Name: java
State:    S (sleeping)
Tgid: 15132
Pid:  15132
PPid: 15017
TracerPid:    0
Uid:  1000    1000    1000    1000
Gid:  1000    1000    1000    1000
FDSize:   256
Groups:   4 24 27 30 46 108 109 1000
VmPeak:    3993828 kB
VmSize:    3936516 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:      807656 kB
VmRSS:      726864 kB
VmData:    3878388 kB
VmStk:         140 kB
VmExe:           4 kB
VmLib:       15552 kB
VmPTE:        1868 kB
VmSwap:          0 kB
Threads:  25
SigQ: 0/63671
SigPnd:   0000000000000000
ShdPnd:   0000000000000000
SigBlk:   0000000000000000
SigIgn:   0000000000000000
SigCgt:   2000000181005ccf
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   0000001fffffffff
Seccomp:  0
Cpus_allowed: ffffffff,ffffffff
Cpus_allowed_list:    0-63
Mems_allowed: 00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  5
nonvoluntary_ctxt_switches:   6

Scala/Spray

結果

起動スクリプト

sbt-assemblyで固めて実行。

1
java -jar ./target/scala-2.10/spray_benchmark-assembly-0.1.jar

ab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ ab -c 100 -n 10000 http://localhost:8000/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:        spray-can/1.1.0
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        1048576 bytes

Concurrency Level:      100
Time taken for tests:   6.184 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      10487070000 bytes
HTML transferred:       10485760000 bytes
Requests per second:    1617.00 [#/sec] (mean)
Time per request:       61.843 [ms] (mean)
Time per request:       0.618 [ms] (mean, across all concurrent requests)
Transfer rate:          1656013.76 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.4      1       5
Processing:     7   61   8.4     60     105
Waiting:        1    6   7.2      3      52
Total:         11   62   8.4     61     106

Percentage of the requests served within a certain time (ms)
  50%     61
  66%     63
  75%     65
  80%     66
  90%     70
  95%     76
  98%     82
  99%     85
 100%    106 (longest request)

procfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat /proc/`pgrep -lf 'spray_benchmark-assembly-0.1.jar' | awk '{print $1}'`/status
Name: java
State:    S (sleeping)
Tgid: 15264
Pid:  15264
PPid: 15017
TracerPid:    0
Uid:  1000    1000    1000    1000
Gid:  1000    1000    1000    1000
FDSize:   256
Groups:   4 24 27 30 46 108 109 1000
VmPeak:    4130404 kB
VmSize:    4130400 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:      833040 kB
VmRSS:      832932 kB
VmData:    4069920 kB
VmStk:         140 kB
VmExe:           4 kB
VmLib:       15804 kB
VmPTE:        1948 kB
VmSwap:          0 kB
Threads:  22
SigQ: 0/63671
SigPnd:   0000000000000000
ShdPnd:   0000000000000000
SigBlk:   0000000000000000
SigIgn:   0000000000000000
SigCgt:   2000000181005ccf
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   0000001fffffffff
Seccomp:  0
Cpus_allowed: ffffffff,ffffffff
Cpus_allowed_list:    0-63
Mems_allowed: 00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  5
nonvoluntary_ctxt_switches:   2

Rubyで関数合成できると便利なのになぁという場面に出くわして、以前に見て知っていたけど試したことはなかったLambdaDriverを触ってみた。

( ꒪⌓꒪) ゆるよろ日記 – Rubyで関数合成とかしたいので lambda_driver.gem というのを作った

install

gem install lambda_driver

サンプル

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'lambda_driver'

add_hoge = lambda{|x| x + "hoge"}
add_fuga = lambda{|x| x + "fuga"}

# >>で合成
add_hoge_fuga = add_hoge >> add_fuga
# < で実行(callの別名)
add_hoge_fuga < "piyo"
=> "piyohogefuga"

# <<で逆順で合成
add_fuga_hoge = add_hoge << add_fuga
add_fuga_hoge < "piyo"
=> "piyofugahoge"

かっこよすぎるやろ!でも仕事のコードで使ったら顰蹙物だろうなぁ。

Motivation

Ruby1.8.7で作った大量のバッチがある。 それを1.9(さらには2.0)に移行していきたいんだけど、 一気に移行するまで待ってると時間がかかるので、 少しずつ移行したい。また、同じサーバ内で並行運用したい。 そこで、rbenvで複数のRuby環境を用意し、切り替えて運用したい。 バッチは全てcronで動いているので、cron環境でのPATHをうまく設定すればいけるはず。

環境

CentOS6.4

事前準備

gitをインストール

1
yum install git

epelをインストール

後で必要になるライブラリを入れるため、EPELを追加しておきます。

1
2
3
cd /usr/local/src
wget http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
rpm -ivh epel-release-6-8.noarch.rpm

rbenv

参考:CentOSでsystem wideなrbenv+ruby-build環境を構築する

rbenvをインストール

rbenvとruby-buildを/usr/localの下にインストールします。

1
2
3
4
5
6
7
8
9
10
su -
cd /usr/local
git clone git://github.com/sstephenson/rbenv.git rbenv
mkdir rbenv/shims rbenv/versions
groupadd rbenv
chgrp -R rbenv rbenv
chmod -R g+rwxXs rbenv
git clone git://github.com/sstephenson/ruby-build.git ruby-build
cd ruby-build
./install.sh

環境変数を設定

グローバルな環境変数にrbenvへのパスを追加。

vi /etc/profile.d/rbenv.sh

1
2
3
export RBENV_ROOT="/usr/local/rbenv"
export PATH="/usr/local/rbenv/bin:$PATH"
eval "$(rbenv init -)"

書いたら読み込みます。

1
source /etc/profile.d/rbenv.sh

インストール可能なruby一覧を見てみる

1
rbenv install --list

ruby install

依存ライブラリのインストール

1
yum install --enablerepo=epel make gcc zlib-devel openssl-devel readline-devel ncurses-devel gdbm-devel db4-devel libffi-devel tk-devel libyaml-devel

ruby自体のインストール

ここでは以下の3つのバージョンを入れてみます。

1
2
3
rbenv install 1.8.7-p374
rbenv install 1.9.3-p448
rbenv install 2.0.0-p247

(Dockerの場合のみ)fdのシンボリックリンク作成

Docker上のCentOS6.4で試していたらエラーが出るので、以下のコマンド実行

参考:unable to set global or or local ruby on freebsd

1
ln -s /proc/self/fd /dev/fd

インストールされたrubyの確認

1
rbenv versions

globalのrubyバージョン設定

1
2
rbenv global 1.9.3-p448
rbenv rehash

localのrubyバージョン設定

1
2
rbenv local 2.0.0-p247
rbenv rehash

rbenv-rehash,bundlerのインストール

いちいちrehashするのも面倒なので、自動でrehashしてくれるrbenv-rehashを入れます。 ついでに後々必要になるbundlerも入れておきましょう。 インストールした全てのバージョンで行なっておく必要があります。

参考:crontabでrbenvのrubyを使う

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
su -

rbenv global 1.8.7-p374
gem install rbenv-rehash
gem install bundler
rbenv rehash

rbenv global 1.9.3-p448
gem install rbenv-rehash
gem install bundler
rbenv rehash

rbenv global 2.0.0-p247
gem install rbenv-rehash
gem install bundler
rbenv rehash

Rubyバージョン設定

それぞれのディレクトリでrbenv local

以下のようなディレクトリ構成で、batch_18はRuby1.8、 batch_19はRuby1.9で動かしたいとします。 exec_batch_a.shはバッチを起動するためのshellスクリプトです。

1
2
3
4
5
6
7
8
9
10
/home/sudix
└── batches
    ├── batch_18
    │   ├── batch_a.rb
    │   ├── exec_batch_a.sh
    │   └── Gemfile
    └── batch_19
        ├── batch_a.rb
        ├── exec_batch_a.sh
        └── Gemfile

この場合、それぞれのディレクトリに入って、rbenv localでRubyのバージョンを指定します。 これで、それぞれのディレクトリに.ruby-versionが作成されるはずです。 同時にbundlerでgemを入れておきますが、ここではpathを指定せず、グローバルに入れていしまいます。 このあたりは必要に応じて変えてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
cd /home/sudix/batches/batch_18
rbenv local 1.8.7-p374
su
cd /home/sudix/batches/batch_18
bundle install
exit

cd /home/sudix/batches/batch_19
rbenv local 1.9.3-p448
su
cd /home/sudix/batches/batch_19
bundle install
exit

Gemfileはここでは普通に作成していますが、クックパッドさんでは バージョンごとのGemfileを作成して管理しているようで、参考になります。

Cookpad の本番環境で使用している Ruby が 2.0.0-p0 になりました

起動shell

バッチ実行前に意図した.ruby-versionが有効となるディレクトリに移動したいので、 起動shellは以下のような感じにしました。

1
2
3
4
5
6
7
8
#!/bin/sh

dir=`dirname $0`
cd $dir

ruby -e "require './batch_a.rb'; BatchA.new.main"

exit

確認

使いたいRubyが本当に使えているか、確認します。 BatchAのmainは、RUBY_VERSIONをputsするだけのメソッドです。

1
2
3
4
5
$ cd
$ /home/sudix/batches/batch_18/exec_batch_a.sh
1.8.7
$ /home/sudix/batches/batch_19/exec_batch_a.sh
1.9.3

cronの設定

参考:cronでのversion変更について

rbenvをglobalにインストールしているので、上記参考先とは rbenvのbinとshimsのpathが異なるので注意してください。 PATHに/usr/local/rbenv/binと/usr/local/rbenv/shimsを追加します。

crontab -e

1
2
3
4
5
SHELL=/bin/sh
PATH=/usr/local/rbenv/bin:/usr/local/rbenv/shims:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

0 * * * * /home/sudix/batches/batch_18/exec_batch_a.sh
0 * * * * /home/sudix/batches/batch_19/exec_batch_a.sh

以上でcronから実行されるバッチのRubyバージョンを切り替えることができました。

http://shop.oreilly.com/product/9780596805531.do

あまりWEBのフロント側の開発を行ったことが無くてこれまでJavaScriptは避けていました。 prototypeとか見る度に変な言語だなぁと感じて、好きではなかったのもあります。 しかしついに使う場面がきそうなので、真面目に勉強することに。 JavaScriptの本と言えばサイ本と聞いたことがあったので、英語の勉強がてら原書を電子版を購入してみました。 気軽に読み始めたものの、700ページもあって読み終えるのに随分時間がかかりましたが、 想像以上の面白さでした。

最初は言語仕様についてひたすら書かれていますが、関数型言語としての側面も紹介されたりして、 Scalaを触っていた事もあり引きこまれました。 わけが分からないと思っていたprototypeも分かりやすく説明されています。 驚いたのが正規表現についてもやたら丁寧に説明されていること。 今まで読んできた正規表現関係の資料の中で一番良かったかも。

後半ではDOM操作やjquery、node.jsの紹介まであり、 さらに最後にはHTML5関連(local storageやWebSocket)にも触れられており、 満足感いっぱいです。

実は英語の本を最後まで読み通したのはこれが初めてだったりしますが、 使われている表現も平易で読みやすく、長文を読むのにも随分慣れた気がします。 その点でもおすすめ。

結論

評価:★★★★★
JavaScriptを一からきっちり勉強したい方にとってもおすすめ。
ただプログラムの経験が無い人がこれで勉強するには難しいかも。

日々変わる数字があって、たまに異常に減ったり増えたりする場合があり、 それが起きたときにすぐわかるようにしたい。 という要望があったので、標準偏差を使ってエラーっぽい値が分かるようにしてみました。 ブラウザ上で実行する必要があったのでcoffeescriptです。

コード

checkRangeListメソッドにデータを与えると、下限以下なら-1、上限以上なら1を返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
######################
# 異常値計算用クラス
######################

class ErrorDataDetector

  self = undefined

  # 平均からどれぐらい離れたら異常とみなすかの定数。
  # ±(この値×標準偏差)から外れていれば異常とします。
  # 2であればおよそ95%範囲の外の値になります。
  normalRange = 2

  constructor: -> self = this

  #平均を求める
  average: (data) ->
    data.reduce((acc, n) -> acc + n) / data.length

  #分散を求める
  #((データ-平均値)の2乗)の総和÷ 個数
  variance: (data, avg) ->
    data.reduce((acc, n) -> acc + Math.pow(n - avg, 2)) / data.length

  #標準偏差を求める
  #分散の平方根
  standardDeviation: (data, avg) ->
    Math.sqrt(self.variance(data, avg))

  #外れ値かどうかを調べ、リストに結果を詰めて返す
  #-1:範囲外(下) 0:正常 1:範囲外(上)
  checkRangeList: (data) ->
    avg = self.average(data)
    sd = self.standardDeviation(data, avg)
    lb = self.lowerBound(avg, sd)
    ub = self.upperBound(avg, sd)
    data.map (n) -> self.checkRange(n, avg, lb, ub)

  # 下限境界
  lowerBound: (avg, sd) -> avg - (sd * normalRange)

  # 上限境界
  upperBound: (avg, sd) -> avg + (sd * normalRange)

  checkRange: (n, avg, lb, ub) ->
    if avg == 0
      0
    else if n <= lb
      -1
    else if n >= ub
      1
    else
      0

実行結果

1
2
3
4
l = [826,751,833,905,888,950,880,868,935,1293,1315,1555,1445,1732,1351,1157,1268,1201,733,2000,100]
d = new ErrorDataDetector()
console.log(d.checkRangeList(l))
 # 結果 => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1]

オチ

さっそく実装して試したところ異常っぽい値を検知できたので意気揚々と担当者に見せたところ、 よくわかんないからもっと簡単なロジックにしてと言われましたとさ・・・。

こちらのエントリ もしぼくが採用するなら を読んで、エラトステネスの篩は知ってはいるものの実装してみたことないなぁと思い、 Scalaで実装してみました。

Wikipediaの例題を参考に作ってみます。 考え方としては自然数の集合に対して、小さい順からその倍数を削除していって、 残ったものが素数、という考えです。

最初、こんな感じで実装してみました。

末尾再帰じゃないバージョン
1
2
3
4
5
6
7
8
9
10
11
12
13
def sieveOfEratosthenes(numbers: List[Int]): List[Int] = {
  numbers match {
    case Nil =>
      Nil
    case 1 :: tail =>
      sieveOfEratosthenes(tail)
    case head :: tail =>
      head :: sieveOfEratosthenes(tail.filterNot(_ % head == 0))
  }
}

// 実行
println(sieveOfEratosthenes(Range(1, 121).toList))

まあこれで動くには動くのですが、最後で再帰の結果に対して演算しているので末尾再帰されてません(@tailrecつけるとコンパイルエラーになる)。 なので数が多くなるとOutOfMemoryで落ちます。

いろいろ考えて、素数の結果リストを引数で渡すようにしました。

末尾再帰バージョン
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scala.annotation.tailrec

@tailrec
def sieveOfEratosthenes(numbers: List[Int], primes: List[Int]): (List[Int], List[Int]) = {
  numbers match {
    case Nil =>
      (numbers, primes.reverse)
    case 1 :: tail =>
      sieveOfEratosthenes(tail, primes)
    case head :: tail =>
      sieveOfEratosthenes(tail.filterNot(_ % head == 0), head :: primes)
  }
}

// 実行
sieveOfEratosthenes(Range(1, 120).toList, Nil)._2

末尾再帰になった!しかし実行時に引数にNilを渡したり、結果がタプルなので結果取得にひと手間かかったり、かっこ悪すぎ。
なので隠蔽を図った結果がこれ。

末尾再帰バージョン(呼び出しをシンプルに)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scala.annotation.tailrec

def sieveOfEratosthenes(numbers: List[Int]): List[Int] = {
  @tailrec
  def sieve(numbers: List[Int], primes: List[Int]): (List[Int], List[Int]) = {
    numbers match {
      case Nil =>
        (numbers, primes.reverse)
      case 1 :: tail =>
        sieve(tail, primes)
      case head :: tail =>
        sieve(tail.filterNot(_ % head == 0), head :: primes)
    }
  }
  sieve(numbers, Nil)._2
}

// 実行
println(sieveOfEratosthenes(Range(1, 121).toList))

できたけどなんかゴチャゴチャしてる感じがありますね・・・。きっともっと素敵な書き方があるはず。
ということでググってみると、
Sieve of Eratosthenes in Scala
こちらの記事を発見。おお、Stream使うとこんなにもスッキリ書けるんですね。確かに衝撃的。
まだまだScala力が足りません。

これまではてなダイアリーを使ってきましたが、
octopressを知って面白そうだったので導入してみました。
「A blogging framework for hackers.」
とか言われたら使うしかないですよね。
導入も簡単だし、markdownで記事書けるし楽しそうなので、
しばらく使ってみます。


参考にさせていただいたサイト

OctopressとGitHub Pagesを使用したブログの構築手順

Octopress で GitHub Pages を置き換える


環境

  • Ubuntu 13.04
  • Ruby 1.9.3

Octpressの設定

Octopressをfork

Octopressをダウンロードする前に、自分のリポジトリにforkしておくのをお勧めします。
いきなりcloneしてもいいのですが、forkしておけば変更後そのままpushすることで、
GitHub上でソースを管理することができます。
こちらからforkしましょう。

Octopressをダウンロード

cloneするだけです。

Octopressのダウンロード
1
$ git clone git@github.com:sudix/octopress.git

初期設定

rubyのバージョン設定と、必要なgemのインストールを実行します。
rubyのバージョン設定は自分の環境に合わせて.rbenv-versionを書き換える事になります。
自分の環境だとubuntuのパッケージから入れたので「1.9.3-debian」となります。
これを設定しておかないと動きません。
また、gemのインストールはbundlerを使って、vendor/bundle以下にインストールしています。
参考:http://octopress.org/docs/setup/

Octopressの初期設定
1
2
3
4
5
$ cd octopress
$ echo "1.9.3-debian" > .rbenv-version
$ sudo gem install bundler
$ bundle install --path vendor/bundle
$ bundle exec rake install

プレビュー

以下のコマンドを実行します。

1
bundle exec rake preview

これで次のURLでプレビューできるようになります。
http://localhost:4000/

テーマ

テーマの設定が可能です。以下からお好みのテーマを探してみてください。
3rd party themes

今回はこのテーマを使ってみます。
greyshade

テーマのインストール
1
2
3
4
5
$ git clone git@github.com:shashankmehta/greyshade.git .themes/greyshade
$ echo "\$greyshade: #073642;" >> sass/custom/_colors.scss //Substitue 'color' with your highlight color
$ bundle exec rake "install[greyshade]"
$ bundle exec rake generate
$ bundle exec rake preview

コンフィグ

参考:http://octopress.org/docs/configuring/
_config.ymlを編集し、各種設定を行います。変更した箇所だけを抜粋しています。

_config.yml
1
2
3
4
5
6
7
url: http://sudix.github.io
title: Sudix Blog
subtitle: my blog about programming, IT, etc...
author: sudix
description: about programming, IT, etc...
date_format: "%Y/%m/%d"
email: "youremailaddress@example.com"

Githubの設定

create repository

手順:http://octopress.org/docs/deploying/github/

Github pages用のリポジトリを作っておく必要があります。
「自分のユーザー名.github.io」という名前のリポジトリになります。
自分の場合だと「sudix.github.io」ですね。
octopressの上記手順で説明のある.comではなく.ioで作る必要があるようです。
昔と変わったのでしょうか。
Github Pagesのマニュアル

リポジトリ作成はこちらから。https://github.com/new


デプロイ&公開

いよいよ公開してみます。

リポジトリの登録

rake setup_github_pagesを実行するとRepository urlを入力しろと聞かれるので、
先ほど作ったリポジトリ(git@github.com:sudix/sudix.github.io)を入力します。

リポジトリの登録
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ bundle exec rake setup_github_pages
Enter the read/write url for your repository
(For example, 'git@github.com:your_username/your_username.github.io)
           or 'https://github.com/your_username/your_username.github.io')
Repository url: git@github.com:sudix/sudix.github.io
rm -rf _deploy
mkdir _deploy
cd _deploy
Initialized empty Git repository in /home/sudix/apps/octopress/_deploy/.git/
[master (root-commit) 5a3c02f] Octopress init
 1 file changed, 1 insertion(+)
 create mode 100644 index.html
cd -
---
## Now you can deploy to http://sudix.github.io/sudix with `rake deploy` ##

デプロイ

以下のコマンドでデプロイされます。

デプロイ実行
1
$ bundle exec rake deploy

公開されたか確認

Github Pagesを開いてみて、公開されたか確認してみましょう。
なお、デプロイして10分ほど待つ必要があるみたいです。
http://sudix.github.io/


コメント欄を追加

DISQUSを使って、コメント欄を追加することもできます。
DISQUSに登録するとshort nameが発行されるので、
それを_config.ymlに設定するだけです。

_config.yml
1
2
3
# Disqus Comments                                                                                                                       
disqus_short_name: sudixsblog
disqus_show_comment_count: false

追加した後、generateし直せば、下にコメント欄が表示されるはずです。


記事を書く

さっそく記事を書いてみましょう。 手順:http://octopress.org/docs/blogging/

ファイル作成

rakeでnew_postを実行すれば記事を記入するためのmarkdownファイルが作成されます。
new_postの後にタイトルを入力しましょう。
ここで指定したタイトルを使ってパーマネントリンクが作成されます。
タイトルは日本語不可のようです。

記事の作成
1
$ rake new_post["my first octopress article"]

タイトルの編集

作成されたファイルを開くと、先頭にyamlで記述された設定があるので、
タイトルを変更したい場合はその中のtitleを編集します。

./source/_posts/yyyy-mm-dd-my-first-octopress-article.markdown
1
2
3
4
5
6
7
8
---
layout: page
title: "Octopressでの初めての記事"
date: 2013-08-06 20:59
comments: true
sharing: true
footer: true
---

記事の編集と公開

あとはmarkdown形式で記事を書き、generateとdeployを実行すれば、記事が公開されます。

おまけ コードスニペットの書き方

gist使ったり色々と書き方はあるみたいです。
参考:http://octopress.org/docs/blogging/code/
基本はバッククォートで囲みます。言語も指定可能です。

```ruby Test.rb
def hoge
  puts "hogefuga"
  1 + 2
end
```

と書けば、以下のように。

Test.rb
1
2
3
4
def hoge
  puts "hogefuga"
  1 + 2
end

Scalaだって

```scala Hoge.scala
object Hoge {
  def main(args: Array[String]) {
    println("hoge")
  }
}
```

Hoge.scala
1
2
3
4
5
object Hoge {
  def main(args: Array[String]) {
    println("hoge")
  }
}

綺麗に表示されて楽しいですね!