yoskhdia’s diary

DDDとかプログラミングとかアーキテクチャとか雑多に

DDD座談会に際して素振りをしていた

ddd-zk.connpass.com

DDD座談会のパネラーとして登壇させていただきました。

素振り

予め参加者の方々からいただいた質問(テーマ)に対して、予行練習というか、ちょっと自分の考えを先にまとめていたのですが、予想通りテーマひとつひとつが盛り上がったので全部を話しきることができませんでした。 折角なのでここに公開しておきます。 ところどころ、座談会終わっての後書きも追記しています。

なお、あくまで私見ということでよろしくお願いします。座談会のまとめ記事というわけではありませんので、悪しからず。
各人のコンテキストで、こんな解釈あるよ等あると思いますので、Twitterでもコメントでもフィードバックいただければ幸いです。


続きを読む

false sharingの整理

マルチスレッド時代における意外なハマりどころfalse sharingについてまとめておきます。 参考にあげている書籍「Javaパフォーマンス」からのまとめです。

false sharingとは

例えば、以下の様なコードがあるとします。

public class DataHolder {
    public volatile long l1;
    public volatile long l2;
    public volatile long l3;
    public volatile long l4;
}

このとき、longは64bit = 8バイトのサイズですが、これらはメモリ上で近接して配置されます。 プログラムがl2を操作しようとすると、ある程度まとまったサイズのメモリが読み込まれます。(※2) 多くの場合、近接するインスタンス変数も操作するため、アクセスはとても高速にでき、パフォーマンスに貢献します。 しかし、複数のスレッド(コア)によって頻繁にアクセスする場合、異なる変数への操作であっても、キャッシュにまとまって載っているため、あるコアでの操作結果を他のコアに通知し、そのコアでは再読み込みを行う必要がでてきます。(volatile変数に書込を行うと、他のすべてのスレッドでキャッシュ上の値が無効化され、メモリから再読み込みが必要) このように意図せず 競合 干渉(※1)が発生し、パフォーマンスが大幅に劣化することをfalse sharingと呼びます。

※厳密には、false sharingは同期やvolatile変数とは無関係に発生しますが、Javaのメモリモデルではメインメモリへの書き出しは同期プリミティブ(CAS命令やvolatile変数も含む)の末尾でのみ行うと定められているため、最も頻繁に発生するのは同期に関連するケースとなります。


※1(2016.7.7追記)
"競合"というとプログラムが意図しない間違った動作を引き起こし性能劣化以外の影響があるように捉えられる、とご指摘いただいたので"干渉"という表現に変更いたしました。 性能劣化以外の影響をご存知の方がいらっしゃいましたら、教えていただけると幸いです。

(※1追記終)


※2(2016.7.8追記)
キャッシュ ライン の説明があった方が干渉の説明に繋がるとご指摘いただいたので補足します。
下記参考1で解説されている内容を抜粋します。

キャッシュでは,メイン・メモリを数バイトから数十バイトの「ライン(あるいはブロック)」という単位で管理します.具体的には,キャッシュとメイン・メモリの間のデータ転送,キャッシュ上にデータがあるかどうか(ヒット/ミス・ヒット)を,すべてラインの単位で管理します

(※2)の個所は、このキャッシュラインの単位でデータがメモリからキャッシュへ読み込まれることを指しています。 イメージとしては参考2の図がわかりやすいと思います。false sharingは各スレッドが同じ変数への アクセス を実際に共有していないのに、キャッシュラインの単位で共有している状態になっており、お互いのアクセスによってメモリからの再ロードを頻発させます。インテルガイドの日本語訳がありましたので末尾の参考に追記しました。
また、JEP 142の @Contended アノテーションも指摘されていたのですが、書籍にはこれにも言及があります。ただ、これは「JEPを経てはいますが、このアノテーションJVM内部での利用を主に想定しています。このアノテーションが今後のリリースで使われ続ける保証も、機能が変わらない保証もありません。」とあるためこの記事では省略していました。

参考1:今さら聞けないマルチプロセッサの基礎教えます ――キャッシュの共有,割り込みの共有,OSによる制御|Tech Village (テックビレッジ) / CQ出版株式会社(キャッシュライン)

参考2:今さら聞けないマルチプロセッサの基礎教えます ――キャッシュの共有,割り込みの共有,OSによる制御|Tech Village (テックビレッジ) / CQ出版株式会社(false sharingの図)

参考3:コンピュータアーキテクチャの話 (6) キャッシュの構造(基礎編) - どういう単位でキャッシュに入れるのか? | マイナビニュース

(ご指摘いただいてキャッシュラインについて改めて調べたのですが、自分の理解が曖昧だったことが分かりました...勉強になります)

(※2追記終)


対策

明確な対策はありません。

検出のためには、プロセッサのアーキテクチャに関する詳細な知識が必要で、また検出するツールもベンダーによって対応が異なるようです。(IntelだとVTuneというツールを公開している。)
ネイティブなプロファイラでは、指定されたコードについて命令ごとに必要なクロックサイクル数(CPI)を知ることができるため、多くのCPIが費やされているかがfalse sharingが発生している可能性を検知する材料になります。

通常false sharingが発生した場合、コードの変更が必要です。 大まかな方針は以下のとおり。
パディングは後述のとおり、期待する効果が得られるか微妙な面があるため、前者のローカル変数で処理をし、最後に同期処理でまとめてしまう方法をまずは検討した方が良いでしょう。

  • データをローカル変数へ移動して、書込は最後に行うようにする。
  • パディングを行って複数の変数が同じキャッシュに読み込まれないようにする。

パディング

CPUのキャッシュが128バイトだとしたら、以下のようなコードにします。

public class DataHolder {
    public volatile long l1;
    public long[] dummy1 = new long[128 / 8];
    public volatile long l2;
    public long[] dummy2 = new long[128 / 8];
    public volatile long l3;
    public long[] dummy3 = new long[128 / 8];
    public volatile long l4;
}

ただし、このようにしてしまうと、キャッシュのサイズはCPUごとに異なるため、異なるアーキテクチャへの移行難易度が上がることと、また、パディングした分、インスタンスサイズが増加するため、インスタンスの数によってはGCに悪影響を及ぼします。 さらに、JVMがこれらの変数を配列ごとやlongごとに並べ替えてしまい、期待通りに機能しない可能性もあります。 (その場合、プリミティブ型の変数を使ってパディングすることもできるが、大量の変数が必要になって現実的ではなくなってしまう。)

Actorモデルとfalse sharing

Actorモデル(Akkaを想定)を使う場合、false sharingはほとんど気にする必要はありません。 それは、対策の1点目のようにActorの持つローカル変数は1つのスレッドのみからアクセスすることになるため、意地悪なコードを無理やり書かない限りは競合しなくなるためです。(Actorの内部データは参照されることがなく、Actor間はメッセージによってやり取りされる。)

ただし、Actorそれ自体が同じキャッシュに載ってしまうという可能性は否定することができません。 (たとえ自分がローカル変数を持つようにプログラミングしなくても、Akkaを使っている場合Actorトレイトでローカル変数を持っているため。) しかし、この場合にfalse sharingが何故発生するのかまでは追えていません。 通常ActorRefを通してActorにはアクセスしますが、Actor System内でどのようにActorを管理しているかによって、false sharingが発生し得るのでしょうか。 ご存知の方がいらっしゃいましたら、教えていただけると幸いです。

(※追記) @TanUkkii007 さんが「Akkaはマルチスレッドプログラミングにおける可視性の問題をどう解決しているか」について書いてくれました。

github.com (※追記終)

参考

XLsoft エクセルソフト : インテル ガイド : 3-4 スレッド間のフォールス・シェアリングの回避と特定(2016.7.8追記)

Mechanical Sympathy: False Sharing

Mechanical Sympathy: False Sharing && Java 7

www.oreilly.co.jp

ドメイン駆動設計 第2章 ユビキタス言語を読みなおした

社内交流会でLTをする機会があったので「ユビキタス言語」についてDDD本を再度読みなおしてみました。

speakerdeck.com

最近、「DDDは負け犬」みたいな話が少しバズりましたが、ユビキタス言語=ユーザの言葉と解釈するのはあまりに勿体無いのではないかなと思います。 ユビキタス言語はより良い・深いモデルを探求するために必要なものです。

スライドの補足

第2章はスライドに書いたことよりも、もっと多くのことについて言及されています。 ここでは、それらの省略してしまった部分を補足しつつ、スライド構成の今ひとつだった部分を正したいと思います。

まず、第2章最初の一文

しなやかで知識豊富な設計を行うには、用途の幅広い、共有されたチームの言語と、その言葉を使った活発な実験が必要である。 – 書籍「ドメイン駆動設計」(p.24)

省略しようがないくらいに、この一文に詰まっているのですが、スライドでは活発な実験にフォーカスをあてたかったので説明では所々省略していました。順番に見ていきましょう。

その前に

まず設計とは何かを考えると、「ある解決したい問題に対して心のなかで持っているモデルを現実に動くもの(システムや機能)として作るために思考し、その結果を仕様や設計書などに落としこむ作業を指す」とここでは定義しておこうかなと思います。 この心のなかに持っているモデルは、視野・視点・視座が異なれば同じ問題であっても人によって描く姿が変わるものです。 DDD本から一部抜粋します。

ドメインエキスパートは、ソフトウェア開発における技術的な専門用語のことは部分的にしか理解せず、代わりに自分の得意分野の専門用語を使用する。それも、さまざまなニュアンスで使うだろう。(中略)ある問題に対して、それぞれ別の部分に取り組んでいる開発者たちは、自分たち独自の設計概念とドメインを記述する方法を、それぞれで考えだしてしまう。(p.24)

(中略)

同一人物でさえ話し言葉と書き言葉で違いがあるので、ドメインについての最も鋭い表現は、たいてい一時的なかたちでしか現れず、コードや文章ではとらえられることがない。通訳はコミュニケーションを鈍らせ、知識のかみ砕きを沈滞させる。(p.25)

二つの問題があるように思います。
一つは、何のモデルなのか、どの側面を見たモデルなのか、を共有できていない点。
もう一つは、共有できていても、言葉が厳密ではなく曖昧さを含んでいる点。

これらの問題があることを念頭に置いて、冒頭の一文に戻りましょう......

"しなやかで知識豊富な設計を行うには、"

DDDは、モデルとコードの距離を近づけて、変わり発展し続けるモデルに追随し、時にはコードからモデルへフィードバックすることを狙っています。 モデルとコードの距離を近づけるためには、コードも柔軟(しなやか)でなければいけません。 そうでなければ、モデルや言語の進化に追いつけず、システムが重荷となりビジネスにおけるシステムの占める位置が後退してしまうでしょう。ハードウェアやミドルウェアの進歩によるリプレースはあっても、モデルの進歩によるリプレースはコストが高くつくものです。

ドメイン駆動とは、問題を解決する(ドメイン)モデルをとらえることから始め、これをもって業務を噛み砕いて理解し、開発を行ってゆくものです。 モデルについては、これ以上は本筋から外れるためここまでとしますが、簡単にまとめると人によって描く解決策(モデル)が異なるため、そのモデルを共有し、かつ共有し続けることが大事ということです。 そして、そのために強固な共通言語(=ユビキタス言語)が必要とされています。 最終的にはこのモデルがコードとなって実現されますが、そのためには、モデルを理解する必要があります。ここでユビキタス言語が登場します。

"用途の幅広い、"

前段の強固なとはどういうことでしょうか。 私はこの「用途の幅広い」が相当するのだと解釈しています。 用途が幅広いとは、言い直すと「あらゆる場面で使うことができる」ということと思います。 それは開発者のなかだけでなく、業務に詳しい人とのコミュニケーションのなかにも現れます。

例えば、開発者の間でもコードだけでなく、タスクや機能を記述するときにも使用します。また、業務に詳しい人と開発者の間でも会話だけに留まらず文書でやり取りする際にも使用します。 そして、開発者でない人たちの間でも同じ言葉が使われ、チームの内から外へと波及していくことが理想とする世界のように思います。 ただし、DDDはあくまでより良い開発のためのものなので、少なくともチームのなか(開発者、ドメインエキスパート、デザイナ、etc)で翻訳のコストを下げることが主軸です。

同じ言葉を使うことそのものが大事なのではなく、翻訳のコストを下げる、ひいてはコードの適応性を高めることの方が肝要でしょう。

"共有されたチームの言語と、"

そうして、チームで共有された言語は維持し続けなければいけません。 今の瞬間に集まって言葉をすり合わせることはできますが、将来にわたってユビキタス言語を共有し続けることは、ユビキタス言語の意図を理解して尊重する意思が必要です。 とはいえ、ユビキタス言語だ!さぁやるぞ!と一人意気込んでも空回りすることも多々あるので、仲間を広げることが第一歩です。(この記事もその一環)

DDDにおけるユビキタス言語で難しいと感じるのは、それはユーザの言葉だけで完結しない点にあると思います。 本来、情報システムが何のためにあるかと考えれば、情報を処理するためであり、そこにはユーザの言葉だけでは不十分です。 我々開発者が設計を行うためという意識のもと、ドメインエキスパートと言葉を交換することが必要です。 これについては、以下の記事がとても勉強になります。

PHP Mentors -> 杉本 啓「2つのドメインモデル―DDDの含意」

業務を学び、システム・ビジネスに適した情報処理のモデルを探求する、ただの御用聞きではない開発のあり方が求められているのではないでしょうか。 ・・・というのは、一部建前で、私としては金を生み出さないものを作りたくないという点に集約されます。役に立ってるのか分からないものをつくるほど優しい人間ではないではないですし、そんなストレスはご免です。そのために、開発者もビジネスに寄与する主体となれるDDDに魅力を感じているのかもしれません。

"その言葉を使った活発な実験が必要である。"

始めから知識豊かなモデルをつくることは多くの場合難しいです。 そのため、継続的にモデルを実験し続けることが必要です。 この実験のためにユビキタス言語が使用されます。 DDD本から一部抜粋します。

知識をかみ砕くプロセスが、より役立つモデルを作り出せるかどうかは、モデルに基づく言語に対してチームが献身するかどうかにかかっている。粘り強くユビキタス言語を使用すれば、モデルの弱点は明らかにならざるを得ない。チームは実験を行って、用語や、用語の組み合わせがぎこちない場合に、その代わりとなる表現を探す。(p.26)

(中略)

モデルに基づく言語を広く使用し、妥協せずにそれがよどみなく流れるようにすることによって、我々は完全で包括的なモデルに近づいていくのである。こうしたモデルはシンプルな要素から成り立っており、この要素が結合して複雑な概念を表現するのだ。(p.26)

(中略)

システムについて語る際には、モデルをいろいろと試してみること。モデルの要素と相互作用を使い、モデルに許された方法で概念を結びつけながら、シナリオを声に出して描写すること。表現すべきことをより簡単に言う方法を見つけ、その新しい考え方を図とコードに再び反映させること。(p.32)

綺麗な設計書を書いたりするよりも、言葉を交わす方がコストは安いという面もありますが、言葉の有用性としてDDD本ではピジン語(通商用混合語)との比較をあげています。 ピジン語は異なる言語で育った人々が貿易のために集まった時、さしあたりの作業のために生み出す言語です。 会話をしていると、単語の解釈や意味が違っていることが自然に見つかり、その違いが自然に解消されていきます。 ユビキタス言語も同様に、議論で使用することによって、全員がその言語で話せるようになり、ニュアンスを互いに教え合うようになる、としています。

これをチームで約束すれば、図やドキュメント、会話のなかで問題をみつけ、代わりの表現を用いて実験することで、問題を取り除くことができるようになります。 更にDDD本から抜粋します。

ドメインエキスパートは、ドメインについての理解を伝えるには使いにくかったり不適切だったりする用語や構造に異議を唱えるべきであり、開発者は、設計を妨害することになるあいまいさや不整合に目を光らせるべきである。(p.27)

スライド中では、ユビキタス言語を通じてお互いを支え合うことに注目しました。 人間は誰しも思考の矛盾や曖昧さを抱えています。 チーム全員でユビキタス言語を使ってモデルに目を向けることで、これらの問題に向き合うことができるようになるのだと思います。 最後にもう一節DDD本から抜粋して終わりたいと思います。

ユビキタス言語を用いることによって、モデルは単なる設計成果物ではなくなり、開発者とドメインエキスパートが協同で行うすべてにおいて、欠かせないものとなる。また、ユビキタス言語は知識を生き生きとした形式で伝える。そうしたユビキタス言語を使って議論することによって、図とコードの背後にある意味に命が吹き込まれるのだ。(p.27)

(ここまでの内容は主に2章の前半であり、後半にも深い洞察がありますが、それらはまたの機会に。)

Scalaマクロへの誘いTweetまとめと補足

Scala 2.11時点でマクロはexperimentalな機能です。 将来のバージョンでは変わる可能性がある点にご注意ください。 将来的には scala.meta というプロジェクトに移るようです。 公開されているスライドを見ると、今のマクロ記述から新しい記述へはこう変わるという点が示されています。

と呟いたように、リフレクションをまず学ぶのが遠そうで実は近道だと思います。 Scalaのリフレクションもまだ改善の余地がある気がするのですが、scala.metaが出るともっと良くなるのでしょうか? Javaのリフレクションとは異なり、Scalaはコンパニオンオブジェクトやトレイトなどコンパイル時に解決されるものがあるため、Javaの感覚で理解しないままClassオブジェクトを使うのは危険らしいです。

他にはimplicitマクロや抽出子マクロがあるようですが、defマクロが一番基本だと思います。 implicitマクロはまだ使う機会がありそうかなと思えますが、ライブラリならともかくプロダクションコードでimplicitを多用するのも...という論もあり、implicit + macroの組み合わせはデバッグがしづらくなるデメリットの方が大きいんじゃないかなというところです。

展開するコードを書く場面では準クォートがあるので良いのですが、受け取ったTreeを分解する過程ではどんなオブジェクトがあるかを知らないと分解のしようがないので、準クォートの前にオブジェクトを学んでおいた方が良いです。 分解の仕方を知っておくと、例えば引数で渡される式を分解しながら、途中に特定のメソッドが呼ばれているかを判定するなどができるようになります。 (こういった使い方をするには、ユースケースを限定しないと複雑度が上がってしまうので難しいところですが...)

scala.metaになったら手を入れなきゃいけなくなりそうな雰囲気がありますので、マクロを使いたいならしっかりテストを書いておくことをオススメします。

補足

※補足4は間違って抜かしてしまっただけです。
Twitterのツイートをembedすると連ツイだと見難いですね...。