😊

[発表振り返り] よくわかるThe Go Memory Model for Go Conference 2023 Online

2023/06/03に公開

Go Conference 2023 OnlineのLong Sessionで発表させていただきました。この記事はその発表作りの振り返りです。

Go Conference 2023 Onlineでは参加者からのアンケートを募集しているそうです。ぜひ回答しましょう!

https://docs.google.com/forms/d/e/1FAIpQLSdXkRUI820tCvMATuhBsvFItIAoYAvcTg4LpSb4KFW3hXHcPw/viewform

発表資料

https://gocon.jp/2023/sessions/A3-L/

https://docs.google.com/presentation/d/1UjL5jTqreNrFpulVi6l_H5vY_Bcz9jQriL65gZs1zFM/edit?usp=sharing

概要

発表が出来上がるまでにあった出来事をカジュアルに振り返ります。重要なインプットや示唆を頂いたことに対するacknowedgementも兼ねています。

勢いで書いているので、大体時系列に書いているようで微妙に前後したりしています。

伝えたいことは特にないのですが、

  • 今後発表することに興味がある
  • Proposalをどう書いたか興味がある
  • Go Memory Modelの勉強方法に興味がある

方には参考になる要素もあるかもしれません。

#gospecreading

メモリーモデルとは直接関係ないながら外せないのがGo Language Specification輪読会です。

言語仕様書を精読することがこれほど楽しいとは思いませんでした。長期に渡り運営・企画と議論のファシリテートをしてくれた__@syumaiさんに感謝です。

本などを読んでいると焦ったくなってつい先を急いでしまうことがありがちだと思うのですが、この会は先を急ぐことよりもちゃんと納得して進むことに重きが置かれていて、それが自分に合っていました。

勉強会のアウトプットは大雑把には「問いかけの深さとそれに対する回答の質の最小値」になるイメージがあるのですが、この2つでより重要なのは「問いかけの深さ」だと考えていて、常連の方は皆さんその点に優れていました。

中でもこの「問いかけの深さ」について最大の貢献をされていたのはDQNEOさんだなと感じています。そこから学んだことは多く、何か書くときに「DQNEOさんならここを聞いてきそう」というようにイメージできるだけでも自己レビューの質が高まるという恩恵があります。

言語仕様書の精読をしていなければメモリーモデルも精読しようと思わなかった可能性も高いです。言語仕様書だけ読んでも「完全理解」じゃないじゃん、と思ったのがメモリーモデル読解の1つのモチベーションになっていたので。

横浜Go読書会

実は自分がメモリーモデルというドキュメントを初めて知ったのは結構前で、横浜Go読書会で題材として取り上げられていたのがきっかけでした。

発表で説明したように、逐次一貫性の成り立たない状況下においてはMessage Passing Testが「失敗」するというような直感的に理解しづらい現象が起きます。
これは発表で取り扱ったGo言語のような高級言語に限った話ではなく、アセンブリプログラムを現代的なハードウェア(CPU)で実行した際にも実際に起こる現象です。
つまり、アセンブリプログラムを実行するハードウェアにも「ハードウェアメモリーモデル」があり、その「ハードウェアメモリモデル」は逐次一貫性を保証しません。

これがなぜ起きるかというと、一つにはCPUのそれぞれのコアがキャッシュ機構を持っていることが関係していたり、CPUが命令流をより高速に実行するためのアウトオブオーダー実行というものが関係しているそうです。
関係しているそうなのですが、私はこの辺りをきちんと説明する能力がありません。おそらく次のような文献をちゃんと読むとよくわかりそうだと思っています:

https://www.lambdanote.com/products/cpu

横浜Go読書会にはこのあたりの技術に詳しい方が何名かおられたため上記のキャッシュ機構などの話について学ぶことができました。
一方でメモリーモデルを読み進めていくと、疑問が出てきました。「このドキュメントは読み手に対してハードウェアの知識を要求していないというか、むしろその部分の詳細を隠蔽した抽象的モデルを定義しているのではないか?」というものです。

この疑問は今思うと正しい疑問で、このドキュメントはプログラミングの一般的ドキュメントよりも、むしろ数学・物理学や理論計算機科学の文献を読むときに近い態度を読者に要求するものでした。
例を挙げると、オートマトンを学んだことのある方であればオートマトンの抽象的な定義を思い出したりするとイメージがつくと思います。ああいったものと同じような読み方が必要だと感じました。

さらに読み進めていくと例の"happens-before"関係と観測可能性が出てきました。発表ではhappens-before関係をグラフで図示することで説明しましたが、実はメモリーモデルそのものには図もグラフも出てこなくて、partial order(半順序)としてしか説明されていません。
読者はpartial orderがどういうものかについてすでに親しみがあって、図を出さなくても頭の中でそのpartial orderを表す図を描くことは簡単にできる、と仮定して書かれているように見えました。

自分は学生のときのバックグラウンドが物理系だったので、次の2つにすでに親しみがありました:

  • 詳細を度外視して単純化・抽象化した形式的モデルを作り、そのモデルから言えることを導出していくという考え方
  • 半順序という概念と、それがDAGで表せて、図示もできること

この親しみがあったので、メモリーモデル全体が何をしようとしているのかなんとなくわかったのですが、その場ではうまく説明ができませんでした。うまく説明ができなかったことにより、「これは説明する価値のあることなのだ」ということに気づいた最初のきっかけになったと思います。

なおこのときに話題になったプログラムについてはbudougumiさんがわかりやすくまとめてくださっています:

https://budougumi0617.github.io/2021/03/31/go-string-null-pointer-panic/

#godocreading

この頃は確かGo1.18が出てからGo1.19が出る前の時期で、Russ Cox氏によるメモリーモデル更新を提案するブログシリーズが出ていました。これを読むことを@__syumaiさんが提案してくださり読み始めたという流れだったかと思います。

この読書会もgospecreading同様充実していたのですが、Russ Cox氏のブログがとにかく素晴らしいです。これほど難しい内容をここまで易しく説明できるのかと感動しました。途中から流石に行間が広いように感じましたけど。

https://research.swtch.com/mm

Go1.19リリースパーティ

https://gocon.connpass.com/event/253355/

#godocreadingが始まってからGo1.19の新しいメモリーモデル(リリース前のもの)も個人的に読み始めていました。
そのタイミングでリリースパーティがあり、枠が空いていたので、発表させてもらおうと思って発表枠で応募しました。

勉強会で数人に発表

この時期に@tenntennさんを含む数人で勉強会をしていました。そこで作りかけの発表資料を使って私から発表させてもらって意見や質問をもらえるように参加者の皆さんにお願いしました。

このときのフィードバックで、happens-before関係をグラフ上の到達可能性(pathが取れるかどうか)で説明する方法を@tenntennさんからサジェストされました。
これにより面倒な半順序関係だの推移法則だのを言う必要がなくなり、本当に伝えたいことに集中した説明が可能になりました。

あとこの勉強会で↓の発表を教えていただいてこれが非常に役立ちました。メモリーモデル説明の方法にも参考にしています

https://www.infoq.com/presentations/go-race-detector/

Go Memory Model入門会

https://www.youtube.com/watch?v=5LHm0kA0ANE

リリースパーティの私の発表はアーカイブがないということもあり、その内容プラスいくつかコンテンツを追加して#godocreadingの入門用発表会をしませんかという提案を@__syumaiさんがしてくださり、実施しました。
syumaiさんがハードウェアメモリーモデルについて発表し、私がGoメモリーモデル入門のリメイクと「逐次一貫性」について発表しました。

この会はGoコミュニティ・Goユーザーではないがメモリーモデルに造詣のある方も何名かみてくださり、かなり深いコメントをいただけたりして面白かったのを覚えています。
また、Zoom参加していたtaskさんからいい質問をいただいていて、それが最終的にGoconfでの発表のシナリオに大きく影響しています。

あと、この会をやる前の時点では「自分は本当にメモリーモデルを正しく読解できているのだろうか、実は全くトンデモな間違いをしているのではないか」という一抹の不安があったのですが(何しろ詳しい人が少ない)、Goコミュニティ外の有識者にもみていただけて根本的な瑕疵の指摘などはなかったので、ある程度安心して良いのかなと思えてきました。

CfP応募

CfPが出ました。ちなみにCfPを出すっていうのは課金するとか募金するとかいうのがちょっと変なのと同じ感じで変らしいので、CfP応募と書いてます。

だそうと思ったネタの候補は2つありました:

  • The Go Memory Model
  • 実験でわかるMinimal Version Selection

プログラムの仕様は割と簡単に試せるのですが、MVSなどのGo modulesの仕様は実験が面倒なので細かい仕様がよくわからないままだなと思っていました。
#godocreadingでもmodulesのドキュメントを読んだのですがMVSの正常系的な普通のパターンはわかったけどエラー的な細かいケースは読み取れなくて、実験も大変なのでそのままスルーしてしまってました。
実験用のモジュールを自分で用意すれば細かいところも色々試せるので、そこを発表しつつ聴いた人が自分で試せるような環境も提供すれば面白いかなと思いました。

実はメモリーモデルをやりたくない理由もいくつかあって、

  • すでに2回発表してあったのでさらにやるべきなのか自信が持てなかった
  • 題材が難しすぎて間違ったことを言うリスクがかなりあった(特にatomicsとメモリーオーダリング周り)

のですが、atomicsに言及していないことに心残りがあったのと、Message Passing Testについて十分満足いく説明ができていなかったと思ったこと、またメモリーモデルをやるなら次回以降ではなく今回じゃないと自分がやる気にならないだろうと思ったのがメモリーモデルで応募する決め手になりました。

Long Sessionで応募したのはShort Sessionだと無理なことが入門会の経験から明らかだったためです。4つしかないLong Sessionを自分が担当するってけっこうプレッシャーだなあと後から思いました。

Proposalを書いた方法

Go conferenceのProposalは特にフォーマットが決まっていなかったので、どのようなフォーマットでPropsalを書くか少し迷いました。
私の場合、迷ったときはこのフォーマットで書くと決めている文章の型がひとつあるので、それを使うことにしました。SCQAというフォーマットで、「考える技術・各技術」の序盤に載っています。

https://www.diamond.co.jp/book/9784478490273.html

私はこのフォーマットが好きなので、それについて書いてみたいと思います。

※私はProposal審査の基準や価値観を全く把握していませんし、これが良い書き方だということではありません。あくまでも一例として参考に供したいと思います。

SCQAとは次の4つで文章を構成する型のことです:

  • Situation(状況)
  • Complication(複雑化)
  • Question(問いかけ)
  • Answer(問いかけに対する答え)

それぞれで書くべき内容は次のようなことです(やや私流の理解になっていると思います):

  • Situation(状況)では、読者と書き手との共通認識となる内容を確立・確認します。これは読者がすでに知っていることの場合もありますが、そうでないときもあります。そうでない場合は、この部分で共通認識にしたい内容を説明します。
  • Complication(複雑化)は、Situationを掻き回す、複雑化する要因を書きます。
  • Question(問いかけ)は、Compliationを踏まえて解くべき問題を定義します。
  • Answer(問いかけに対する答え)は、Questionに対する答えを書きます。

私の場合は、それぞれ次のように当てはめてProposalを書きました。

  • Situation: The Go Memory Modelは基本的で重要なドキュメントである
  • Complicatoin: しかし、これを読むのはとても難しい
  • Question: どうすれば読めるか?
  • Answer: 図解によって行間を埋めれば読めるようになる

発表準備

結構長らくモチベーションが上がらずだったのですが、初期に次のようなマインドマップ的なものを書いたりしてました。

memory_model_mindmap

ただここからあまり手が進まなかったです。要因として、

  • 観測可能性まではすでに説明したことがあるのでモチベが微妙だった
  • 一方まだ説明したことのないatomicsは自分の理解が不完全で勉強から始めないといけなかった。そのタスクの分解をしてなかった。

といったことがあります。

結局、前半のMessage Passing Testの取り扱いについて新しい説明のストーリーを思いついた(後述)ことがモチベ回復のきっかけになったかもしれません。

Rust Atomics and Locks

この頃次の書籍がWebで無料で読めるようになりました:

https://marabos.nl/atomics/

この時期はまだSeqCstではないatomicsについても言及する気でいた(Go言語のatomicsはSecCstだがそれが唯一のatomicsではない)のでより緩やかなatomicsのメモリーモデル的な定式化も理解しておきたいと思っていたのですが、発表時間的に40分では無理だと思ってそこの優先度がさがりました。
AcqRelは何となくわかった気がするのですがまだ心許ないです...

ストーリーの再構成

必要な知識は概ねそろっているものの、前回の入門Go Memory Model輪読会の内容そのままで良いとは思っていなかったので、ストーリーの再構成をどうするかつらつら考えていました。

https://twitter.com/yohhoy/status/1645744892696723457?s=20

C/C++の言語仕様(メモリーモデル含む)に詳しい@yohhoyさんがリプライをくださり、これがきっかけで発表のストーリーができました。

つまり、

  • 「素直に考えるプログラマーは知らず知らずのうちに逐次一貫的に考えている」
  • 「逐次一貫的な考えではうまくいかない状況が存在する」
  • 「それをメモリーモデルで説明する」

というストーリーです。

J.J. サクライの書き出しを思い出す

皆さんはJ.J. サクライの「現代の量子力学」を読んだことがありますか?私はありません。

https://www.amazon.co.jp/現代の量子力学-上-第3版-物理学叢書112-J-J-サクライ/dp/4842703768

しかし、書き出しがとにかく印象的でそれだけは覚えています。

我々は量子力学を歴史的順序に沿って説明する代わりに,古典的概念を根底から覆す一例から始めるとしよう.最初にショック療法を受けることによって,読者はいわゆる“量子力学的考え方”に早くから慣れていけるであろう

上記のストーリーの再構成が思いついたとき、これを連想しました。つまり、「逐次一貫的概念を根底から覆す一例(Message Passing Test)から始めるとしよう. 最初にショック療法を受けることによって, 読者はいわゆる"メモリーモデル的考え方"に早くから慣れていけるであろう」というわけです。これを発表でやってみようかなと思いました。

スライド作成

スライド作成にあたっては、私の中でレクチャー系プレゼンの理想と思っている早水先生のグラフ理論Youtube講義シリーズのクオリティーに少しでも近づけようと思いました。

https://www.youtube.com/watch?v=hRJRaIP6VGk

そこで講義を見直そうとしてみたら、その早水先生が「プレゼン資料の作り方」についてプレゼンしているというありがたすぎる事実に気づきました。そこでそれをみて書かれていることを最低1つは今回のスライドに反映させようと思いました。

https://twitter.com/shino_nobishii/status/1652915109826793472?s=20

ask the speakersでいただいた質問など

大変重要な質問をいただいたので回答と一緒に共有します

race detectorを使えばメモリーモデルを読まなくていいというのはなぜですか?

data raceのないプログラムであれば逐次一貫的なモデルで説明がつくからです。

race detectorを使っていてテストのカバレッジが十分であれば、プロダクトコードがdata raceを発生させていないことにある程度自信が持てます。
data raceを発生させないプログラムは逐次一貫モデルに従うので、メモリーモデルの詳細を理解することなく、「すべての演算が何らかの順序で一列に並べられて実行されたもの」としてすべての挙動を説明できます。これはプログラマーが並行処理を考えるときの「素直な」考え方そのままで良いということです。
よって、race detectorがうまく使えていれば、happens-beforeとか観測可能性とかを考える必要はありません。

race detectorにfalse positiveはありますか?

いいえ、ありません。(あったらrace detectorのバグです)

https://go.dev/doc/articles/race_detector

とはいえこちらにははっきりそうとは書いていないですね。

仕組みは↓の発表がわかりやすいです(英語)
この仕組みから考えてfalse-positiveはないはずですし、発表内でもそう言われています。

https://www.infoq.com/presentations/go-race-detector/

発表で使われていた図の描き方は一般的なものですか?

はい。

メモリーモデルという分野についていうと他にも例があって、Rust Atomics And Locksで使われていたりします。

https://marabos.nl/atomics/memory-ordering.html#spawning-and-joining

より一般的に、happens-before関係というのは数学では順序関係というものの一種として理解されます。そして、順序関係をこのような図で表現することもよく行われます。

https://ja.wikipedia.org/wiki/ハッセ図

Next step?

ジェネリクス型推論の仕様が結構変わるのと自分以外それを日本語で説明しそうな人がいないのでやりたさはあるのですが、そもそも変更前の仕様が知られていなさそうすぎる...

最後に

何人かの方から話を聞いて、Go Conferenceが今の盛り上がりとクオリティを持っているのは決して当たり前のことではないという実感を深めました。
このイベントに関わられた・関わったことのあるすべての方に感謝申し上げます。

GitHubで編集を提案

Discussion