Yammerの従業員であるCoda Hale氏がScalaの商用ベンダであるTypesafe社へ送ったメールがYCombinatorとGitHubのgist経由でリークした。メールによれば、Yammerは複雑さや性能面を鑑みて、基盤のインフラをScalaからJavaへ戻すそうだ。
YammerのPR部門のShelley Risk氏によればこのメールはCoda Hale氏の個人的な意見であり、Yammerの公式な見解ではないとのことだ。このリーク後、Coda Hale氏は自身の考えをhttp://codahale.com/the-rest-of-the-story/で表明している。氏はこのメールはDonald Fischer氏(Typesafe社のCEO)からのフィードバックの要請に答えたもので、移行を示唆するツイートを補足したものだと説明している。このメールを公にするつもりはなかったが、氏はこれをGitHubのGist(削除されているが)にアップし、友人からフィードバックを求めた。結果的にこのメールは広く共有された。
2010年8月、氏はYammerのエンジニアリングブログに、リアルタイム機能のためにScalaへ移行していると書いた。目標はJVM上(性能のため)でサービスを提供しつつ、約50%コードを削減することだ。
最初のArtieのプロトタイプはJavaで実装しました。しかし、週末になって試しにScala 2.8で再実装したところ、コードが半分になり、いくつか面白い機能も実装できました。私は納得しました。Javaを雇うのは簡単ですが、Scalaチームの方がいろいろなことができそうです。
しかし、1年と4ヶ月経つと、この決定は反転していた。
今、Yammerでは、基盤インフラをJavaへ移行しています。 Scalaはファザードとレガシなライブラリの部分で使い続けるつもりです。 急いではいません。始めたばかりです。この決定までには時間がかかりました。 Javaの代わりにScalaを使うことで生まれる軋轢や複雑さは、 Scalaの生産性や運用負荷の削減によっては埋め合わせできません。 まだ、Scalaを使っていますし、ずっと使い続けると思いますが、主要な開発はJavaで行うつもりです。
Stephen Colebourne氏は最近Scalaは新しいEJB2かという記事を書いたが、氏はこのメールに箇条書きで注釈を付けている。
- 言語としてのScalaは興味深い特徴を持っている。しかし、とても複雑な言語でもある。
- Scalaがもたらす概念や実装に加えて、自然なScalaを書こうとする文化がある。これを突き詰めるとある時点でベストプラクティスが現れる。それは、コミュニティを無視することだ。
- 後になって気付いたことだが、私はScalaを学ぶこと(教えること)の難しさと重要さを低く見積もりすぎていた。というのは、Scalaの経験のある開発者を雇うのは不可能だからだ。これは思ったよりも遥かに大きな問題だ。
- 開発に不安を持ち込むのは問題だ。SBTが唯一のツールなのは、MavenやAntの限界を示している。この2つのビルドツールはJavaのエコシステムのものだ。
- Scalaのメジャーリリースは後方互換性がないので、Scalaの開発者は新しいライブラリを使い、車輪の再発明を促進しやすい。
- バイトコードの解析と調査を行った結果、100倍も性能が改善される簡単なルールが見つかった。
- forループを使わない
- scala.collection.mutableを使わない
- scala.collection.immutableを使わない
- private[this]を使う
- クロージャを使わない
- 私はこの[Javaへ移行するという]問題をチームに切り出し、ふたつのコードでデモをしたところ、移行することに対してすぐに合意がとれたことに驚きました。Scalaには見落としている点がまだあると思いますが、そのような特徴があってもScalaを使い続ける理由としては不十分です。
このような問題のいくつかは状況に依存する(例えば、経験のある開発者の雇いやすくなればなるほど、ある言語は人気が出る)が、テストをすることもできる。例えば、アドバイスできることのひとつはfor
ループを使わないことだ。これは下記のコードでテストできる。
scala>
var start = System.currentTimeMillis();
var total = 0;for(i <- 0 until 100000) { total += i };
var end = System.currentTimeMillis();
println(end-start);
println(total);
114
scala>
scala<
var start = System.currentTimeMillis();
var total = 0;var i=0;while(i < 100000) { i=i+1;total += i };
var end = System.currentTimeMillis();
println(end-start);
println(total);
8
上記の'until'を使ったforループ(多くのプログラマが普通だと思っている書き方)の処理は、whileを使ったループの処理に比べて読みやすいが、とても遅い。Javaの同様のループ処理の場合の結果は、for
でもwhile
でも同じ2ミリ秒だ。
mutableのマップにIntegerオブジェクトのデータセットをロードするを使った別のテストも行った(今回はJavaとScalaを比較した。ボキシングのコストは同等と想定する)。
scala>
val m = new scala.collection.mutable.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
101
scala>
val m = new java.util.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
28
scala>
val m = new java.util.concurrent.ConcurrentHashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis();
while(i<100000) { i=i+1;m.put(i,i);};
var end = System.currentTimeMillis();
println(end-start);
println(m.size)
55
普通のJavaのコードと比べるとjava.util.HashMap
の場合は性能は同じだ。java.util.concurrent.ConcurrentHashMap
を使った場合はJavaの方が同じScalaのコードと比べて2倍速い。両方ともScalaのHashMapよりも性能が良い。(計測環境はOSXとJVM 1.6.0_29、Scala 2.9.1。Scalaはこの記事を書いたときの最新のバージョン。)
残念ながらScalaのコレクションはScalaのライブラリAPIで広く使われている。従って、上記のコード上ではJavaオブジェクト型からScalaオブジェクト型に暗黙に変換される。くだんのメールによれば、これが性能問題を引き起こし、大幅な書き換えの原因になった。
Scalaのコンパイラがinvokedynamic
を使ったコードを生成すれば、クロージャ(ラムダ)の性能は改善するかもしれない。これはScalaの将来のバージョンのコンパイラで実現するかもしれない。加えて、JDK 8(ネイティブのラムダとメソッドハンドルを提供する予定)には多くの性能上の長所があるので、将来のScalaもこれらの利点の恩恵を受けることができるだろう。
そして、Scalaに対して各リリース(2.9.2から2.9.3へのマイナーバージョンアップで互換性を維持するだけでなく)の後方互換性を維持するように求める圧力が高まっている。ScalaのロードマップについてはTypesafe社から正式な発表はない。また、いつコンパイルされたバイナリが各リリース間で後方(あるいは前方)互換性があるようになるのかも分からない。後方互換性があるようになれば、より安定したライブラリをリリースし、Scalaに興味がある開発者が誰でも利用できるコミュニティ用のリポジトリを準備することも出来る。
コミュニティコメント
一方的な見解のみを翻訳することの妥当性について
by 宏太 水島,
InfoQ(英語版)のコメントが参照されにくいことによる問題提起と解決案
by 宏太 水島,
Re: InfoQ(英語版)のコメントが参照されにくいことによる問題提起と解決案
by Hanyuda Eiiti,
for文のパフォーマンスについての追試結果
by 宏太 水島,
Re: for文のパフォーマンスについての追試結果
by 宏太 水島,
一方的な見解のみを翻訳することの妥当性について
by 宏太 水島,
スパムの可能性があると認識されました。モデレーターが確認し問題がなければ24時間以内に公開します。その際あなたへの通知は行われませんのでご了承ください。
はじめまして。水島と申します。元記事の「ベンチマーク」については、これがベンチマーク
のやり方として良くないことが、英語版の記事のコメントにおいて、多数指摘されています。
たとえば、Alex Blewitt氏の示したベンチマークでは、ベンチマークに向いていない
対話型環境(通常のScala実行環境よりも実行が遅くなることがあります)を使って、
「1回だけ」、しかもms単位での計測を行っていますが、これはマイクロベンチマークの
一般原則を完全に無視しています。英語版の記事へのコメントを見ていただければわかり
ますが、実際の所、コードがJITコンパイルされる(これは、特にその部分が繰り返し実行
される場合に一般的な状況です)場合、forはwhileと同程度の性能になりますし、scala.collection.mutable.HashMap と java.util.HashMap の性能差についても、悪くて
2倍程度、良ければ同程度になります。
forとwhileの性能差の詳細な測定については、
d.hatena.ne.jp/chimerast/20110310/1299723622
のブログ記事も参考になると思います。
これは、この記事に限らず、英語版のInfoQ記事を翻訳される方にお願いしたいのですが、
元記事に対して反論が多数寄せられている場合、その全てを翻訳するのはさすがに無理だ
と思いますが、主要な反論を取り出して、両論併記の形にしていただけないでしょうか。
英語版の記事を読まない読者は、日本語版での偏った情報のみを目にする事になりますから、
結果として、記事の公平性を判断するための材料が無くなります。これは問題だと思います。
InfoQ(英語版)のコメントが参照されにくいことによる問題提起と解決案
by 宏太 水島,
スパムの可能性があると認識されました。モデレーターが確認し問題がなければ24時間以内に公開します。その際あなたへの通知は行われませんのでご了承ください。
水島と申します。先ほどの、翻訳記事に関する私のコメントが少し一方過ぎたというご指摘を複数の方からいただき、考え直すところがありましたので、改めて投稿させていただきます。
まず、テクニカルな話については、概ね変わりありません。元記事(および翻訳記事
においても)ベンチマークは、ベンチマークとしては上手く機能しておらず、実際のScalaの
性能とはかなり異なります。また、元記事が参照しているCoda Hale氏の見解ですが、Scala
のバージョン間のバイナリ非互換性の問題など、的を射た指摘がいくつもあるものの、性能
特性については疑問が残ります。実のところ、Code Hale氏のパフォーマンスに関する
アドバイスは、Scala 2.7(~2010前半)では正しかったのですが、現在の安定版である
Scala 2.9では、forループに関する性能はかなり改善されており、JITコンパイルされた後
ではwhileループとほぼ同等の性能になります。また、コレクションについても、概ね
同様です。
次に、私が現在、InfoQ Japanの翻訳記事について問題意識を持っている事を挙げさせていただき
ます。今回のように、元記事が技術的な誤りを持っているまたはcontraversialなもので、元記事で多数の反論コメントが寄せられている場合、Twitterなどの一部メディアでは、
読者は、日本語訳された記事のみを読み、元記事への反論コメントは読まれない傾向がしばしば
あります。これは、以前の翻訳記事「Scalaは新しいEJB 2か」において、私が観察した限りで
実際に見られた傾向です。
ただ、実際の所、翻訳される方には、英語のコメント全てに目を通して精査している時間も無い
かと思います。そこで、提案させていただきたいのですが、原文へのリンクと共に、原文に対するコメント欄へのリンクをコメント数と共に表示していただく事はできないでしょうか。原文に対して、コメントがある程度ついている事がわかれば、原文がcontraversialなものである事がより読者にわかりやすくなるのではないかと考えます。
以上、先ほどは一方的な投稿、失礼いたしました。
Re: InfoQ(英語版)のコメントが参照されにくいことによる問題提起と解決案
by Hanyuda Eiiti,
スパムの可能性があると認識されました。モデレーターが確認し問題がなければ24時間以内に公開します。その際あなたへの通知は行われませんのでご了承ください。
水島様
InfoQJapanチーフエディタの羽生田栄一です。
本翻訳記事に対して重要なご指摘と追加情報をいただきまして、ありがとうございます。水島さんもおっしゃられる通り、InfoQ記事は興味深い技術情報に関しての速報性を優先しております。そのため、今回のような状況が時折発生しますが、ぜひ本コメント欄などを有効活用して、翻訳記事においても深い情報交換ができる場を作っていきたいと思います。翻訳者や日本側エディタも気が付いた範囲で、翻訳本文への追記修正や本コメント欄を利用しての記事投稿後の追加情報などを載せていければと思います。
なお、水島さんからのご提案についてはすぐに実現できるかは未定ですが検討させていただきます。今回は投稿ありがとうございました。
for文のパフォーマンスについての追試結果
by 宏太 水島,
スパムの可能性があると認識されました。モデレーターが確認し問題がなければ24時間以内に公開します。その際あなたへの通知は行われませんのでご了承ください。
先ほどの投稿で、
> コードがJITコンパイルされる(これは、特にその部分が繰り返し実行
される場合に一般的な状況です)場合、forはwhileと同程度の性能になりますし
と書きましたが、これは少し勇み足でした。まず、Scala 2.8.2.final
(これはScala 2.8.X系列の最新メンテナンスリリースです)では、実際にforは
whileと同程度の性能でした。
d.hatena.ne.jp/chimerast/20110310/1299723622
のベンチマークコードを元に追試したところ、
|*micro sec|*.foreach|*.for|*.while|
|single|429|408|392|
|double|454|450|398|
|triple|736|758|472|
という結果になりました。ここで、triple(3重ループ)の場合、2倍近く遅くなって
いますが、これはinner loopにおけるオブジェクト確保が効いている可能性があります。
ともあれ、二重ループでも大差無い性能が出ているため、2.8.2に関しては問題ありません。
一方、Scala 2.9.1.finalで同じコードで測定したところ、
|*micro sec|*.foreach|*.for|*.while|
|single|1,084|1,104|401|
|double|5,637|1,152|388|
|triple|1,910|1,880|448|
という結果になりました。Scala 2.9.1.finalでは、通常のユースケースで、
forはwhileに比べて2~3倍程度遅いことになります。
元記事のベンチマークのやり方が適切でなく、実際の性能を反映していないという
結論には変わりありませんし、実用上forがこのくらい遅い事が問題になるケースは
多く無いでしょう。ただ、
> forはwhileと同程度の性能
は、Scala 2.9.1.finalにおいては正確ではなく、「forはwhileに比べて2~3倍程度
遅い」が正確ですので、訂正させていただきます。
Scala 2.9.1.finalでforの性能が遅くなっている理由は調査の必要がありますが、
Scala 2.9.0で並列コレクションが入ったので、そのための実装変更が原因になって
いる可能性はあります(これは推測ですが)。
Re: for文のパフォーマンスについての追試結果
by 宏太 水島,
スパムの可能性があると認識されました。モデレーターが確認し問題がなければ24時間以内に公開します。その際あなたへの通知は行われませんのでご了承ください。
なお、計測環境は、
CPU: Core i5-2430M 2.40GHz
OS: Windows 7 64bit (Home Premium)
Java実行環境: JRE1.6.0_29(server, 64 bit VM)
RAM: 8GB
となります。