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 0sbt コマンドを実行すると sbt のプロンプトが起動するので、そこで run を実行してください。
scala コマンドから直接実行した場合と同様に、10 個の乱数を素因数分解する時間が出力されました。
Scala コメント (0) 2018/01/23 20:15:05