RSS2.0

Scala で遊んでみる

最近私のまわりでも Scala について耳にすることが増えてきましたが、私自身はこれまであまり触ったことがなかったので、少し試してみることにしました。
Scala については、関数型であることや並列処理に特化していることによく触れられています。JVM 上で動作することもあって Java の影響を強く受けていて、型安全性やオブジェクト指向言語であること、Java との親和性なども特徴としています。

最初の 1 歩ということで、Scala 言語そのものについて調べてみるというよりは、実際に動かすところまでを目標に置いてみました。

Scala を実行する

まずは言語標準の scala コマンドから Scala コードを動かしてみます。手順は以下の通りです。

Scala のインストール

前提として Java 8 以上が必要になるので、別途インストールしておきます。
その後は yum コマンドから Scala をインストールしましょう。
# yum install scala

インストール後に scala コマンドが実行できれば OK です。
$ scala -version
Scala code runner version 2.10.6 -- Copyright 2002-2013, LAMP/EPFL

scala コマンドで実行する

以下の内容のソースコード factrization.scala を作成します。
import scala.collection.mutable.ArrayBuffer

object Factorization {

  def main(args: Array[String]): Unit = {
    val random = scala.util.Random
    val numbers =
      if (args.isEmpty) {
        // 引数で素因数分解する数値が指定されていなければ、10 個の乱数を生成する
        (1 to 10).map(i => random.nextInt(Int.MaxValue))
      } else {
        // 引数の数値を素因数分解する
        // Array 型を Vector 型に変換する
        args.map(i => i.toInt).to[collection.immutable.Seq]
      }

    println("numbers: " + numbers.mkString(", "))
    // 並列化せずに素因数分解し、その処理時間を計測する
    logProcessingTime("Serial  ", numbers.map(i => factorize(i)))
    // 並列化して素因数分解し、その処理時間を計測する
    logProcessingTime("Parallel", numbers.par.map(i => factorize(i)))
  }

  // 引数の数値を素因数分解し、素数のリストを返す
  private def factorize(number : Int) : List[Int] = {
    val list = new ArrayBuffer[Int]
    var n = number
    var f = 2

    while (n != 1) {
      if (n % f == 0) {
        list += f
        n /= f
      } else {
        f += 1
      }
    }

    println("Thread:%3d factorize %10d to %s".format(Thread.currentThread.getId, number, list.mkString(", ")))
    list.toList
  }

  // 第二引数の式を処理し、その処理時間を出力する
  private def logProcessingTime(label: String, process: => Unit) = {
     val start = System.currentTimeMillis

     process
     println(label + ": " + (System.currentTimeMillis - start) + "ms")
  }

}

このソースコードでは、10 個の乱数を生成してそれぞれ素因数分解し、かかった処理時間を出力します。
ただし、Scala らしく並列化して(JVM 上で動作するので実際にはマルチスレッドで)実行した場合とそうでない場合の 2 パターンで処理を行い、処理時間がどの程度変わるのかを測定できるようにしました。

ソースコードの内容については、今回は特に解説を記載しません。
Scala 言語自体については公式サイト言語仕様や、こちらのドワンゴの研修資料を読んでみてください。

scala コマンドで .scala ファイルを実行するには、scala コマンドの引数に実行する .scala ファイルを指定します。
$ scala factrization.scala
numbers: 740840753, 1799107720, 732514010, 1338251277, 1332376906, 183685917, 2062665120, 1533913786, 1055422572, 481899844
Thread:  1 factorize  740840753 to 740840753
Thread:  1 factorize 1799107720 to 2, 2, 2, 5, 19, 881, 2687
Thread:  1 factorize  732514010 to 2, 5, 83, 131, 6737
Thread:  1 factorize 1338251277 to 3, 11, 40553069
Thread:  1 factorize 1332376906 to 2, 7, 269, 499, 709
Thread:  1 factorize  183685917 to 3, 47, 1302737
Thread:  1 factorize 2062665120 to 2, 2, 2, 2, 2, 3, 5, 233, 18443
Thread:  1 factorize 1533913786 to 2, 766956893
Thread:  1 factorize 1055422572 to 2, 2, 3, 87951881
Thread:  1 factorize  481899844 to 2, 2, 29, 4154309
Serial  : 6001ms
Thread: 10 factorize  183685917 to 3, 47, 1302737
Thread: 10 factorize 2062665120 to 2, 2, 2, 2, 2, 3, 5, 233, 18443
Thread:  9 factorize  740840753 to 740840753
Thread:  9 factorize 1799107720 to 2, 2, 2, 5, 19, 881, 2687
Thread:  9 factorize  732514010 to 2, 5, 83, 131, 6737
Thread: 10 factorize 1533913786 to 2, 766956893
Thread:  9 factorize 1338251277 to 3, 11, 40553069
Thread:  9 factorize 1332376906 to 2, 7, 269, 499, 709
Thread: 10 factorize 1055422572 to 2, 2, 3, 87951881
Thread: 10 factorize  481899844 to 2, 2, 29, 4154309
Parallel: 4204ms
並列化せずに処理した場合は約 6 秒、並列化した場合は約 4.2 秒という結果になりました。
出力されているスレッド ID を見ると、並列化した場合は 2 スレッドが動作しているのがわかりますが、全体で見ると半分の速度にスケールしているわけではないことがわかります。処理する数値によっては、2 割程度しか処理時間が短縮されない場合もありました。
私もまだそんなに Scala を使い込んでいるわけではないのですが、なんとなく、この辺の動きは Scala の動く JVM の性質が出ていそうだなと思いました。

sbt で Scala を実行する

Scala で開発をする場合には sbt を利用するのが一般的のようです。
sbt は Scala や Java のビルドツールで、maven や gradle のような役割を担うものです。他のモジュールとの依存性解決や、Java と Scala のハイブリッドなプロジェクトの管理などもできるようです。

sbt のインストール

sbt は以下の手順でインストールすることができます。
# curl https://bintray.com/sbt/rpm/rpm | tee /etc/yum.repos.d/bintray-sbt-rpm.repo
# yum install sbt

sbt でのプロジェクト作成

sbt コマンドで新規プロジェクトを作ってみます。sbt はネットワーク上のテンプレートを読み込んで雛形とし、プロジェクトを作成できます。
このネットワーク経由で必要なファイルを読み込む動作のため、sbt コマンドは全体的に初回動作が重いです。
$ sbt new scala/hello-world.g8
...
name [Scala Seed Project]: factorization

Template applied in ./factorization
途中でプロジェクト名の入力を求められるので、入力してください。ここでは factorization としています。

作成すると、カレントディレクトリ内に、指定したプロジェクト名と同じ名前のディレクトリが作られます。これがプロジェクトのディレクトリになります。
中には build.sbt や project/build.properties といったビルドのためのファイルや、ソースコードの src/main/scala/Main.scala があります。

sbt コマンドで scala を実行する

まずはソースコードを用意します。
自動生成される src/main/scala/Main.scala を見ると、App を継承したクラスがエントリーポイントとなることがわかりますが、前述の factrization.scala をそのまま動かすことも可能です。
src/main/scala/Main.scala を削除し、src/main/scala/ 下に factrization.scala を配置します。

ソースコードを配置したら、以下の手順で scala を動かします。
$ sbt
sbt:hello-world> run
[info] Running Factorization 
[debug] Waiting for threads to exit or System.exit to be called.
...
numbers: 1575155119, 1777423420, 228484249, 283434246, 149148348, 1385273509, 1944285823, 428309206, 1277389407, 471955042
Thread: 30 factorize 1575155119 to 19, 82902901
Thread: 30 factorize 1777423420 to 2, 2, 5, 83, 127, 8431
Thread: 30 factorize  228484249 to 7, 47, 694481
Thread: 30 factorize  283434246 to 2, 3, 3, 15746347
Thread: 30 factorize  149148348 to 2, 2, 3, 457, 27197
Thread: 30 factorize 1385273509 to 17, 23, 43, 82393
Thread: 30 factorize 1944285823 to 59, 32953997
Thread: 30 factorize  428309206 to 2, 13, 13, 31, 41, 997
Thread: 30 factorize 1277389407 to 3, 7, 60828067
Thread: 30 factorize  471955042 to 2, 13, 59, 359, 857
Serial  : 1030ms
Thread: 32 factorize 1385273509 to 17, 23, 43, 82393
Thread: 32 factorize 1944285823 to 59, 32953997
Thread: 32 factorize  428309206 to 2, 13, 13, 31, 41, 997
Thread: 31 factorize 1575155119 to 19, 82902901
Thread: 31 factorize 1777423420 to 2, 2, 5, 83, 127, 8431
Thread: 31 factorize  228484249 to 7, 47, 694481
Thread: 32 factorize 1277389407 to 3, 7, 60828067
Thread: 32 factorize  471955042 to 2, 13, 59, 359, 857
Thread: 31 factorize  283434246 to 2, 3, 3, 15746347
Thread: 31 factorize  149148348 to 2, 2, 3, 457, 27197
Parallel: 755ms
...
[debug] Exited with code 0
sbt コマンドを実行すると sbt のプロンプトが起動するので、そこで run を実行してください。
scala コマンドから直接実行した場合と同様に、10 個の乱数を素因数分解する時間が出力されました。
  Scala  コメント (0)  2018/01/23 20:15:05


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2018, 8>>
2930311234
567891011
12131415161718
19202122232425
2627282930311