小中学生のプログラミング表現は多様化・実用化の時代に。2017年の全国小中学生プログラミング大会グランプリは?


エデュケーション@プログラミング+
第9回

第2回 全国小中学生プログラミング大会 (JJPC) 最終審査&表彰式レポート

2017年12月08日 19時00分更新

文● 杉本 敏則/ASCII

 2017年11月26日(日)東京・飯田橋にて、小中学生(6歳~15歳)を対象としたプログラミングコンテスト「第2回 全国小中学生プログラミング大会」(主催:全国小中学生プログラミング大会実行委員会=株式会社角川アスキー総合研究所、株式会社UEI、NPO法人CANVAS/共催:株式会社朝日新聞社/後援:文部科学省、総務省、経済産業省)の最終審査&表彰式が開催された。本イベントレポートでは、当日の最終審査に残った入賞作品10点の紹介や、表彰式およびその一環でおこなわれた実行委員会のメンバーによる座談会の様子などをお届けする。

昨年度を上回る167点の作品が集まったコンテスト

 この大会は、プログラミング教育の義務化を見据え、全国の小中学生にプログラミングで表現する場を提供することにより、学ぶ強い動機づけとなることを目的として昨年から開催されているもの。今年度の作品テーマは「こんなのあったらいいな」で、募集期間(2017年8月1日から9月15日まで)に全国各地の小中学生から167点の作品(本レポート末尾に作者の学年など応募内訳データを記載)が寄せられ、昨年度を上回る作品数が選考の対象となった。

全国小中学生プログラミング大会(通称:JJPC)の大会ロゴ。

 当日のイベントは、大会実行委員長である稲見昌彦氏(東京大学先端科学技術研究センター教授)の開会宣言からスタート。稲見氏は「今回のテーマである “こんなのあったらいいな” は、まさしく私が研究に取り組むモチベーションそのもの。プログラミングするというのは、コンピューターで何かを作るという意味に限らず、世の中の仕組みや人々の考え方、行動をプログラミングするということも当てはまる。こうした大会を活用して、単一化や標準化ではない色々な可能性を持った未来を、是非多くの子どもたちに作り出してほしい」と期待の弁を述べた。

イベント開始前に応募者である小中学生たちと直接やり取りしながら、各作品の特徴を確認する稲見氏(写真中央左)。

 最終審査&表彰式と題されたこの日のイベントでは、事前審査によって選ばれた10の入賞作品について、応募者自身がブース展示をおこない審査員に直接アピールをおこなった。各審査員は実際に作品を確認し、作者一人ひとりに直接質問したうえで、最終審査を実施した。

最終審査に残った入賞作品10点の紹介&作者へのインタビュー

 イベント当日、入賞作品の応募者(以降、作者)となる小中学生たちは、思い思いの工夫をこらしたブース展示をおこない、審査員に対してだけでなく会場を訪れた取材陣や関係者にも、自身の作品について積極的なプレゼンテーションをおこなっていた。ここで、その入賞作品10点と作者それぞれの様子を、学年順にご紹介(グループ応募の場合は年長者の学年を記載)する。

『とうちゃんおこしロボ』崎山 盛一(サキヤマ セイイチ)さん(小学1年)

ドアノックするようにお父さんの体を叩くための長い手が印象的な作品。

 ブロックでかたちを組み立て、プログラミングで動きを与えられるロボットプログラミングキット “アーテックロボ (Studuino)” を用いて作られた、その名の通りお父さんを起こすためのロボット。「3回くらい自分が乗らないと起きてくれないお父さん」をロボットがアームでトントンと叩いて起床をうながすという、非常に可愛らしくも、身近な「こんなのあったらいいな」を解決するために生まれた作品だ。

審査員にアピールをおこなう作者の崎山盛一さん。

 作者の崎山盛一さんにお話を聞くと、「どんな作品にするか決めるまでは時間が掛かったが、作り出したら3日程度で完成できた」とのこと。ロボット作りがとても好きな様子で、レゴブロックを使ったロボットを1日に4種類も作って過ごすこともあるそう。これからもプログラミングを続けて、ゲームづくりにも挑戦してみたいと今後の抱負を語ってくれた。

『あなたのバーチャルアシスタント・ロボット”NOYBO”』森谷 頼安(モリヤ ライアン)さん(小学3年)

画面に映されているバーチャルアシスタント機能をプログラムされたロボットキャラクターと、作者の森谷頼安さん。

 「あなたがパソコンを使っているときに、ついつい忘れてしまいそうな用事を教えてくれる」バーチャルロボット。子ども向けプログラミング言語として幅広く活用されているScratch(スクラッチ)で制作された作品だ。制作のきっかけになったのは、Appleの音声アシスタント機能・Siriを面白い!と感じたこと。ただ、Siriには顔がなくて寂しいと思い、NOYBO(ノイボ)というキャラクターを作り、今の時刻や次の予定、電卓の計算結果、しりとりなどのミニゲームも、声で教えてくれるようにプログラミングしたのだという。画面上のキャラクターが話す声は、作者があ、い、う、え……と一言ずつ声を吹き込んだもの。一定時間入力がないと、ロボットが本を読み始めて待機するといった工夫も施されている。

NOYBOが持っている多数のアシスタント機能を実際に操作しながらアピールした。

 作者の森谷頼安さんに普段どんな毎日を過ごしているか聞くと、プログラミングだけでなく水泳やダンスにも取り組むなど、とても忙しい様子。こうした忙しさのなかでつい忘れてしまうことを、楽しく教えてくれるアシスタントがほしいという「こんなのあったらいいな」が、作品制作のもうひとつのきっかけだと教えてくれた。最近では、Appleのアプリケーション開発環境であるXcodeや、世界中の開発者から支持されているゲーム開発環境であるUnityを使ったプログラミングも始めているとのこと。将来の夢をたずねてみると「指示された内容をプログラミングするだけの仕事ではなく、自由に考えて表現できるような仕事に就きたい」と話してくれた。

『毎日チェックアプリ』大竹 悠太(オオタケ ユウタ)さん(小学4年)

小学4年生が作ったとは思えない実用度を持つ備忘録アプリ。ウェブブラウザのローカルストレージにデータを保存できるよう頑張ったのだという。

 ウェブ開発に欠かせないプログラミング言語・JavaScriptで制作されたToDo(備忘録)アプリ。ウェブブラウザのChromeがインストールされていれば、スマートフォンやタブレット、PCと幅広いデバイスで利用できる(実際のアプリはこちら)もの。「宿題したの?など、お父さんやお母さんからよく注意を受けるから、自分がやることをきちんとチェックできるように」という理由から作ったのだそう。実際に毎日使うことで、ご両親も「やっていることを確認しやすくなった」と、実用的であるという感想を述べられた。

作者の大竹悠太さん。

 「制作期間は3ヵ月ほぼ毎日」と教えてくれた、作者の大竹悠太さん。今回使用したJavaScriptを学び始めた当初は「大変でなにがなんだか分からなかった」が、2ヵ月程度取り組んでみてずいぶん使いこなせるようになったそう。プログラミング歴は約4年で、過去にはScratchで冒険RPGゲームなどを作った経歴もあるとのこと。これからやってみたいことを聞くと、始めたばかりのUnityでゲームづくりにチャレンジしたいそうで、将来はゲームクリエイターになってみたいと語ってくれた。

『僕のドラえもん』蓼沼 諒也(タテヌマ リョウヤ)さん(小学5年)

迷路そのものの形や、神経回路が走るような動作から、見た目にも人工知能をイメージしている点が分かりやすい作品。

 「簡単な仕組みを応用できれば、大好きなドラえもんを僕にも作れるかもしれない」というきっかけから制作された、人間の脳をイメージさせる迷路におけるゴールまでの最短経路を探索するというプログラム作品。作品の着想は、単細胞生物の粘菌が迷路を解けることを紹介した児童書『かしこい単細胞 粘菌(福音館書店)』から得たのだそう。また制作面ではシミュレーション計算モデルのひとつである “セル・オートマトン” に関するウェブの記事をお母さんと見つけて参考にしたとのこと。ゴールまでの全経路を神経が走ることで自動探索し、ゴール到達後に今度はスタート地点へ最短経路だけが巻き戻りながら示されていくという、見た目からも内容が伝わりやすい作品である。

作者の蓼沼諒也さん。当日はソースコードだけでなく、参考にした資料も分かりやすく展示していた。

 今回の作品で使用されたのは、学びやすさと奥深さを兼ね備えているビジュアルプログラミング言語・ビスケット。当日は作品のソースコード(ビスケットの場合、言葉ではなくメガネのモチーフがそれにあたる)を印刷したものが展示されており、どのような内容のプログラムなのか丁寧に解説されていた。作者の蓼沼諒也さんは第1回大会でもプログラム作品が最終審査に選出されており、2年連続の入賞。今後は「迷路そのものを生み出すプログラムを作ったうえで最短経路を導き出せる作品にしたい」と、さらなる向上を目指していることを教えてくれた。

『回一首(まわりっしゅ)』菅野 晄(スガノ ヒカリ)さん(小学5年)

画面下から上へと移動するひらがなにボールが引っかかり、画面上部の赤いブロックで示された境界線を越えるとゲームオーバーになる(画像:作者の応募時資料より)。

 「百人一首を楽しく覚えられるようになりたい」と制作されたゲーム作品。歌詠み音声(作者自身の声だとのこと)が流れると同時に、ひと文字(ひらがな)ずつ歌を構成する言葉(オブジェクト)が画面の下から上へと回転しながら浮かんでくる(いわゆる落ちゲーとは逆の動き)。各文字が形として持つ “くぼみ” や “引っかかり” に、画面に表示されているボール(玉)が入り込み、画面上部までボールが到達してしまうとゲームオーバーとなる。うまく文字を避けながらボールを操作するには、次にどの文字が来るかを百人一首を覚えて予想できるようになることがコツ、というテーマを持っている。様々な機能があるなかで「海外の留学生にも楽しんでもらえるように」と、英語版も用意されている点が印象的な作品だ。

作者の菅野 晄さん。後に紹介する『narratica(ナラティカ)』の作者・菅野 楓さんと姉妹揃っての入賞となった。

 作者の菅野 晄さんにお話を聞くと、半年の制作期間の後、同じくらいの時間を掛けて作品をアップデートさせたとのこと。「スリルを感じられるゲーム性なら楽しく百人一首を覚えられそう」と、文字を回転させるなど様々な工夫を考え出した。これまでのプログラミング歴は、Scratchからスタートし、ウェブ開発をHTMLやJavaScriptを通じて経験。その後、Microsoftが開発した言語・C#や、今回の作品で使用したUnityを経て、現在はAppleが開発した言語・Swiftを頑張っているという。ちなみに「いちばん好きな百人一首の歌は?」の質問には、自身の名前である “ひかり” という言葉が唯一使われている『ひさかたの光のどけき春の日に 静心なく花の散るらむ』だと嬉しそうに答えてくれた。

『応援ロボ Maria』kohacraft.comさん(小学6年)

写真中央左に映るのが作品となる応援ロボ。工作やプログラミングの工夫を紹介する手書き資料も多数展示されていた。

 近くに物があると反応する近接センサーを活用して、勉強しているときや疲れているときに、声とダンスで応援してくれるロボット作品。液晶画面も付いていて、画像やメッセージでも応援してくれる。作者は、第1回大会でグランプリを受賞した、お兄さんと姉さん2人の3人グループだ。見た目のデザインや機能のアイデア出しは妹さん2人の担当。「チアガールが好きで、応援してもらえたら頑張れそう」と制作アイデアを出し、特にMariaの顔をどんな表情にするか、何度も作り直して工夫したのだと教えてくれた。

昨年度の参加に引き続き、今年度も兄妹仲良く作品をアピールした。

 いっぽう、プログラミングなどの実装担当はお兄さん。電子工作でよく活用されているArduino(アルドゥイーノ)でプログラミングされている。妹さん2人が「こんな機能も追加してほしい」と出してくる様々なアイデアに、あまり高性能ではない機材で応える必要が生じたため、「コンピューターにとって分かりやすいプログラムに変換する」目的からProcessingという言語を組み合わせる工夫をしたのだという。その結果、Mariaは両手両足を自由に動かせるだけでなく、音声と文字や画像の出力も可能になったとのこと。「最初はもっとシンプルで、ロボットが手足を動かせるだけだったんだけど……」と言いながらも、妹さん2人の要望に技術で応えるお兄さんの優しさが印象的だった。

『キラキラミュージックBOX』平野 正太郎(ヒラノ ショウタロウ)さん(小学6年)

LEDの放つやわらかな光の表現が印象的で、且つ独立して動作するという完成度の高さが際立った作品だ。

 「ないものは自分で作っちゃう。だからこのゲームもArduinoで作った」という明快な理由から作られた、光と音に合わせてタイミングよくボタンを押して楽しめるゲーム機。今回応募されたのは “バージョン4” となる改良版。これまでたくさんの人に遊んでもらって、アップデートを重ねてきた力作だ。

作者の平野正太郎さん。展示中もゲーム機のメンテナンスを自分1人でこなしていた。

 「具体的なアップデートの内容は?」という質問に作者の平野正太郎さんが教えてくれたのは、スピーカーの音を聴き取りやすく/ボタンを押す場所を示す光の発生箇所をランダムに/小さなお子さんでも遊びやすいように音楽のスピードを変えられるように/各種の設定を遊ぶ人が分かりやすく調整できるように、など、徹底して「どうすればもっと楽しく遊んでもらえるか」というユーザー目線に立った内容ばかり。将来は「人の役に立つような便利なロボットが作りたい」と抱負も語ってくれた。

『ツンデレ貯金箱』三重っ張りチルドレンさん(中学1年)

貯金箱の中央に付けられた画面に、不機嫌そうな表情とともに「マア、ショセンコンナモンカ。ビミョー」といった、貯金をうながすようなツンデレセリフが表示される。

 貯金箱に表情を示す画面が付いており、目標金額へ向けてお金を入れないと不機嫌な表情が表示され、貯金箱を喜ばせるために貯金を頑張る必要がある、というユーモアな作品。「ただの貯金箱じゃつまらないし、ちょっと便利なものでも “ただ便利なだけやん!” と思っちゃうから」というのが、ツンデレというアイデアを生んだのだそう。こどもパソコンであるIchigoJam、およびBASIC言語が使用されている。制作したのは、同じ三重県内でではあるものの、普段は車で2時間以上の距離に暮らしている男子2人の遠距離コンビ。2人はプログラミングと電子工作をそれぞれ分けて担当しており、互いの進捗を揃えないと制作が進められないため、Facebookのメッセンジャー機能などでやり取りをしながら完成までたどり着いたのだという。

さながら漫才コンビのような絶妙な掛け合いを見せてくれた、作者である三重っ張りチルドレンの2人。

 工作面で工夫したのは、入れた硬貨の種類をセンサーが選別できるように、硬貨がセンサーのある各小部屋へ正しくすべり落ちて行く「黄金比じゃないけど、それ的な絶妙な角度」のスロープ(すべり台)部分。サイズの似ている5円玉と50円玉を区別する点についても試行錯誤を重ねたとのこと。いっぽうプログラミング面の工夫としては、「貯金箱の実物が自分の手元にない状況でセンサーを設計するために、仕組みを再現して開発するための方法を思い付くまでが大変だった」と、遠距離でペア開発を進める2人ならではの苦労を語ってくれた。今後作ってみたいものとしては、2人揃って「今あるものを面白おかしくすることで、課題が解決できるような作品」とのことで、2人のコンビも「一生続くんじゃないかな(笑)」と強い結束を言葉で表現してくれた。

『金魚まもる君』野口 航(ノグチ ワタル)さん(中学2年)

実際に水槽を持ち込んで作品が展示されていた。ただし金魚は「自宅でお留守番してます」とのこと。

 金魚が暮らしている水槽内の状況を、水温:温度センサー/水のにごり具合:照度センサーとLED/水槽内のpH(ペーハー):pHセンサーと複数のセンサーでモニタリングし、その数値をブラウザ上に表示するという、ハードとソフトを組み合わせて開発された作品。家族のなかで金魚の世話を担当している作者のお母さんの負担を減らしたいという思いが制作のきっかけになったという。CPUの冷却などにも用いられる “ペルチェ(ペルティエ)素子” という電子部品を採用したことで水槽内の水温を調整できる点や、ブラウザにセンサーからの情報を数値だけでなくグラフやメーターで分かりやすく表示させた点などが、工夫したポイントとのことだ。

審査員に作品をアピールする、作者の野口 航さん。ハードウェアとソフトウェアのスムーズな連動が作品の大きな魅力だ。

 主にArduinoを用いてこの作品を制作した野口 航さんに話を聞くと、プログラミングと電子工作について、別々に取り組んだ経験はあるが、それらを組み合わせた作品制作は今回が初めてだという。現在興味を持って勉強しているのは3D関連の開発。「今はなかなか難しいけど、3Dを活用するプログラマーに将来はなりたい」と今後の夢も語ってくれた。

『narratica(ナラティカ)』菅野 楓(スガノ カエデ)さん(中学2年)

映画などの脚本における登場人物の感情をグラフ化し、様々なストーリーで比較してみると、優れた脚本にはストーリー全体における緩急のバランスに “1:2:1” という共通した比率が見出だせるのだという。

 自然言語処理を用いて映画やドラマの脚本を解析し、登場人物の感情をグラフ化することで「面白いお話の構成とはどういうことなのか」を調べるソフトウェア作品。オーソドックスな物語の展開があることは知っていたが、映画『君の名は。』における登場人物の感情グラフ(下記の新海 誠氏によるツイートを参照)とオーソドックスなそれは全く波形が異なっており、「面白いストーリーにはいくつかのパターンがあるのでは?」という自分の問いを確かめるためにこの作品を制作したのだという。

多数の関係者へ向けて堂々と作品をアピールする、作者の菅野 楓さん(写真右)。

 制作プロセスについて作者の菅野 楓さんに質問すると、言葉の処理をおこなうための手法である形態素解析についてはゼロからのスタートで、大学の先生に意見を求めに行くなどして学んだとのこと。苦労した点としては「日本語によくある “主語がない文章” について、どうすればそれを話した登場人物に正しく割り振れるか」だったそう。この点は、主語を持つ前述の文章を参照することで解決できたと教えてくれた。ちなみに自身が物語の作り方(構成)に関して興味を持った際に「アニメ監督の宮崎 駿さんは宮沢賢治の影響を受けているらしい」と聞き、それを確かめるべく宮崎監督本人に話を聞くため、実際にスタジオジブリへ足を運んだ(そして宮崎監督と話せた!)のだという。自らの考えを形にするために、識者へ意見を求めたり、プログラミングという手法を学んでみたりと、具体的にアクションを起こしていく力強さが印象的だった。

実行委員による座談会:AI時代の教育と地域への広がり

 以上ご紹介した10点の作品について、審査員が最終審査をおこなっている時間を利用して、会場では応募者がステージに登壇し、客席およびニコニコ生放送視聴者へ向けた作品紹介が実施された。当日のイベント司会を務めた吉田尚記氏(ニッポン放送アナウンサー)と小中学生たちの軽快なやり取りで、会場は大きな盛り上がりを見せた。

作者1人1人に対して吉田アナウンサー(写真右)から時には技術的な質問も交えられつつ、インタビュー形式で作品紹介の時間が設けられた。

 作品紹介に続いて、大会の実行委員を務める、遠藤 諭氏(株式会社角川アスキー総合研究所 取締役主席研究員)・清水 亮氏(株式会社UEI代表取締役社長兼CEO)・石戸奈々子氏(NPO法人CANVAS理事長)と、司会の吉田アナウンサーが登壇しての座談会「AI時代の教育と地域への広がり」がおこなわれた。

AI時代の教育は何を目的にするか/教育現場でのデジタル活用は今後どうなる?

座談会がおこなわれたステージの様子。

 遠藤氏の「AI時代の教育とは?」という質問に、清水氏は「プログラミング教育の目的としてロジカルシンキング的な能力を獲得するというイメージが先行していた時期から、ディープラーニングやAIが登場した現在、教育における賢さや知性、知能の定義・基準をもう一度考え直す時期へと、教育の目的も変化していると言えるのでは。AlphaGo(アルファ碁)が棋士の直感をロジックで打ち負かしたわけだから、ロジカルな能力で人間を上回るコンピューターを使いこなすために、プログラミングが必要だという話なのではないでしょうか」とコメントした。

大会実行委員を務める、清水氏(写真左)と石戸氏(写真右)。

 石戸氏からは「保護者や教師の方々からのAIに関する感想は漠然とした不安を感じさせる声が大半。大人でも理解するのは難しい題材だが、AIがどういうものかの具体的なイメージを伝えることは必要」という教育現場の状況が紹介された。また前述の状況を踏まえ「新しいデジタル文化やデバイスについて、最初は教育面で否定的な保護者の反応が多くても、保護者の世代自体が入れ替わることで状況はこれまでも変化してきた。この歴史を踏まえれば教育現場へのIoT導入も今後おそらく進んでいくだろうが、結局はそれを何に用いるかが重要」と、プログラミングやデジタルを教育で活用するうえでの本質的な目的を追求することの重要性を石戸氏は述べた。

プログラミング教育を進めるにあたり、地域における情報差を改善していく取り組みが重要

実行委員会の事務局長を務める遠藤氏。座談会では主にモデレーター役を務めた。

 プログラミング教育自体の地域への広がりについてテーマが移ると、まず石戸氏から「プログラミング教育の必修化を見据え、多数のプログラミング教室が出てきたが、とは言えまだ首都圏が多い。住んでいる地域で得られる知識に差が生じないように、各地域へ出向いてワークショップなどを開催し、プログラミング教育を届けるようにしている」と、NPO法人CANVASの取り組みについて紹介があった。この点に関する吉田アナウンサーの「距離に関わらず情報や知識にアクセスできるのがネットの強みですよね?」という意見に対して、石戸氏が披露したのは「個々人が限りなく無料に近いコストで、ネットを通じて自らのペースで学べるような時代における、学びとは何か?学校とは何か?ということが問い直される時期に来ている」という考え。ここでも、冒頭の清水氏による考察とも近い「知性や学びの定義そのものを見直す」という点が触れられたのが印象に残った。

 ここから話題は、学校での学びだけに限定されない学びの在り方など、広い社会を巻き込んだ “教育のリ・デザイン” といった点に関する意見交換へと展開。話は尽きないものの、タイムリミットを迎えて座談会は終了した。

グランプリ/総務大臣賞は小5の「自分だけの人工知能」を目指した作品に!!

 応募者の作品発表と座談会を経て、いよいよ表彰式。今年度の審査基準である「発想力」、「表現力」、「技術力」の観点から、最終審査を経て下記のような結果で各賞の授与がおこなわれた。

グランプリ/総務大臣賞 僕のドラえもん 蓼沼 諒也 小学5年
準グランプリ narratica(ナラティカ) 菅野 楓 中学2年
準グランプリ キラキラミュージックBOX 平野 正太郎 小学6年
優秀賞・中学校部門 ツンデレ貯金箱 三重っ張りチルドレン 中学1年
優秀賞・小学校高学年部門 回一首(まわりっしゅ) 菅野 晄 小学5年
優秀賞・小学校低学年部門 あなたのバーチャルアシスタント・ロボット”NOYBO” 森谷 頼安 小学3年
イシダ賞
(株式会社イシダ提供)
応援ロボ Maria kohacraft.com 小学6年
入選 金魚まもる君 野口 航 中学2年
入選 毎日チェックアプリ 大竹 悠太 小学4年
入選 とうちゃんおこしロボ 崎山 盛一 小学1年

 第2回のグランプリである総務大臣賞には、小学5年・蓼沼諒也さんの作品『僕のドラえもん』が輝いた。粘菌の動きという自然界のアルゴリズム(ネイチャー・テクノロジー)とプログラミングを組み合せた発想が、今後のプログラミング教育普及における指針を示すうえでの好例だとして、グランプリ/総務大臣賞に選ばれた。

グランプリに輝いた蓼沼諒也さん。「グランプリになるとは思っていなかったのでめちゃくちゃ嬉しいです。コンピューターも粘菌も両方好き。(将来、何になりたい?の質問に)すごい人かな」と、恥ずかしそうにしながらも喜びを述べた。

 準グランプリには2作品が選ばれた。準グランプリ1作目は、中学2年・菅野 楓さんの作品『narratica(ナラティカ)』。すでに存在している課題を解決するためにプログラミングを用いるというのではなく、問いそのものを作り出し、何が課題であるかを見つけ出すために、プログラミングを活用するという動機から、大人顔負けの作品を完成させた点が評価された。

準グランプリ受賞の菅野 楓さん。「嬉しいです。これからも頑張ってnarraticaをもっと発展させた作品にしたいと思った。将来はちゃんと世の中の人に役立つようなサービスを作れるようになりたいです」と今後の抱負も語った。

 準グランプリ2作目は、小学6年・平野正太郎さんの作品『キラキラミュージックBOX』。ゲーム作品としての完成度の高さもさることながら、電子工作技術の面でも内部が非常によく考察されて作られていた点が、審査員から高く評価された。受賞の喜びを壇上で “スキップ” しながら体の動きで表現した平野さんの様子も印象的だった。

準グランプリに輝いた平野正太郎さん。「実は1位になれるとイベント前はポジティブに考えていたけど、みんなの発表がすごくて諦めかけていた。一言であらわすなら、今の喜びをプログラミングで表現したいくらい嬉しいです」と受賞の感想を教えてくれた。

 なお、すべての受賞者に賞状授与のうえ、グランプリには盾と副賞「MacBook Pro 13インチモデル」、準グランプリには副賞・ノートパソコン「HP Spectre x360」、優秀賞には副賞・ノートパソコン「HP Pavilion x360」、特別賞であるイシダ賞には副賞・図書カード3万円分が、入選には記念品・書籍『ギネス世界記録2018』がそれぞれ贈られた。

 各賞授与のあと、審査委員長である河口洋一郎氏(CGアーティスト、東京大学大学院情報学環教授)から「受賞作品はただプログラミングをしたという以上のプラスアルファを持ったものばかり。加えて、それぞれが異なる方向性を持った、豊かな多様性のある作品が揃った。それぞれ我が道を行く受賞作品を審査するのは難しくも楽しいことだった」と、第2回大会が非常に充実した内容であった旨の総評が述べられた。

昨年度に引き続き審査委員長を務めた河口氏。

 またそれぞれの賞は審査員である、金本 茂氏(株式会社スイッチサイエンス代表取締役)、林千晶氏(ロフトワーク代表取締役)、増井雄一郎氏(株式会社トレタCTO)、松林弘治氏(エンジニア/著述家、Project Vine 副代表)から、講評付きで授与された。

作者、審査員、実行委員揃っての集合写真。未来に対する期待が会場中にあふれるイベントとなった。

今年度の応募者属性/今後の大会による取り組み

 本レポートの最後に、今年度の第2回大会・応募状況について、簡単にご紹介したい。

 昨年度の第1回大会と比較すると、応募作のジャンルとしては<ゲーム>と<アート・デザイン>の割合が減少し、いっぽうで<電子工作・ロボット>と<アプリ・ツール>の割合が増えたという結果になった(前回比で、ゲーム:7パーセント減/アート・デザイン:11パーセント減/電子工作・ロボット:8パーセント増/アプリ・ツール:11パーセント増)。とは言え、応募作品の半数はゲームが占めており、プログラミングへの取り組みとゲーム開発の高い親和性は今年度の状況からも見て取れる。

第2回大会応募作のジャンル内訳(N=167)。

 応募者の学年としては、第1回大会はの小中の応募比率が “ほぼ半々” だったが、今年度は小学生からの応募が71パーセントという結果になった。特に応募全体の53パーセントを占める小学校高学年については、審査に関わった事務局スタッフからも「部門賞の審査における激戦区だった」という感想が聞かれた。

第2回大会応募者の学年内訳(N=167、グループ応募の場合は代表者の学年)。

 なお、大会実行委員会である「全国小中学生プログラミング大会実行委員会(略称:JJPC)」は、2017年12月に株式会社イシダ協賛のもと、滋賀県の栗東市教育委員会と連携し、栗東市の小学5~6年生と小学校教員へ向けたプログラミングワークショップを開催することが、栗東市の野村市長による定例記者会見で発表された。JJPCは今後も様々な地域や企業と連携し、小中学生に向けたプログラミング関連の取組みを続けていくということだ。

 また、本レポートでご紹介してきたイベント当日の様子が動画にまとめられており、こちらも是非ご覧いただきたい。

イベント当日の様子(動画提供:moovoo)

■関連サイト



カテゴリートップへ


この連載の記事

【12/17初開催】角川アスキー総合研究所 × MaruLabo「ディープラーニングの基礎を画像認識で学ぶ」ハンズオンセミナー


特別企画@プログラミング+
第26回

東京・飯田橋にて開催する「新しいスタイルのディープラーニングハンズオン」ご紹介です。

2017年12月08日 19時00分更新

文● 丸山不二夫、編集● プログラミング+編集部

 ディープラーニング(深層学習)や機械学習の分野・領域に関して、より多くの方々に知識と理解を得ていただくことを目的に、角川アスキー総合研究所はこれまで多数のセミナー・講座を開催してまいりました。こうした講座を受講してくださった方々の「実際にディープラーニングを体験してみたい」という多数のお声にお応えすることを目的に “参加しやすさを重視した新しいスタイルのディープラーニング体験(ハンズオン)セミナー” を、2017年12月17日(日)に初開催いたします。

 本セミナーはAWS(アマゾン ウェブ サービス)やGoogle、Microsoftと共にハンズオン開催を続けてきたMaruLaboとの共催企画です。セミナー内容と特長について、本記事では講師・丸山不二夫氏からのメッセージをご紹介します。

ハンズオンセミナーについて(文・丸山不二夫氏)

 今回のハンズオンは、ディープ・ラーニングの応用として最大の成果を納めている「画像認識」技術を素材として、ディープ・ラーニングの基礎を学ぶことを目的としています。個人の興味としてではなく、会社で、AIの取り組みを始めようとしている人には、ちょうどいい入門講座になると思います。

講師を務める丸山不二夫氏。ディープラーニングをテーマに多数の講義を、これまで角川アスキー総合研究所と開催してきた。

 まず、はだかのTensorFlowで、DNN(Full Connect Multi-Layer Feed-Forward Neural Network)の基礎を学びます。そのあとは、Kerasを使おうと思っています。ディープ・ラーニングを実際に使う上でで重要なことは、モデルを変更したり、メタ・パラメーターを変更して、推論の制度を上げることなのですが、今までのハンズオンでは、なかなかそこまでできませんでした。Kerasなら、そういうことが簡単にできます。

 丸山は、角川アスキー総合研究所と、ディープ・ラーニングの「6時間集中講義」を何度か行ってきました。ただ、それは「座学」でした。「6時間集中講義」に参加された方、講義部分は重複がありますが、ぜひ、自分の手で実際に、ディープ・ラーニングを動かしてみる、このハンズオンにご参加ください。

 丸山は、また、昨年来、MaruLaboとして、Googleさん、AWSさん、Microsoftさんと、それぞれのユーザーコミュニティであるTFUG, JAWS, JAZUGの協力を得て、「クラウド・ハンズオン」を展開してきました。それは、以前から、ディープ・ラーニングの学習と開発には、クラウドのGPUを使うのが一番いいだろうと考えていたからです。

 ただ、「実績の」と書いてありますが、クラウド・ハンズオンでは、いろいろ失敗も経験してきました。GPUの手配ができなくて、CPU10数個で代用したものの、時間内に機械の「学習」が終わらなかったり、GPU環境の構築に時間の大半を使ってしまって、ほとんど何もできなかったり。また、参加者は、クラウドのアカウントを事前に取得することが必須なのですが、苦労して周知したはずなのに、アカウントのない人が参加したり。

 今回のハンズオンは、そうしたMaruLaboの「経験値(失敗のですが)」と、何より、クラウド上のディープ・ラーニングGPU開発環境の充実に依拠した、クラウド・ハンズオンらしい、新しいスタイルで行います。

 まず、参加者は、クラウド・アカウントの取得・GPU確保等の「事前準備」は、一切いりません。主催者が、AWSさんと一括契約して、参加者分のハンズオン・アカウントとGPUインスタンスを、クラウド上に確保しています。その費用は、講習費用の中に含まれています。参加者は、自分のPC/Macを持って参加するだけで、面倒な環境構築をスキップして、すぐに、クラウド上のディープラーニングGPU環境が利用できます。

 また、コンソールからのコマンド・ベースではなく、Jupyter Notebookを使って、ブラウザーベースで、ハンズオンを行います。エディターを起動してソース・コードを編集しなくても、モデルやメタ・パラメータを変更して、「学習・推論」を実行することが、簡単に可能になります。ハンズオン終了後、正式にクラウドのアカウントを取得していただければ、自宅や会社で、ハンズオンの課題を復習できる情報を参考として提供します。

(ハンズオン後の学習については、アカウントの取得に加えて、クラウド上での環境構築の一定の知識が必要になります。操作は比較的簡単なものですので、積極的にチャレンジされることを期待しています。ただし、ハンズオン終了後の環境構築については、主催者は、援助・協力できません。個人の責任でおこなってください。あしからず。)

 今回のハンズオンでは、AWSの松尾さんが特別に登壇して、ハンズオンの直前にアメリカで開催されるAWS Re:Inventでのディープ・ラーニング関連の最新情報を提供してくれます。これも、楽しみです。

セミナー開催概要

  • タイトル
      角川アスキー総合研究所 × MaruLabo
      「ディープラーニングの基礎を画像認識で学ぶ」ハンズオンセミナー
      クラウドを利用した、PCを接続するだけで、すぐに大事な課題に集中できる新しいスタイルのハンズオンです
  • 日時:2018年12月17日(金)13:00 – 19:00(12:30 受付開始)
  • 会場:角川第3本社ビル(東京都千代田区富士見1-8-19)
  • 対象者:以下条件の両方に該当されている方を想定とするセミナー内容です。

    • なんらかの言語を利用したプログラミング経験があり、今回使用するプログラミング言語である「Python」がどのような言語かを理解されている方(Pythonでの開発経験自体は問いません)
    • ディープラーニングの開発について、概要レベルでの知識をお持ちの方(ハンズオン受講にあたって必要な基礎知識については当日ご説明を受けていただけます)
  • 参加費:2万7000円(税込)
      マルレク個人協賛会員と学生向けにそれぞれ割引がございます。詳しくはPeatixページをご覧ください。
  • 講師(敬称略)
      丸山 不二夫
      古川 新(MaruLabo)
      松尾 康博(アマゾン ウェブ サービス ジャパン株式会社 ソリューションアーキテクト)
  • 司会進行
      遠藤 諭(株式会社角川アスキー総合研究所 取締役主席研究員)
  • 主催:株式会社角川アスキー総合研究所
  • 共催一般社団法人MaruLabo
  • 詳細情報・ご応募Peatixページをご覧ください

講師プロフィール(敬称略)

丸山 不二夫

東京大学教育学部卒業。一橋大学大学院社会学研究科博士課程修了。稚内北星学園大学学長、早稲田大学大学院情報生産システム研究科客員教授等を歴任。オープンソースのコミュニティ活動に積極的に参加。日本Javaユーザー会名誉会長。日本Androidの会名誉会長。クラウド研究会代表。近年では、日本のIT業界がグローバルな技術イノベーションの一翼を担うことを目標に、連続講演会「マルレク」を主宰し、クラウドコンピューティングや人工知能などの技術について講演を行っている。

古川 新(MaruLabo)

当日、以下の内容でのハンズオンを講師として担当。

【題名】実践で学ぶディープラーニングの基礎

【概要】画像の分類を行うニューラルネットワークの構築を通して、ディープラーニングの基礎知識とモデルの開発プロセスを学びます。実装には世界中で使われているフレームワークであるGoogle TensorFlowを利用します。

【ハンズオンで扱う内容】

  • ディープラーニングにおけるモデルの設計、開発を実践します
  • モデルの設計を通してディープラーニングの理論を再確認します
  • モデルの実装方法をコーディングで学びます
  • モデルの評価方法と可視化ツールの使い方を学びます
  • モデルの精度を改善する多くの基礎的な手法を学びます
  • 高度な手法の提案と、その他今後の応用へのアドバイス
松尾 康博
アマゾン ウェブ サービス ジャパン株式会社 ソリューションアーキテクト

Amazon Web Services Japanにてソリューションアーキテクトとして幅広いお客様向けのクラウド導入支援に従事。最近は主に製造業や金融業向けのHPC(High Performance Computing)やDeep Learningなどの案件を支援。独立系SIerでR&D、スタートアップのCTOを経て現職。

詳細情報、お申込みはPeatixから!!



カテゴリートップへ


この特集の記事

最新実機に触れながら開発ノウハウを学べる!! Windows Mixed Reality をフィーチャーした講義、今週日曜開催


セミナー・イベント情報@プログラミング+
第20回

2017年11月12日(日)VR/ARビジネスと開発技法の最前線2017 Part.2

2017年11月10日 18時00分更新

文● プログラミング+編集部

VR技術体験が今まさにすべての人のものになろうとしている?

 その用途として「ゲームや映像を楽しむもの」というイメージを持たれている方も多いと思われる、VR(仮想現実)ヘッドセットなどの活用範囲が、デバイスと技術の著しい進化や入手しやすい価格帯の製品増加に伴い、デザイン・教育・医療など多くの産業分野へと今まさに拡がりを見せ始めています。

 特に注目したい動きは、マイクロソフトが普及を進めている、Windows Mixed Reality ヘッドセットの登場です。ヘッドセットそのものの登場に加え、2017年10月に提供されたWindows 10の最新アップデート “Windows 10 Fall Creators Update” を通じて、マイクロソフトはより多くのコンシューマー向けPCで、仮想現実をより気軽に体験できるようにサポートを進めています。

 これはつまり、ハイスペックな対応ゲーミングPCなどを必要とすることなく、我々が家庭やオフィスで日頃使用しているPCを用いて、VR/AR/MRといった技術にグッと触れやすくなっていることを意味します。体験への敷居が一段と低くなったことが、あらゆるビジネスにおいてその導入検討を加速させていく。そうであるのならば、これからVR/AR/MR技術は様々な産業へどのような影響を与えうるのかを考えるのは、今まさに旬なトピックだと言えます。

 こうした背景を踏まえ、これまではあまりVRなどの技術へ関心をお持ちでなかった方にも、是非この分野の可能性を知っていただきたいという思いから、2017年11月12日(日)に東京・飯田橋にて、角川アスキー総合研究所はリアル講座『VR/ARビジネスと開発技法の最前線2017 Part.2』を開催いたします。

 今年4月の開催に引き続き講師を務めるのは、Oculus Japan Teamを立ち上げ、現在のVR/AR/MRムーブメントを常にリードしてきた、近藤 “GOROman” 義仁 (@GOROman) 氏(株式会社エクシヴィ 代表取締役社長)。またゲスト講師として、日本マイクロソフト株式会社のテクニカルエバンジェリストである高橋 忍氏の登壇も決定いたしました。

 講座においては、なかなか知る機会の少ないVR先進諸国の最新事情から、実機を用いたデモンストレーション、対応コンテンツ開発における実践的ノウハウまで分かりやすく知っていただくことができます。

 現状スマートフォンが優勢を占める企業・ブランドと顧客のデジタルなタッチポイント(接点)や、体験を通じてしか学ぶことのできない研修や危機管理体験など、これからVR/AR/MRが変化させていく可能性を秘めたビジネス用途は多数考えられます。こうした最新事情と今後の可能性へ、本講座で是非触れてみてはいかがでしょうか。ご参加いただきやすい週末日曜13時からスタートの本講座へ、より多くのご参加をお待ちしております。

2017年11月12日(日)開催講義概要について

VR/ARビジネスと開発技法の最前線2017 Part.2

  • 日時:2017年11月12日(日)13:00 開演 19:00 終了予定
  • 会場:角川第3本社ビル(東京都千代田区富士見1-8-19)
  • 講師:近藤 “GOROman” 義仁 氏(株式会社エクシヴィ 代表取締役社長)
  • ゲスト講師:高橋 忍 氏(日本マイクロソフト株式会社 テクニカルエバンジェリスト)
  • 進行:遠藤 諭(株式会社角川アスキー総合研究所 取締役主席研究員)
  • 参加費:1万8900円(税込)
  • 詳細情報・参加お申込みhttp://lab-kadokawa36.peatix.com/

講義アジェンダ

1.『世界のVR/AR/MRビジネス最前線』
VR/AR/MR技術を活用したビジネスにおいて先を行く、中国深セン・上海・北欧フィンランド、また最近急速に注目され出しているエストニアのVRコミュニティを実際に視察してきた講師が、現地におけるVR/AR/MRの盛り上がりやビジネス事情について、その最新状況をお伝えします。また開発者のあいだで話題を集めている、フィンランドVarjo社の超高解像度HMD体験についてもお話します。

2.『最新のVR/AR/MR基礎知識』
VR/AR/MR技術の基本から最新のVRデバイス “Windows Mixed Reality ヘッドセット” まで、実機デモンストレーションを交えながらわかりやすく解説します。

3.『VR/AR/MRコンテンツ開発、運用の落とし穴とその対策』
コンテンツ開発における基本の技法から、最新デバイスでの開発方法と対策、実際に体験展示、運用する際のコツについて知見をシェアします。

4.『実践Windows MRコンテンツ開発』
ゲームエンジンであるUnityを用いながら、Windows Mixed Reality ヘッドセットによるコンテンツを実際に開発する手法を学びます。

5.『登壇者トークセッション:VR/AR/MRの未来』
Windows Mixed Reality ヘッドセットの登場で、今後の未来はどう変わるのか。質疑応答も交えながら、ビジネスのターゲットも含めて、VR/AR/MRの未来について登壇者による対談を行います。

※講義内容は変更になる場合があります。

講師プロフィール&メッセージ

近藤 “GOROman” 義仁 (株式会社エクシヴィ 代表取締役社長)

ゲームプログラマとして大学を中退し上京。PlayStaion/2/Xbox等のコンシューマタイトル制作に関わり、描画エンジン・アニメーションエンジン等を開発。2012年Oculus Rift DK1に出会い、自らVRコンテンツの開発を行いVR普及活動をはじめる。2010年株式会社エクシヴィを立ち上げ代表取締役社長となる。並行して2014年からOculus Japan Teamを立ち上げ、Oculus VR社の親会社であるFacebook Japan株式会社に所属。国内パートナー向けに技術サポート、数多くの講演を行う。現在はエクシヴィにてVRコンテンツ開発を行っている。個人でも”GOROman”として、VRコンテンツの開発、VRの普及活動を広く行っている。代表作はMikulus, Miku Miku Akushu,「初音ミク VR Special LIVE -ALIVE-」ロート デジアイ,DMM GAMES VR × 刀剣乱舞ONLINE 三日月宗近Ver.など多数。

高橋 忍(日本マイクロソフト株式会社 テクニカルエバンジェリスト)

日本マイクロソフトでテクニカルエバンジェリストととして、主にクライアントアプリケーション開発技術を中心に啓もう活動を行う。特にWindows 10のアプリ開発やMixed Reality、またUI/UX技術に加えて最近はクラウド技術にいたるまで、セミナーやハンズオン、もしくは各種案件を通じて開発者のための支援活動を続けている。

【高橋氏からのメッセージ】日本でHoloLensが発売されてから、コミュニティの盛り上がりはもちろん、多くの企業様でこの新しいデバイスと技術が注目されています。実際に私も様々な企業様とお話をして、すでに数多くの分野やプロジェクトで開発が進められています。先日は日本からもマイクロソフトの Micxed Reality 認定パートナーが発表されました。このセミナーを通じて講師の近藤氏と一緒に、日本と世界での状況や技術について私が知っている限りの情報をお話ししたいと思っています。

■参考記事

■関連サイト



カテゴリートップへ


この連載の記事

第二の故郷「香港」で私もドローンを飛ばしてみた


遠藤諭のプログラミング+日記
第28回

そろそろ上海蟹や煲仔飯(土鍋飯)のおいしい季節ですが

2017年10月13日 19時00分更新

文● 遠藤諭(角川アスキー総合研究所)

 10月1日(日)テレビ東京「ABChanZOO(えびチャンズー)」で、ドローン(UAV=無人模型航空機)に関してレクチャーさせてもらった。登場シーンでいきなりズッコケたのは置いておくとして、見た人からはドローンの最新事情が分かってよかったという反響ももらった(ドローンは“ただ凄い”と紹介する番組が多いのに対して割りと踏み込んだ説明ができましたからね=ちなみにスタジオの小物が機械式計算機などとてもマニアック)。

 その1週間後、1年半ぶりで香港にでかけてきた。今回の目的は、日本では売っていない“あるもの”の購入と香港でドローンを飛ばすことである。

 思い返せば、2013年頃から、YouTubeにDJI Phantomシリーズを中心にドローン映像が上がりはじめた。DJIは、香港からほど近い中国本土の深センに本社があるからだろう、香港を空撮したものが目立つ。それを見て、俄然空撮がしたくなりDJI Phantom 2 Vision+の発売を待って買った私としては、第二の故郷といっていい(渡港40回以上)香港でドローンを飛ばすことが1つの夢だったのだ。

 具体的に、ドローンツアー決行となったきっかけは離陸重量300gの「DJI Spark」を買ったのと、池澤あやかさんの「ハワイでドローン飛ばしてみた」という記事である(現地のルールや申請関連にも触れたとてもいい記事)。先日も「中国でドローン飛ばしてみた」が公開された。その池澤さんとツイッターでやりとりしていたら、香港のドローンの規制やルールについて「こんな感じでは?」と教えてくれた。

DJI Sparkは、スマホでは100m、送信機では500mの距離で操作可能。キロ単位で飛べるPhantomやMavicに比べると自分がいるまわりを散策する感じに近い。上位機種にはない「おまかせ撮影」(被写体のまわりを旋回しながら撮影など)の機能もあるライトユーザー向け新世代ドローンといえる。

 香港のドローン規制については、香港特別行政区の民航處(CAD=Civil Aviation Department)の関連ページに説明があるのだが、DJIの「安全飛行 フライトマップ」で具体的な制限区域も分かる(警告が出たり離陸できない場所など)。

DJIの「安全飛行 フライトマップ」は各国の飛行制限区域やルールの概要が分かる。

 現地についてからは銅鑼湾にある「DJI 香港旗艦店」に立ち寄って、店員さんにアドバイスをもらうこともできた。今回のような場合は申請不要とのこと。もらった民航處のパンフレットでは、7kg以下の機体、高度は300フィート(91.44メートル)を超えない、昼間の飛行に限るとしたうえで、避けるべき場所や行為、飛行場など具体的な制限区域があげられている(実際に飛行を計画する場合はあらためて民航處のホームページを確認のこと。先の池澤さんの記事によると中国は6月1日以降登録が必要になったとのこと)。

「崇光 – SOGO」(香港そごう)の裏側にあるDJI 香港旗艦店。1階には実機を飛ばせるミニブースもある。

店内は広々としてとてもキレイ。プロ向けの上位機種やこんな品も展示されている。スタッフがとても親切だ。

 東京23区の場合、申請なしには200g以上のドローンを上げることができないのに対して、香港の制限区域はかなり限られている。しかし、香港の市街地は超人口過密状態だ。ふつうに街を歩い回って安全を確保できそうな場所を見つけるのは、正直なところ至難のワザといえる。私の場合は、香港の知人家族がハイキングをかねて車で香港島を西側からまわりこんでくれたりしたのでたすかった(というか楽しかった)。そうでない場合は、Google Street Viewなど可能な限りの情報をもとに少しでも下調べをしておくしかない。

 ということで、飛行高度の関係もあって2013年頃にYouTubeで見た絶景といえるほどの映像ではないのだが、いくつか空撮映像の拙作を紹介させてもらいます。

 

南Y島を望む海岸にて撮影。あとで調べて分かったのだが最後のほうに出てくるのは第二次世界大戦時に英国が作ったトーチカとか。

 

ビクトリア湾が制限区域だが港のようすは撮影したくて鯉魚門の近くまで移動して撮影。船舶も近寄れないので距離を置いてだが気分はもう「ミスターBOO!」だ。

 香港に出かけはじめた89年には、鯉魚門には、まだハードディスクメーカーのMaxtorの工場なんかがあった。1980年代は、缶詰工場などが次々コンピューター関連に切り替わったなどといわれたが、急速に台湾へ、そして中国へとコンピューターの製造が移っていったのはご存じのとおり。それでも、香港の電脳街は楽しくて『月刊アスキー』で何度やったか分からない。やっぱり、アジアと電脳とエスニックフードは三位一体なのだと思う。

 冒頭に掲げたビルの写真は、映画『トランスフォーマー4』に登場した怪物ビル(海景樓、福昌樓、海山樓、益昌大厦、益發大厦の5つのビルの合体)。裏手の山道にある休憩所からあげさせてもらったのだが、さすがにビルに寄っていく余裕はなかった。このビルに関しては以下のTHETA画像をご覧あれ(2016年1月の撮影)。

Post from RICOH THETA. – Spherical Image – RICOH THETA

 ところで、この夏は、クオリティソフト株式会社の浦社長に誘われて同社の南紀白浜オフィスに出かける予定だったのだが、私のスケジュールの都合で中止になってしまい迷惑をかけてしまった。同社は、プライベートビーチも持っているうえに、ドローンスクール事業でも定評がある。ということで、なんとなく2カ月遅れでドローンを飛ばすことはできたのだが、やっぱりドローン空撮は楽しい。

 どう楽しいのかというと、一眼レフが、世界を違ったレンズで切り取ることで世の中の見方を変える道具だとすると、ドローンは視点を変えて世界の見方を変える道具なのだ。

遠藤諭(えんどうさとし)

 株式会社角川アスキー総合研究所 取締役主席研究員。月刊アスキー編集長などを経て、2013年より現職。角川アスキー総研では、スマートフォンとネットの時代の人々のライフスタイルに関して、調査・コンサルティングを行っている。著書に『ソーシャルネイティブの時代』、『ジャネラルパーパス・テクノロジー』(野口悠紀雄氏との共著、アスキー新書)、『NHK ITホワイトボックス 世界一やさしいネット力養成講座』(講談社)など。

Twitter:@hortense667
Mastodon:https://mstdn.jp/@hortense667

■関連サイト

DJI(https://www.dji.com/jp
クオリティソフト株式会社(http://www.qualitysoft.com/
ドローンビジネスカレッジ(https://drone-bc.jp/


カテゴリートップへ


この連載の記事

戦後GHQによる「航空禁止令」(?)とはどんな内容だったのか読んでみよう


遠藤諭のプログラミング+日記
第22回

2017年08月15日 12時00分更新

文● 遠藤諭(角川アスキー総合研究所)

たった1枚の指令書

 NHK BSの「なぜ日本は焼き尽くされたのか~米空軍幹部が語った“真相”~」を見た。今年4月に発掘された米空軍幹部246人にインタビューしたテープを中心に表題のテーマに迫った内容。この番組の内容にあまり踏み込むつもりはないのだが、軍隊というのは企業とおなじく人によっていかようにも動く不可思議なものだという印象だ。

 それで1つ書いておきたいのは、日本の戦後の技術開発に関するストーリーでは、しばしば触れられるGHQ(連合国軍最高司令官総司令部)による「航空禁止令」と呼ばれているものについてである。

 1945年(昭和20年)に「航空機の研究・設計・製造を全面禁止された」などとあり、1952年(昭和27年)にサンフランシスコ講和条約の発効によって、ふたたび開発が可能となったなどと書かれることが多い。2015年の三菱リージョナルジェットの初飛行のときも、頻繁にニュース等で引用されたのでご存じの方も多いはず。

 ところが、この「航空禁止令」について私は少しひっかかっていたことがあった。『計算機屋かく戦えり』(KADOKAWA / アスキー・メディアワークス刊)の元になる『月刊アスキー』での取材では、ちょうど同じ戦後間もない時期に国産コンピューターを開発していた人たちからはGHQという言葉も何度となく聞くことになった。

 GHQの指導により、1948年に逓信省電気試験所が商工省工業技術庁電気試験所と逓信省電気通信研究所への分離。その電気通信研究所へは「日本としてのトランジスタの利用法を考えろ」と非常にはやい時期にGHQから指示があったなどだ。ところが、初期のコンピューターの主要用途の1つが航空機の翼面設計だったにもかかわらず、GHQによる航空禁止令についてはボンヤリとしたままだった。

 一方、航空機の開発に従事していた人たちのインタビューでは必ずこの禁止令に関するコメントが出てくる。実際は、どんなことだったのだろう?

 これについて、ちょうど1年ほど前(2016年3月15日)から、GHQが日本政府に対して出したさまざまな指令(Supreme Commander for the Allied Powers Directives to the Japanese Government =SCAPINs)が、国立国会図書館の「リサーチ・ナビ」で公開されているのをご存じだろうか? 一昨年、米国国立公文書館(RG 331)のマイクロフィルムを複写して所蔵していたものをデジタル化、昨年から公開されたのだ。

ペラペラのたった1枚のタイプ打ちされた指令は6項目からなっている。具体的な、今後の航空機の所有・開発・研究等を禁止しているのは、4項目目と5項目目のそれぞれ1センテンスだ(国立国会図書館リサーチ・ナビより)。

 「日本のポツダム宣言受諾を受けて、トルーマンは、1945年8月14日付けで米太平洋陸軍司令官のマッカーサーを連合国最高司令官(Supreme Commander for the Allied Powers, SCAP)に任命した。同年9月2日に調印された降伏文書により、連合国最高司令官が降伏実施のために適当と認めて自ら発した布告、命令及び指令を日本政府及び日本軍は遵守し、実施する義務を負った」(同ページより)とあり、2631アイテム(収集時のマイクロフィルムは7巻分)が、ネットで閲覧できる(いままでも資料はあったはずだがこのような形で公開されたのはありがたい)。

 この中に「航空禁止令」と一般に呼ばれているものに相当する内容が含まれている。「SCAPIN-301: COMMERClAL AND CIVIL AVIATION 1945/11/18」と題されたたった1ページのタイプライターで打たれた文書で、具体的には、リサーチ・ナビの当該ページを見ていただくのが早いのだが、タイトルは「Commercial and Civil Aviation」(商業および民間航空)とだけつけられたものとなっている。政府や民間の航空関連の組織の解散などに続けて、4項目目で1945年12月31日以降の航空機や関連部品、施設などの購入・所有等を、ワーキングモデル(作業用模型)も含めて航空機に関係するものを禁止。

4. On and after 31 December 1945 you will not permit any governmental agency or individual, or any business concern, association, individual Japanese citizen or group of citizens, to purchase, own, possess, or operate any aircraft, aircraft assembly, engine, or research, experi- mental, maintenance or production facility related to aircraft or aeronautical science including working models.

 そして、5項目目では、航空科学や航空力学、そのほか航空機や気球に関係した教育・研究・実験をも禁じている。

5. You will not permit the teaching of, or research or experiments in aeronautical science, aerodynamics, or other subjects related to aircraft or balloons.

 国会図書館の資料のことを知ったのは、今年1月のことだが、今回、これを書いていて『The Allied Occupation and Japan’s Economic Miracle: Building the Foundations of Japanese Science and Technology 1945-52』という本があることを知った。航空禁止の件についても少し踏み込んで語られているようだ。また、インターネットアーカイブで『Japan’s air power options: the employment of military aviation in the post-war era.』というドキュメントを読むこともできる。

 日本のテクノロジーの発展の仕方やこのことについてより詳しく知りたい人は、参考になるかもしれない。

遠藤諭(えんどうさとし)

 株式会社角川アスキー総合研究所 取締役主席研究員。月刊アスキー編集長などを経て、2013年より現職。角川アスキー総研では、スマートフォンとネットの時代の人々のライフスタイルに関して、調査・コンサルティングを行っている。また、2016年よりASCII.JP内で「プログラミング+」を担当。著書に『ソーシャルネイティブの時代』、『ジャネラルパーパス・テクノロジー』(野口悠紀雄氏との共著、アスキー新書)、『NHK ITホワイトボックス 世界一やさしいネット力養成講座』(講談社)など。

Twitter:@hortense667
Mastodon:https://mstdn.jp/@hortense667



カテゴリートップへ


この特集の記事

教室数は1000校以上!! ヒューマンアカデミー『ロボット教室』責任者から“プログラミング教育”の本質を聴いた


スペシャルトーク@プログラミング+
第14回

「プログラミングして意のままにロボットを動かした、という体験を多くの子どもたちへ届けたい」

2017年06月27日 19時00分更新

文● プログラミング+編集部

 2020年からプログラミングの義務教育化を控えている現状だが、教育現場からはカリキュラムづくりや先生に求められる能力など、依然として不安や課題を多く抱えている旨の声が聞かれる状況だということも否定できない。そんな中、未就学児からの“習いごと”として、プログラミング教育の重要性が広く認識される以前から『ロボット教室』を展開してきた存在がある。教育事業大手の『ヒューマンアカデミー』だ。

 今回編集部は、この『ロボット教室』事業を牽引してきた、ヒューマンアカデミー株式会社 児童教育事業本部 チーフマネジャーの神野佳彦氏から、事業に関するお話をうかがうことができた。ロボットクリエイターの高橋智隆氏をアドバイザーに迎え、2009年6月からロボット教室事業を展開してきた同社の経験や工夫からは、プログラミング教育の導入を考えるうえで非常に学ぶ点が多い。教育現場へのプログラミング教育を具体的に検討されている方々にとって、貴重なノウハウを含んでいるインタビューを、今回はお届けしたい。

“自分が作り上げたものが実際に動く”経験を年少時に体験しておくことの重要性

―― いまロボット教室は、教室数でいうとどれくらいまで増えているんですか?

神野氏(以下、敬称略) 2017年5月末時点で、1000校を超えたくらいですね。

―― いつ頃から急に増え始めた、などあるのでしょうか?

神野 ここ4、5年で急激に増えている状況です。

―― ここ4、5年で急増した要因や背景としては、どのような社会の変化が挙げられますか?

神野 やはり、世の中がプログラミング教育を含め、ロボットなどの分野へ力を入れていこうという流れになっていますし、その流れを汲んだ習いごとも増えており、保護者の方々も意識をし始めていることが、ロボット教室が急増した一番の要因ではないかと思っています。

―― 教室は1000校を超えているとお話してくださいましたが、それに応じて生徒数も増えているんですよね?

神野 そうですね。生徒数は2017年5月末時点で、1万5000名を上回ってまいりました。

―― 教室に通われている生徒さんの年代は、どのあたりがメインなのでしょうか?

神野 7~9歳くらいまでの年齢層の生徒さんが多いですね。

―― 7~9歳だと、具体的にはどういったことをロボット教室では学ぶのでしょうか?

神野 オリジナルのブロックキットで時間内に1体のロボットを完成させます。“自分でロボットを作って実際に動かす”という一連の流れを、教室に通うお子さんたちには必ず経験してもらうにしています。

―― そうした経験によってお子さんたちは、どういった能力を伸ばしていけるのでしょうか?

神野 まずは、ロボットクリエイターの高橋智隆さんに監修いただいたロボットを、オリジナルのテキストに沿って製作します。そこで様々なことを学んでいただきます。その後、ロボットを作る過程で得た知識をいろいろと応用していくことで、想像(創造)力を育むことに注力しています。実際に自分の手を動かして、ブロックを用いながら立体的にロボットを作っていくため、空間認識能力などを養うことができます。また、作ったロボットを改造していくことで、表現力・思考力も高めることができます。

―― 2020年からプログラミング教育が必修化するわけですが、プログラミング教育には、やはり小さな頃から取り組んだほうがよいのでしょうか?

神野 低学年で最初から、プログラミング言語を用いてプログラミングをすることは難しいかなと思いますが、“ビジュアルプログラミング言語”を用いて、プログラム自体の成り立ちや、プログラミングの流れをまず学び、接するところから始め、そのうえで本格的にプログラミングを学ぶというのがスムーズではないかと考えています。

―― “ビジュアルプログラミング言語”を学習の導入に用いることには、具体的にどういった教育上のメリットがあるのでしょうか?

神野 “ビジュアルプログラミング言語”では“動きの箱”を組み合わせる、ということをします。“命令の箱”とも表現できると思いますが、それらを組み合わせることによって、ひとつのスクリプトを完成させることができます。そして、自分で作ったロボットを、自分で作ったプログラムで、自分の思うように動かしていきます。そうすると、単に“作る”だけでも、“作って動いた”というだけでもなく、“自分の意のままに動かすことができた”という経験を得ることができます。これを体感していただけることこそが、小学生を対象としたロボット教室でビジュアルプログラミング言語を用いることの、一番のメリットではないかと思います。

想像(創造)力や課題解決能力を育む場を、学年で制限する必要はない

研修を受けた先生が教壇に立つが、プログラミング経験がなければ先生が務まらないというわけではないそうだ。

―― プログラミングの義務教育化を控えていますが、では学校の先生たちがプログラミングをどのように教えればよいかというのは課題としてよく挙げられる点です。貴校ではどのような方々が先生を務められているのでしょうか?

神野 本校のロボット教室で先生として活動されている方々は、ヒューマンアカデミーで研修を受けて合格点が出た方と、研修を受け一定期間教室を運営されている先生からOJT(オン・ザ・ジョブ・トレーニング)などで指導を受け、本部担当者より承認された方のみ、教壇に立っていただくようにしております。

―― 自社で育成された方々を先生として採用されているということでしょうか?

神野 採用しているというよりは、フランチャイズシステムで展開しておりますので、ご契約いただいたロボット教室を担当する方には、研修できちんとしたレクチャーを受けることで、先生として教壇に立つ準備をしていただいているというほうが正しいですね。

―― ロボット教室の先生になるうえで、資格などは必要なんですか?

神野 資格などは必要ないですよ。子どもたちには、理系分野へ興味を持っていただくことが一番の目標ですが、発想力や想像(創造)力を引き出す目的も、ロボット教室にはあります。ですから、子どもたちが作ったロボットやプログラムをきちんと評価することができる人、コミュニケーション能力が高い人のほうが先生に向いていると思います。

―― では、プログラミング経験を持っていなければ先生が務まらないというわけではない、と?

神野 どなたでも、お子さんと接することが好きな方でしたら、先生になっていただけますよ。

―― 小学校低学年からロボット教室を受講可能とのことですが、学習面での段階やコース分けはあるのでしょうか?

神野 未就学児向けの『プライマリーコース』から始まりまして、『ベーシックコース』、『ミドルコース』、そして『アドバンスプログラミングコース』と、4つのコースをご用意しております。

―― 4つのコースは内容面でどのように分けられているんですか?

神野 『プライマリーコース』『ベーシックコース』『ミドルコース』は、プログラミング的な要素を含んでいません。これら3つのコースは、モノづくりにフォーカスした内容です。オリジナルのブロックキットでロボットを製作し、モーターと歯車でそれを動かすといった“動きの仕組み”を最初に学びます。モーターの回転運動をどのようにパーツを組み合わせれば上下や左右の運動へ変換できるかな、とか、歯車の組み合わせをどう変えればスピードが出るようになるか、パワーを出すためにはどう改善すればよいのかなどを、手を動かしながら考えます。そうして動きの仕組みを学んだうえで、『アドバンスプログラミングコース』でプログラミングを学びます。ここからは動きをも想像(創造)すると言ったらよいでしょうか。このような流れで各コースを展開しています。

―― 各コースは学年や年齢で分けられているのでしょうか?

神野 小学1年生以上のお子さんは『ベーシックコース』から、5~6歳で未就学児のお子さんは『プライマリーコース』から学んでいただく、というように目安は設けておりますが、まずは体験授業を受けていただき、お子さんの状況に応じてスタートのコースを提案しています。

―― 生徒数が1万5000人を超えていると、なかには天才的なお子さんというのもいるのでしょうか?

神野 ブロックが大好きで、普段からご自宅でよく遊んでいるお子さんや、お父さんとプラモデルをよく作るというお子さんですと、低学年であっても高学年の生徒さんより早く作ってしまう、というのはよく見受けられます。なかでもすごいお子さんは、私どもが年に1度開催している全国大会の創作ロボットコンテストで、大人も思いつかないようなロボットを発表されることが多々ありますね。

―― 大会で発表できる機会があるというのは、お子さんにとっても記憶に残る経験になりますね。

神野 私たちもそう考えて毎年開催しております。昨年、東京大学の安田講堂で開催した全国大会には、1600名ほどの方にご来場いただきました。その大勢の観客の前で自分が製作したロボットについてプレゼンするという、大人でも足がすくんでしまうような状況は、なかなか経験できないことだと思いますね。

2016年8月に開催された『第6回ヒューマンアカデミーロボット教室全国大会』の様子。

―― そんなすごい場で発表されるのは小学生のお子さんでしょうか?

神野 年長のお子さんから中学3年生まで、幅広い参加者がいらっしゃいます。

自発的な学びの姿勢を育て、その結果として理系好きが増えてほしい

―― ロボット教室の今後についてはどのようにお考えですか?

神野 私たちが最初にロボット教室を開始した時のコンセプトとしては、サイエンスや理系分野へ興味を持ってくれるお子さんをもっともっと増やしたいということを一番に考えていました。理系分野へ興味を持つ、その足がかりになるような学びの場を作れないかということです。もちろん、理科好き・理系好きに育っていただきたいという願いは今も続いております。それに加え、自分からものごとに取り組む姿勢や、想像(創造)力、論理的思考力、ロボットを製作するなかで失敗した時にその原因をつきとめ、改善し、次に活かすという課題解決能力、そして“ロボットを作りきった”という達成感を、成功体験として積み重ねることで、自分に自信が持てるお子さんたちが増えればいいなと思っています。

―― 理系ありきではなく、ロボット教室を受講する一番のメリットは、想像(創造)力や表現力というところにまずあるということでしょうか?

神野 そうですね。形が複雑なロボットや、歯車の組み合わせが難解なロボット、微調整がとても必要なロボットなど、さまざまなロボットを製作するなかで、想像(創造)力や表現力が問われる経験を教室で積んでいただいて、いつか自分で考えだした、誰も思いつかなかったような創作ロボットを作っていただけたらなと思っています。

―― 創作性豊かなロボットを作り出せるお子さんというのが、今後どんどん登場しそうですね。楽しみです。

神野 日本国内だと、都市部では教室数もかなり増えてきています。今後は郊外など、ロボット教室がまだないエリアにも展開を拡げていく計画を進めています。また、海外でもロボット教室を展開しており、現在はアジアを中心にロボット教室が増えていますが、もっと興味をもっていただける国が増えればと思っています。

―― 教室だけでなく、例えば教材を他の機関がされている授業で使わせてほしい、といった話もあるのではないでしょうか?もともと“これ”といった教材が存在していなかった分野だとうかがいましたが。

神野 私どもも、最初はまったくのゼロからロボット教室を立ち上げましたので、教材開発については苦労したところです。そういった意味では今、学校教育でもアクティブラーニングや、プログラミング的思考力の育成がうたわれておりますので、教材の学校導入などについても、少しずつではありますがお問い合わせをいただいております。

―― 今後の教育現場において有益な価値を貴校は蓄積されているということでしょうか。

神野 そうですね。学校さまに導入いただけるパッケージもご用意しておりますので、是非お声掛けをいただければと思います。

―― いま、プログラミングを小さい時から学ぶというのはどういう意味か、改めて教えていただけますか?

神野 今後AIなどの分野が伸びるにつれ、新たな職業の選択肢が増えていくと言われています。逆にある分野では、仕事がAIに取って代わられるということも起こり得る、という時代になっていきます。そうなると、プログラミングやAIの分野に関する知識の有無が、大人になってから大きな違いを生み出すと思います。現在の子どもたちが、私どものロボット教室に通うことを、そうした分野へ興味を持つ第一歩にしてもらえたらなと思っています。

―― 英語を学ぶよりもプログラミングを学ぶほうが重要、なんていう極端な意見も聞かれますが……?

神野 どちらも習ったほうがよいと思います。例えばロボット分野でどんどん名を馳せていくにしても、そこには世界という選択肢が当然視野に入ってくると思うんです。そのときに英語(語学)に長けているといないとでは、だいぶ違うと思います。私どもも、子どもたちの未来のために、英語教育には力を入れております。これからの時代の流れを見据えると、プログラミングと英語(語学)は、絶対両方学んでおいたほうが良いと言い切れます。

神野 佳彦(かんの よしひこ)

大学卒業後、大手予備校FC事業のチェーンマネジメントや学習塾運営・生徒募集のコンサルを経て2008年、ヒューマンアカデミーに入社。現在、児童教育事業本部 チーフマネージャーを務める。

■関連サイト


カテゴリートップへ

この連載の記事

Go言語とコンテナ


Goならわかるシステムプログラミング
第20回

Go言語によるプログラマー視点のシステムプログラミング

長かった本連載も今回が最終回です。 この連載では、プログラムがコンピュータ上で動くときに何が起きているのかを、Go言語のコードを通して覗いてきました。 今回は、その締めくくりとして、コンテナについて紹介します。 現在広く利用されているコンテナ技術であるDockerのコアは、Go言語製のlibcontainerというライブラリです。 このライブラリを使って自作のコンテナを仕立ててみます。

今回の原稿にあたっては、仮想化周りでsyohexさんに細かく指摘をいただきました。ありがとうございました。

仮想化

コンテナの話に入る前に、コンテナと目的がよく似た技術である仮想化について説明します。 仮想化は、コンテナよりも先に広く使われるようになった技術ですが、 歴史的にさまざまなソリューションがあり、どのような仕組みか、どのようなメリットがあるか、どのような制約があるか、どこにフォーカスするかで分類の仕方なども大きく変わります。 ここでは、仮想化とは何かについて、軽く概要だけ触れます。

ソフトウェアは、ハードウェア、そしてその上で動くOSの上で動作します。 ハードウェアを制御し、効率よくストレージやネットワークの入出力ができるようにしたり、メモリを管理したり、CPUの処理時間を割り振ったりするのは、OSの役割です。 そのハードウェアとOSの間にもう一つOS(もしくはOSのようなもの)の層を差し込むことで、1台のハードウェア上に複数のOSやシステムを安全に共存させ、ピークの異なる複数のサービスをまとめるなどして効率的にハードを使うのが仮想化です。 具体的には、完全な機能を持った普通のOS(ホストOS)の上にハードウェアをエミュレーションする仮想化のためのソフトウェアを作り、その上にゲストOSをインストールして使います(ゲストOSも普通のOSです)。

仮想化のためのソフトウェアとしては、Oracle VirtualBoxやHyper-V、Parallels、VMWare、QEMU、Xen Serverといったものが利用されています。 これらは、大きく分けて次の2つの方式に大別できます。

  • CPUを完全にエミュレーションすることで別のハードウェアのソフトウェアも使える方式。CPUをエミュレーションするため、どうしてもパフォーマンスは大きく落ちるが、現世代の高速なCPUを使って旧世代のCPU向けに書かれたアプリケーションを動作させるのに利用される
  • 同じアーキテクチャのCPUに限定されるものの、エミュレーションが不要で高速な方式

仮想化は、クラウドコンピューティングを支える大事な技術です。 仮想化そのものをサービスとして提供しているものは、Infrastructure as a Service(IaaS)と呼ばれます。 Amazon EC2やGoogle Compute Engine、さくらのVPSなど、OS環境を提供するサービスが登場したおかげで、ソフトウェアビジネスの構造は大きく変わりました。

仮想化は低レイヤの技術の組み合わせ

仮想化そのものは、OSの上でOSを動かすという、大掛かりで複雑な仕組みです。 ゲストOS上で動いているプログラムから、システムコール呼び出しなどの特権的な命令でホストOSが呼ばれてしまうと、仮想化していたつもりがおかしなことになってしまいます。 あるゲストOSで設定したCPUの状態(例えば、例外発生のモード変更)が、別のゲストOSに影響を与えてはいけません。ホストOSとゲストOSの間も同様です。 こうした要件は、システムを仮想化するのに満たすべき必要条件として、1974年に論文化されています。

最新のIntel系CPUは、この「PopekとGoldbergの要件」を満たす仮想化支援機能として、VT-xというものを備えています。 これは、第5回 Goから見たシステムコールで触れたユーザモード3)、特権モード0)の下に、ハイパーバイザー用OSのモード-1)を追加し、それを使うことで、ゲストOSからホストOSへの処理の移譲が必要な操作を効率よくフックできる仕組みです。 一見すると、WindowsやmacOSというホストOSがあり、その上のアプリケーションとして仮想化のシステムがいて、その中でゲストOSが実行されているように見えますが、CPUから見ると、ホストOSの下により強力な権限をもったレイヤーが追加されているというわけです。

現在のIntel系CPUには、VT-x以外にも、ゲストのメモリアドレスとホストのメモリアドレスとを変換する拡張ページテーブル、外部ハードウェアとのアクセスでホストOSを介さずに実行(PCIパススルー)できるようにするVT-d、ネットワークの仮想化のVT-cなど、さまざまな仮想化支援機能が実装されています。 これらの支援機能により、ハイパーバイザーが毎回割り込みをしてホストに投げなくても済むようになり、ネイティブに近い速度で仮想化が動くようになってきました。

ハイパーバイザーの側では、WindowsのHyper-V、macOSのHypervisor.framework、LinuxのKVMといった、OSが提供する支援機能を利用します。 高効率な仮想化は、CPUやOSなどさまざまな低レベルのレイヤーの手助けにより実現しているのです。

準仮想化

本文で紹介した仮想化の手法は「完全仮想化」と呼ばれるものです。これに対し、「準仮想化」について紹介している書籍も多くあります。 完全仮想化と準仮想化とでは、ハイパーバイザーやVirtual Machine Manager(VMM)と呼ばれるシステムがホストOS(あれば)やゲストOSの調停を行うのは同じですが、 OSの実行に必要だけど他に影響を与えうる命令(センシティブ命令)の扱いが異なります。

  • 完全仮想化では、ゲストOSがホストOS上にインストールされ、ゲストOSは自分が仮想環境で動いているのを意識する必要がない。 他に影響を与えうる命令を実行すると、割り込みが発生し、ハイパーバイザーがその処理を代行する

  • 準仮想化は、ハードウェア上にインストールされたハイパーバイザー(ホストOSはない)上で動作する。 ゲストOSは、自分がハイパーバイザーの上で動作していることを意識しており、他に影響を与えうる命令の代わりにハイパーバイザーを呼ぶ(hypercall)

準仮想化では、事前にセンシティブ命令を書き換えておくことで仮想化を実現します。 ハイパーバイザーの仕様に合わせてカスタマイズされたゲストOSを使うため、ホストOSは不要であり、全体のレイヤーが薄くなります。 また、センシティブ命令をハイパーバイザーによる例外処理の割り込みではなく効率の良い呼び出しに書き変えたり、パフォーマンス・チューニングを施したりすることで、完全仮想化よりも高いパフォーマンスを実現できます。 Amazon EC2も、初期には準仮想化メインで運用されていました。

完全仮想化では、センシティブ命令などの呼び出しに対してハイパーバイザーが割り込みを行い、適切に変更を局所化させます。 この方法ではパフォーマンスに難がありますが、本文で紹介したようなさまざまな支援機能をCPUが提供することで高速化が施され、欠点が改善されてきました。

準仮想化には、Windowsなどの外部からのカスタマイズが難しいOSは開発元の協力がないと動かせず、GPUなどの新しいハードウェア対応が難しいといったデメリットもあります。 そのため、徐々に完全仮想化のほうが優勢になっていきました。 Amazon EC2も、リストを見る限り、現在ではほぼ完全仮想化となっています(ただし、インスタンスタイプごとに、完全仮想化(HVM)と準仮想化(PV)とで対応具合が異なります)。

コンテナ

ここまでの説明からわかるように、仮想化は、使いたいサービスだけでなくOSも含めてまるごと動かすことが前提の仕組みです。 そのため、たとえばゲストOSとホストOSが同じLinuxであればカーネルやシステムのデーモンを重複してロードすることになり、無駄にメモリを消費してしまいます。

そこで、「OSのカーネルはホストのものをそのまま使うが、アプリケーションから見て自由に使えるOS環境が手に入る」の実現に特化したのがコンテナと呼ばれる技術です。 「アプリケーションが好き勝手にしても全体が壊れないような、他のアプリケーションに干渉しない・されない箱を作る」という機能だけ見ると、仮想化もコンテナも同じであるため、コンテナのことを「OSレベル仮想化」と呼ぶこともあります。

仮想化ではストレージをまるごとファイル化したような仮想イメージを使ってアプリケーションを導入しますが、コンテナでもイメージと呼ばれるものを使います。 Amazon EC2 Container Service、Azure Container Service、Google Container Engine(GKE)、さくらインターネットのArukas(β版)など、コンテナのイメージ上でアプリケーションのデプロイが可能になるサービスもあります。

一口にコンテナ技術といっても、内部では複数の機能を組み合わせて実現されています。 たとえばLinuxでは、コンテナを実現するためのOSカーネルの機能として、コントロールグループ(cgroups)および名前空間(Namespaces)があります。 これらの機能を組み合わせることで、さまざまなOSのリソースを、仮想メモリを用意するように気軽に分割できます。

コントロールグループ(cgroups)は、次の項目の使用量とアクセスを制限できるようにするカーネルの機能です。

  • CPU
  • メモリ
  • ブロックデバイス(mmap可能なストレージとほぼ同義)
  • ネットワーク
  • /dev以下のデバイスファイル

また、カーネルでは、次のような項目について名前空間(Namespaces)を分離できるようになっています。

  • プロセスID
  • ネットワーク(インタフェース、ルーティングテーブル、ソケットなど)
  • マウント(ファイルシステム)
  • UTS(ホスト名)
  • IPC(セマフォ、MQ、共有メモリなどのプロセス間通信)
  • ユーザー(UID、GID)

これらの機能については、次節でコンテナを自作しますが、そのコードを見れば概要がつかめるはずです。 より詳しい情報は、Surgoさんによる下記の記事などを参照してください。

コンテナと仮想化の関係

仮想化ではOSを起動する必要があるため、起動には長い時間がかかります。一方、コンテナはプロセスを起動するように仮想環境を構築できます。 コンテナのほうが効率が良いので、コンテナが仮想化を置き換えるとか、仮想化は古いというわけではありません。 コンテナのツールとして人気のDockerは、Linuxではコンテナだけを利用しますが、macOSやWindowsではOSが提供する仮想化の仕組みを使ってLinuxを動かし、その中でコンテナを利用します。 コンテナの中で動かしたいシステムがLinuxであれば、一度Linuxを動かす必要がありますが、現在サーバー開発ではLinuxがよく使われ、提供されているDockerイメージもほぼLinuxなので、Linux以外には厳しい状況です。

例外がFreeBSDです。 FreeBSDには昔から、Linuxバイナリを動かすエミュレーション機能があるので、FreeBSDをホストにしてDockerでLinuxイメージを使うと、FreeBSDのカーネルのままJailでコンテナ化し、コンテナ内部ではLinuxバイナリを動かすという動きになるようです。

また、Windowsは軽量なLinuxのコンテナ相当のWindowsコンテナと、仮想化もプラスして他のOSも起動できるHyper-Vコンテナの2種類あります。

libcontainerでコンテナを自作する

現在、Dockerのコアとなっているのは、Go言語で書かれているlibcontainerというライブラリです。 このライブラリを利用してGo言語でコンテナを実装し、Linux上での起動に挑戦してみましょう。 今回のコードのサンプルはLinuxバイナリが直接実行できる環境でしか動作しません。 WindowsやmacOSをお持ちの方は、VirtualBoxなどの仮想環境をインストールして、Ubuntu Linuxなどを入れて試してください。

なお、言うまでもありませんが、コンテナの実装はDockerだけではありません。 Dockerにはrktというライバルもいて、こちらを推す人も少なくないのですが、rktはlibcontainerと違ってライブラリとして使うことが想定されていないので、今回はlibcontainerを取り上げます。

Dockerとlibcontainer

当初、Dockerは、Linuxにおけるコンテナ機能のためのユーティリティであるLXCのラッパーでした。現在のDockerは、libcontainerをベースに書き直されたものです。 さらにその後、libcontainerを含むコア部分はrunCというツールになりました。 それによってlibcontainerがなくなったわけではなく、現在もrunCのディレクトリ内に同梱されています。

runCは、Open Container Initiativeに寄贈されています。 OCI傘下になったことで、今後はさまざまなプラットフォームにも対応されていくことでしょう。

なお、ここで紹介する自作はコンテナは実験的なものであり、実用面ではrunCコマンドやDockerを使うほうがはるかに簡単でお手頃です。

OSのブートに必要な下準備

コンテナとなるコードを書く前に、コンテナ内で新しいOSを起動するために必要なファイル一式を用意する必要があります。 ここではサイズが小さいAlpine Linuxのイメージに含まれているファイルを利用させてもらうことにします。 必要なファイルは下記の要領で取得できます。

$ docker pull alpine ⏎
Using default tag: latest
latest: Pulling from library/alpine
2aecc7e1714b: Pull complete
Digest: sha256:0b94d1d1b5eb130dd0253374552445b39470653fb1a1ec2d81490948876e462c
Status: Downloaded newer image for alpine:latest
 
$ docker run --name alpine alpine ⏎
 
$ docker export alpine > alpine.tar ⏎
 
$ docker rm alpine ⏎

これで、ファイルシステムの中身がtarファイルとして取り出せました。 作業フォルダにrootfsというフォルダを作り、このtarファイルを展開しておきましょう。

$ mkdir rootfs ⏎
 
$ tar -C rootfs -xvf alpine.tar ⏎

libcontainerを利用してコンテナを作る方法は、libcontainerライブラリのREADMEにほとんどそのまま書いてあります。 それを参考にコンテナを作成してみましょう。

まずは必要なライブラリを取得してきます。 libcontainerライブラリのほかに、unixパッケージをインストールしてください。

$ go get github.com/opencontainers/runc/libcontainer ⏎
$ go get golang.org/x/sys/unix ⏎

それでは実装を見ていきましょう。

libcontainerを使うときは、main()関数のほかに、init()関数を定義します。 これは、Linuxの起動時には最初に呼ばれるinitプロセスが必要なためで、その処理をinit()以下に集約します。

コンテナの生成とinitプロセスの起動とを1つの実行ファイルで実現するには、自分自身をinitプロセスとして呼び出すモード(InitArgsの部分)をmain()の中で利用します。 その部分までのコードを下記に示します。

package main
 
import (
    "github.com/opencontainers/runc/libcontainer"
    "github.com/opencontainers/runc/libcontainer/configs"
    _ "github.com/opencontainers/runc/libcontainer/nsenter"
    "log"
    "os"
    "runtime"
    "path/filepath"
    "golang.org/x/sys/unix"
)
 
func init() {
    if len(os.Args) > 1 && os.Args[1] == "init" {
        runtime.GOMAXPROCS(1)
        runtime.LockOSThread()
        factory, _ := libcontainer.New("")
        if err := factory.StartInitialization(); err != nil {
            log.Fatal(err)
        }
        panic("--this line should have never been executed, congratulations--")
    }
}
 
func main() {
    abs, _ := filepath.Abs("./")
    factory, err := libcontainer.New(abs, libcontainer.Cgroupfs,
                                     libcontainer.InitArgs(os.Args[0], "init"))
    if err != nil {
        log.Fatal(err)
        return
    }

main()関数の以降の大部分は、生成するコンテナの環境設定です。 ホスト名(Hostname)やマウントするファイルシステム(Mounts)などをconfigインスタンスに設定していきます。 あらかじめ用意した仮想OSの実行環境もここで指定します(Rootfs)。


    capabilities := []string{
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_SETGID",
        "CAP_SETUID",
        "CAP_SETFCAP",
        "CAP_SETPCAP",
        "CAP_NET_BIND_SERVICE",
        "CAP_SYS_CHROOT",
        "CAP_KILL",
        "CAP_AUDIT_WRITE",
    }
    defaultMountFlags := unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
    config := &configs.Config{
        Rootfs: abs+"/rootfs",
        Capabilities: &configs.Capabilities{
            Bounding: capabilities,
            Effective: capabilities,
            Inheritable: capabilities,
            Permitted: capabilities,
            Ambient: capabilities,
        },
        Namespaces: configs.Namespaces([]configs.Namespace{
            {Type: configs.NEWNS},
            {Type: configs.NEWUTS},
            {Type: configs.NEWIPC},
            {Type: configs.NEWPID},
            {Type: configs.NEWNET},
        }),
        Cgroups: &configs.Cgroup{
            Name: "test-container",
            Parent: "system",
            Resources: &configs.Resources{
                MemorySwappiness: nil,
                AllowAllDevices: nil,
                AllowedDevices: configs.DefaultAllowedDevices,
            },
        },
        MaskPaths: []string{
            "/proc/kcore", "/sys/firmware",
        },
        ReadonlyPaths: []string{
            "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
        },
        Devices: configs.DefaultAutoCreatedDevices,
        Hostname: "testing",
        Mounts: []*configs.Mount{
            {
                Source: "proc",
                Destination: "/proc",
                Device: "proc",
                Flags: defaultMountFlags,
            },
            {
                Source: "tmpfs",
                Destination: "/dev",
                Device: "tmpfs",
                Flags: unix.MS_NOSUID | unix.MS_STRICTATIME,
                Data: "mode=755",
            },
            {
                Source: "devpts",
                Destination: "/dev/pts",
                Device: "devpts",
                Flags: unix.MS_NOSUID | unix.MS_NOEXEC,
                Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
            },
            {
                Device: "tmpfs",
                Source: "shm",
                Destination: "/dev/shm",
                Data: "mode=1777,size=65536k",
                Flags: defaultMountFlags,
            },
            {
                Source: "mqueue",
                Destination: "/dev/mqueue",
                Device: "mqueue",
                Flags: defaultMountFlags,
            },
            {
                Source: "sysfs",
                Destination: "/sys",
                Device: "sysfs",
                Flags: defaultMountFlags | unix.MS_RDONLY,
            },
        },
        Networks: []*configs.Network{
            {
                Type: "loopback",
                Address: "127.0.0.1/0",
                Gateway: "localhost",
            },
        },
        Rlimits: []configs.Rlimit{
            {
                Type: unix.RLIMIT_NOFILE,
                Hard: uint64(1025),
                Soft: uint64(1025),
            },
        },
    }

最後に、ここまでの部分で作ったconfigインスタンスをCreate 関数に渡してコンテナを作ります。 コンテナ内部で起動するプログラムは&libcontainer.Processで指定できます。 ここではシェルを起動するようにしましょう。


    container, err := factory.Create("container-id", config)
    if err != nil {
        log.Fatal(err)
        return
    }
 
    process := &libcontainer.Process{
        Args: []string{"/bin/sh"},
        Env: []string{"PATH=/bin"},
        User: "root",
        Stdin: os.Stdin,
        Stdout: os.Stdout,
        Stderr: os.Stderr,
    }
 
    err = container.Run(process)
    if err != nil {
        container.Destroy()
        log.Fatal(err)
        return
    }
 
    _, err = process.Wait()
    if err != nil {
        log.Fatal(err)
    }
 
    container.Destroy()
}

以上でコンテナの実装は終了です。 さっそく実行してみましょう。 コンテナの中でシェルが起動するはずです。 そのシェルで/bin/hostnameを実行すると、上記コードで設定したホスト名である「testing」が表示されることがわかります。

$ go build -o container container.go ⏎
 
$ sudo ./container ⏎
[sudo] shibuのパスワード: [sudoパスワードを入力] ⏎
/bin/sh: can't access tty; job control turned off
/ # /bin/hostname ⏎
testing

なお、このconfig構造体も含め、各サブ構造体はJSONでのシリアライズが可能なタグが付いており、JSONから読み込むこともできます。 コンテナ構造体を使えば、プログラムを使って外部からコンテナを操作することも可能です。

// コンテナ内部で動作しているプロセスIDのリストを[]int形式で返す
processes, err := container.Processes()
 
// CPU、メモリ、I/O、コンテナの統計情報取得
stats, err := container.Stats()
 
// コンテナを停止
container.Pause()
 
// コンテナを再開
container.Resume()
 
// コンテナのinitプロセスにシグナル送信
container.Signal(signal)

まとめ

最終回として、最近話題になることが多いコンテナについて説明しました。 どちらかというと、Kubernetes、Docker Swarm、Mesosといった大規模なオーケストレーションの方面に話題がシフトしていっていますが、今回の話はその下で行われている基礎の説明になります。

コンテナはOSが持つリソースに「壁」を作って、そのプロセス専用の環境を作ることでした。 連載ではネットワーク、ファイルシステム、プロセス、並列処理、メモリをそれぞれ何回かに分けて紹介してきました。 コンテナが扱うものはそのすべてです。

付録:この連載で扱わなかった低レイヤの話題

Go言語でサポートしていないPOSIXの機能

次のような機能はGo言語の標準ライブラリでサポートしていないため説明しませんでした。 例えば、共有メモリのシステムコールも定数だけはコードにあったりはしますが、Go言語のランタイムにないということはあまり需要もなかったり、代替手段があったりします。

  • POISXの共有メモリ
  • POSIX MQ
  • POSIX非同期I/O
  • UDPConn.RecvMsgUDPConn.SendMsgの制御命令バイト列の読み書き

IEEE752の浮動小数点数や数値の表現、SIMD

数値周りも、本来であればきちんと理解すべきトピックです。しかし本連載ではCPU内部の話は取り扱わず、OSとのやりとりに関係する部分にフォーカスしたこともあり、システムで数値をどう扱っているかについては触れませんでした。

Go言語でのSIMDは昔よりはやりやすくなっていて、x86命令のバイナリ表現を手書きする必要はなくなってきていますが、アセンブリを書かなければならない点は変わっていません。

ロギング

ロギングに利用される定番ツールとしては、syslog/logstash/fluentdなどがあります。 syslogは、かつてはシステムプログラミングの話題の中で説明されることもありましたが、現代においてはデータ処理のパイプラインを担うミドルウェアとして扱われることが多く、OSのサービスからは少し離れます。 そのため、本連載では特に取り上げませんでした。

GUI

GUIは、OSごとに差異が大きすぎる上に、それなりに中身のある話をするとなるとボリュームが極めて大きくなるので、本連載では取り扱いませんでした。 おそらくフォントのハンドリングとテキスト描画だけで本が書けます。

暗号化/乱数

OSには、暗号論的擬似乱数生成器など、暗号化にかかわるAPIもいくつかあります。 macOSのSecurity FrameworkであるSecKeychainAddGenericPassword()や、WindowsのCryptProtectData、GnomeキーリングやKwalletなどのセキュアなデータ保存など、便利な機能がたくさんありますが、OS間の差異も大きく、暗号も背後にある理論的な説明が多くなりすぎてしまうため本連載では取り上げませんでした。

Go言語で作成したプログラムのブートストラップ

システムプログラミングやカーネルの本の締め括りとして必ずといっていいほど取り上げられるのは、プログラムの起動シーケンスの説明です。 興味がある方は、英語ですが、次のブログ記事などをご覧ください。

あとがき

20回に渡り、Go言語によるシステムプログラミングについて取り上げてきました。 連載にあたって心がけたのは次の3点です。

  • システムコール周辺の関数の使い方を紹介するだけではなく、その後ろで一生懸命仕事をしているカーネルのようすが見えるようにする
  • 2020年が近い年に新しく書き下ろすので、過去の本になかった新しいトピックを入れる(古いトピックを削る)
  • Linuxなどの特定の環境だけでなく、WindowsやmacOSを使っている人でもなるべく幅広く読めるようにする

Go言語を題材として選択したのはランタイムの奥まで気軽にのぞける言語だからです。Go言語そのものの説明が目的ではなかったため、Go言語を学ぼうと思っていなかった人にはとっつきにくい内容もあったかもしれません。 しかし、コンピュータシステムを学ぶ言語の選択肢が事実上C言語ばかりというなかで、間口を広げる貢献はできたと思います。 次は皆さんが、自分の得意な言語でトライしてみてください。

記事の執筆にあたっては、会社のメンバーとの会話や社内の読書会、プログラマー仲間との普段からのチャットなどから学んだこと、着想を得たことが助けとなりました。 時には、分からないことを質問して教えてもらったり、原稿のレビューをしてもらったりと、一人では超えられなかった壁を超えることができました。 また、編集の鹿野さんには、日本語の校正にとどまらず、読者に伝わりやすくするために説明の順序を大幅に入れ替えたり、雪だるまの挿絵をたくさん入れていただきました。 もちろん、完走できたのは、最後までお付き合いしていただき、時には厳しく指摘もしていただいた読者のみなさんのおかげです。 どうもありがとうございました。

脚注


カテゴリートップへ

この連載の記事

Go言語のメモリ管理


Goならわかるシステムプログラミング
第19回

Go言語によるプログラマー視点のシステムプログラミング

ソフトウェアにとってメモリは不可欠です。 実行する命令も、メモリにロードしなければ実行できません。 ソースコードに書かれた定数値も、いったんメモリにロードしないと使えません。 関数を呼び出すにも、スタックと呼ばれるメモリ領域が必要です。 スタック以外に、ヒープと呼ばれるメモリ領域が必要なこともあります。

今回は、Go言語のプログラマーが作成するプログラムの下で、どのようにメモリが管理され利用されるかを探ります。 Go言語のメモリ管理というとガベージコレクターの話を思い起こすかもしれませんが、ガベージコレクターについては本連載では取り上げません。

メモリ確保の旅

コンピューターに接続されている物理的なメモリチップが、どのような過程を経てプログラムで使われるのか、順番に見ていきましょう。

(1): カーネル

最近のオペレーティングシステムでは複数のプロセスを同時に実行できます。 それらのプロセスのそれぞれに、そのプロセスだけが使うメモリ空間があります。 各プロセスのメモリ空間は、物理的なメモリとどう対応しているのでしょうか。

いま、メモリが8GBのマシンがあり、そのうちの1GBバイトをOSが消費しているとします。 残り7GBを使って、1GBずつのメモリを消費する7個のプロセスを順番に起動し、奇数番目に起動したプロセスを終了しました。残りのメモリは、1GBずつ細切れになった4GBです。 この状態で、4GBのメモリを消費するプロセスを追加で起動するとどうなるでしょうか? (以降、話を単純にするため、インテル製の64ビットCPUの場合について説明します。)

現代のOSでは、この追加のプロセスを問題なく起動できます。 その秘密は、CPUに内蔵されているメモリ管理ユニット(MMU)仮想メモリの仕組みです。 プロセスはメモリを読み書きするのに物理的なアドレスを直接使っているわけではなく、プロセスごとに仮想的なメモリアドレスがあり、それを使ってメモリにアクセスしているのです。

仮想メモリアドレスから実際の物理アドレス上のデータへのアクセスには、ページテーブルと呼ばれる階層型のデータ構造が使われます。 「ページ」というのは、メモリを管理する単位です。メモリ全体は4KBずつの「ページ」に分けられています。 それぞれのページが物理メモリのどのアドレスに確保されているかを示すのがページテーブルです。 OSは、プロセスがメモリを確保すると、このページテーブルを用意します。 仮想メモリのおかげで、物理的な保存領域が1GBずつの細切れになっていても、プロセスから見ると4GBのフラットなメモリ領域が確保されているように見えるのです。

ページテーブルによる変換が挟まるので遅くなるように思えますが、実際には変換テーブルをキャッシュして高速化する仕組みがCPUには備わっています。 このキャッシュをTLB(Translation Lookaside Buffer)と呼びます。

メモリ管理のこの部分は、Go言語から直接触れることはできません。とはいえ、パフォーマンスに影響を与えうるため、ページサイズの情報を返すAPIが用意されています。

package main
 
import (
    "fmt"
    "os"
)
 
func main() {
    fmt.Printf("Page Size: %d\n", os.Getpagesize())
}

(2): プロセスのメモリ空間

プロセスは、起動するとOSからメモリをもらいます。 OSは、プロセスごとに仮想メモリの領域を確保します。

確保される領域の大きさは、アドレスを示す番地の桁数によるので、基本的にはCPUのビット数に基づきます。 とはいえ、CPUのビット数で表現しうる範囲のメモリ領域へ自由にアクセスできるわけではなく、扱えるメモリの量とCPUのビット数は必ずしも一致しません。 たとえば、現在主流のインテルアーキテクチャの64ビットモードだと、リニアに扱える仮想メモリ長は47ビット(128テラバイト)が2つで、合計256テラバイトです。 ただし、実際にはWindowsもLinuxも、1プロセスあたりの最大のプロセスのメモリサイズは128テラバイトに制限されています。 そのため、プロセスから見える仮想メモリ空間の範囲は、128テラバイト分ということになります。

いま、プログラムAとプログラムBの2つのプロセスを起動すると、それぞれのプロセスに0番地~0x00007fffffffffff番地まで広がる空間が見えます。 同じ0番地であっても、プログラムAとプログラムBが見ている実際の物理的なメモリの場所は違います。 他のプログラムのメモリ領域は、覗き見たり書き込んだりできません。

47ビットの空間すべてをプロセスが自由に使えるわけではありません。「メモリをこれだけください」とカーネルにお願いして、47ビットの空間の一部と物理メモリの対応付けをしてもらいます。 現在は多くてもせいぜいギガバイト単位でメモリを使うプログラムがほとんどでしょう。 カーネルはこの要求されたサイズのみを、仮想メモリの仕組みを使って確保し、物理メモリと対応付けます。

ユーザーのメモリ空間は大きく3つの連続したメモリ領域に分かれます。 その間の空きスペース分はメモリの確保が行われません。 ユーザーのメモリ空間は図のようになっています。

若い番地のほうから、プログラム、プログラムの静的変数などが置かれます。 その先の番地に、カーネルから動的にもらったメモリ領域が置かれていきます。 中段には共有ライブラリがマッピングされて置かれます。

最上段にはカーネルがマッピングされ、通常はその下からアドレスが若くなる方向にスタックメモリが確保される、という説明をよく見かけますが、Go言語ではスタックメモリの管理は独自に行っているため、必ずしもこれとは一致しません。

空きスペースの領域はこれから説明するようなシステムコールで動的に割り当てたメモリとして使われます。

(3): システムコール

プロセスができると、隙間がなくフラットなプロセス固有のメモリ空間ができること、そして、仮想メモリ空間の中にプログラムが実際に利用するメモリのブロックができることを説明しました。 次は、このメモリブロックをOSに依頼してもらってくるためのシステムコールについて説明します。

POSIX系OSでメモリブロックの確保に使うシステムコールは、すでに何度か登場しているmmapです。 Go言語では、この連載の第12回「ファイルシステムと、その上のGo言語の関数たち(3)」でファイルをメモリ空間に配置するときに紹介した、syscall.Mmapを使います。 このシステムコールは、対象のファイルを指定せずにアノニマス(無名)フラグを付けて実行すると、ファイルを読み込まずに指定サイズのメモリブロックを確保します。 これによって実行時に必要な動的メモリを確保できます。 mmap以外にも、POSIX系OSには、ヒープサイズを拡張するbrkおよびsbrkという、割り当て済みのメモリのサイズを変更するシステムコールがありますが、これらはGo言語のランタイムでは使われていません。

Go言語のWindows実装では、mmap相当のCreateFileMappingではなく、アノニマスフラグを付けたときのmmapの挙動とほぼ同じVirtualAllocを使います。

なお、mmapシステムコール自体はマッピングしたメモリブロックを仮想メモリのどこのアドレスに配置したいかのヒント情報を指定できますが、これはメモリマップを管理している言語ランタイム向けの機能ですし、通常のアプリケーションで使う時は0を設定して自動設定にします。 Go言語のsyscall.Mmapでは最初から指定できません。

(4): ランタイム:ヒープメモリ

OSカーネルが物理メモリと仮想メモリのマッピングを管理しており、プロセスのメモリ空間へOSカーネルからシステムコールによってメモリを持ってくるところまで説明しました。 ここまでだと、まだメモリのかたまりが手に入っただけです。 ユーザーコードから扱いやすい形式で適切にメモリを確保したり解放したりするのはランタイムの仕事です。

それに、OS内部でのメモリ確保はコストのかかる可能性のある処理です。 物理メモリが足りなければ、優先度の低いメモリ領域を選んだり、HDDなどのストレージにスワップアウトしたり、それでも必要なメモリを確保できなければ他のプロセスを強制終了(LinuxのOOMキラー)させたりするといったことが裏で行われる可能性があります。

そこで、mmapなどのシステムコールを使って比較的大きめのメモリブロックをOSからもらっておき、細かいメモリのやりくりは効率よくユーザーランドの中で行って、不要になったらOSにまるごと返却したり、あるいは足りなければOSからメモリをもらったりします。 そのために使うメモリ領域としては、ヒープとスタックの2種類がありますが、まずはヒープから紹介します。

C言語でメモリ確保に使う有名な標準関数はmalloc()です。 これは必要な容量を指定するとそのサイズのメモリブロックが確保され、そのポインタを返す関数です。 mallocの仲間にはjemalloc、TCMallocなどがあり、Go言語ではTCMallocを採用しています。 TCMallocはGo言語と同じGoogle製です。

TCMallocは、マルチスレッド時代に合わせて設計されているmalloc実装で、主な特徴は2つあります。

  • 32キロバイト以下の小さなオブジェクトについては、スレッドごとにメモリブロックを管理する。これにより、ロックなどのスレッド競合によるパフォーマンス劣化を防ぐ
  • 32キロバイトよりも大きなオブジェクトについては、4キロバイト単位に丸め、共有の中央ページヒープで管理してメモリの無駄を減らす

32キロバイトより大きなオブジェクトは、中央ページヒープを直接利用してメモリが確保され、返されます。 中央ページヒープは、1ページあたり4キロバイト単位で、255ページまでのサイズごとの空きメモリブロックのリストになっています。 256ページを超えるオブジェクトはサイズごとに分類されずに1つのリストになっています。 たとえば、1001キロバイトのメモリが必要であれば、1004に丸めて251ページ長のリストを探しに行き、返却されている空きメモリがあればそれを返し、空きがなければ最低1メガバイト単位でOSからメモリをもらって、必要なサイズに切り出して返します。

小さなオブジェクトについても、同様に小さな単位の「クラス」という分類で空きメモリのリストを持っています。 これらはロックが必要がないため高速に処理できます。 大きなオブジェクトと同様に、リクエストされたサイズに近いクラスの空きリストがあればそこから返します。 なければ、スパンと呼ばれる空きメモリのストックから切り出して返します。 それもなければ、中央ページヒープからメモリをもらいます。

Go言語のヒープメモリの管理そのものの説明はネット上にもほとんどありませんが、下記の記事が参考になるでしょう。

(5): ランタイム:スタック

関数を呼ぶと、リターンアドレスや新しい関数のための作業メモリ領域(コンパイル時にサイズが分かるので固定量)を含む「スタックフレーム」と呼ばれるメモリブロックが確保されます。 このスタックフレームは、スレッドごとにあらかじめ確保されているメモリブロックに対して順番に追加したり削除したりされるだけなので、割当のコストはほぼゼロです。

デフォルトのスタックメモリのサイズは、Linuxではulimit -sで設定します。OSによって初期値が違うようですが、だいたい8MBぐらいが多いようです。 Windowsの場合はコンパイル時のフラグで設定され、32ビット、64ビット問わずデフォルトで1MBです。 ネイティブのOSスレッドは、作成時にこれだけの固定のメモリを確保する必要があるため、そのぶんだけ作成コストが上乗せされます。

これに対し、Go言語のgoroutineでは、最初は4キロバイトの小さなサイズのスタックを確保します。 OSスレッドと比べると極めて小さいサイズであり、goroutineの高速な起動にも貢献しています。 もし関数呼び出しで大きなサイズが必要なことがわかれば、別にスタックフレームを準備し、そちらに引数をコピーして、あたかもスタックがはじめて使われていたかのような状態で関数呼び出しを行います。 これにより、スタックサイズがギガバイトサイズになっても問題なく再帰ループが回せます。

なお、ランタイムのコードで次のコメントをよく見かけますが、これは、この関数の呼び出しではスタックの操作はしないという指示になります。

//go:nosplit

(6): ユーザーコード

ようやくユーザーコードにやってきました。 Go言語では、構造体やプリミティブの初期化の方法がいくつか提供されています。

// プリミティブのインスタンスを定義
var a int = 10
 
// 構造体のインスタンスをnewして作成
// 変数にはポインタを保存
var b *Struct = new(Struct)
 
// 構造体を{}でメンバーの初期値を与えて初期化
// 変数にはインスタンスを保存
var c Struct = Struct{"param"}
 
// 構造体を{}でメンバーの初期値を与えて初期化
// 変数にはポインタを保存
var d *Struct = &Struct{"param"}

配列やスライスの定義もいくつかあります。 varで定義するときは型を明示しますが、右辺で型が明確にわかるときは、:=と書くことで変数宣言と代入を同時に行えます。

// 固定長配列を定義
a := [4]int{1, 2, 3, 4}
 
// サイズ等を持ったスライスを定義
b := make([]int, 4, 8)
 
// バッファー無しのチャネル
c := make(chan string)
 
// バッファー有りのチャネル
d := make(chan string, 10)

これらのコードでは、「メモリを確保する」ことをコンパイラやランタイムに指示していることになります。

C/C++の場合は、ポインタを使わずにローカル変数として宣言するとスタックにメモリが確保され、newmalloc()を使うとヒープメモリにメモリが確保される、というシンプルな仕組みになっています。 Go言語の場合、どちらに置くかはコンパイラが自動的に判断します。newで作っても、その関数内でしか利用されなければスタックに確保されます。ローカル変数として宣言しても、そのポインタを他の関数に渡したり、関数の返り値として返すとヒープに置かれます。 そのため、C/C++で発生する「ローカル変数のポインタを関数の返り値として返すと、呼んだ側からアクセスしに行ったときにはもうスタックのフレームが巻き戻されて無効なメモリになっており、実行時エラーで落ちる」という問題は置きません。

メモリがスタックとヒープのどちらに確保されているかは、ビルド時に-gcflags -mを渡すと表示されます。

$ go build -gcflags -m sample.go ⏎
./sample.go:7: can inline sub
./sample.go:23: inlining call to sub
./sample.go:10: &b escapes to heap
./sample.go:9: moved to heap: b
./sample.go:17: b escapes to heap
./sample.go:16: make([]int, 10) escapes to heap
./sample.go:14: test_make make([]int, 10) does not escape
./sample.go:17: test_make ... argument does not escape
./sample.go:24: a escapes to heap
./sample.go:24: *b escapes to heap

スタックのほうが高速なので、デフォルトではスタックを選択しようとします。 しかし、外部の関数に渡したり返り値で使おうとしたりすると、宣言した関数のスコープよりも変数の寿命が長くなる可能性があるため、ヒープに逃がす(escape)ことがわかります。 ただし、fmt.Printlnのような表示しか行わずデータを変更しない関数でもヒープにされてしまいます。 データのサイズと使われ方次第ですが、参照専用で変更しない場合は、ポインタではなくて実体のコピーを受け取るような関数にしたほうがパフォーマンスが上がることもあります。 他の関数に渡しただけでスタックからヒープになってしまうのは、まだ最適化不足とも言えるので、今後のGoのコンパイラでは改善して欲しいところです。

Go言語のFAQにあるHow do I know whether a variable is allocated on the heap or the stack? (ヒープとスタックのどちらに変数が割り当てられているのか知る方法があるのか?)という項目には、Go言語ではコンパイラが安全側に倒しながら判断するため、どちらに置くかはプログラマーが意識する必要がない、と書かれています。 C/C++では、ヒープの場合はdeletefree()で明示的に削除したり、あるいはスマートポインタで削除されるように仕向ける必要があります。 「どちらか適切なほうに自動で割り当てられる」というGo言語の方針を実現するには、ヒープであってもスタックと同じように不要になったら自動で解放するガベージコレクタが不可欠になります。

Go言語のスライスは他の言語にはあまり見られない特殊なデータです。 実際には裏に配列があり、そこを参照するウインドウ(対象の配列、スタート位置、終了位置の3つの情報を持つ)のようなデータです。 この裏の配列が必要に応じて新しく割り当てられたりしますが、このあたりの話は日本語のウェブサイトの記事も多くあるため、割愛します。

OSの小話 – 仮想メモリが実現する高度な機能

仮想メモリには、フラグメント化されたメモリをフラットに見せかけるアドレス変換以外にも数多くの機能があります。 それらを組み合わせることで、物理メモリのサイズを超える量のメモリを扱うことができますし、メモリの無駄を減らして効率よくアプリケーションに配ることもできます。

たとえば、ページテーブルのエントリには、メモリの該当ページに対する読み書きが行われたときにカーネルに通知するためのフラグや、書き込みされたことを示すフラグが設定できます。 これらのフラグを利用することで、高度なメモリ管理が実現できます。 その一例として、デマンドページングと呼ばれる、「メモリを確保したと見せかけて、はじめてアクセスがあったときに実際に取得しにいく」という仕組みがあります。 デマンドページングでは、たとえばメモリを1GB取得したとしても、即座に物理メモリを1GB取得しに行かず、取得していない部分についてはアクセスがあったときにOSからメモリを受け取るようにします。 メモリを確保しても実際に使うまでにはタイムラグがあるので、デマンドページングによって初期化コストが削れ、使用メモリのピークが異なるプロセス同士でうまくメモリを融通しやすくなります。 デマンドページングは、初期化時だけではなく、優先度の低いメモリ領域をディスクに退避して(スワップ)必要なときに書き戻す際にも使われます。

また、第12回「ファイルシステムと、その上のGo言語の関数たち(3)」コピーオンライトというメモリ節約のテクニックが使われていることを紹介しましたが、これもページテーブルのフラグで実現されています。

仮想メモリには、複数のプロセスでシステムの同じ共有ライブラリをロードしている場合にメモリ消費を抑える仕組みもあります。 それぞれのプロセスの仮想メモリには同じライブラリが個別にロードされているように見えますが、ページテーブルを使って同じアドレスを参照することで、1つ分のメモリしか消費しないようにするのです。 このようなメモリの使われ方は、メモリの使用方法を観察するツールで確認できます。

Linuxのカーネルの書籍を読むと、メモリ管理のセクションには、ディスクの読み書きをメモリに保存しておくページキャッシュ、優先順位を決めてメモリの解放、同一サイズのオブジェクトを効率よく管理する仕組み(スラブアロケータ)、大きいバッファを管理するバディ・システムなど、ここで触れたもの以外にもいろいろな手法が登場します。さらに、macOSでは、メモリの圧縮を動的に行う(おそらく圧縮機構付きスワップ)といったトピックもあります。しかし、アプリケーションのメモリ管理からは少し離れるので、これらの機能については本連載では割愛します。

まとめ

今回は、OSのメモリ管理から、Go言語のプログラム側のメモリの取扱まで一通り紹介してきました。 比較的高速なコードを出力するGo言語にあって、パフォーマンス上の問題として取り上げられやすいのがメモリのアロケーションです。 高速をうたうライブラリはみな、ベンチーマークでメモリのアロケート回数とバイト数も出力し(-benchmemオプションを付与すれば出力される)、いかに回数が少ないかをアピールしています。 メモリ割り当て回数がゼロ(0 allocs/op)はGo言語界においては勲章のひとつのように扱われることすらあります。

「メモリ確保は裏でこれだけ頑張っている高価なオペレーションだ」というのが伝わったなら、今回の記事の役目は果たせたと思います。

次回は最終回になります。


カテゴリートップへ

この連載の記事

『異能vation』公募スタート。今年度はICT分野の”異能なアイデア”そのものも応募可能に!


通称”変な人プロジェクト”が2017年6月30日まで公募受付中。今年は募集部門が増えた!!

2017年05月22日 23時55分更新

文● 編集部

画像は『異能vation』公式サイトより

総務省が実施する『変な人プロジェクト』今年度も公募開始

 2014年(平成26年)度から総務省がスタートし、“国が変な人認定してる”と毎度話題の多い、総務省 独創的な人向け特別枠『異能vation』プログラム。通称『変な人プロジェクト』としても知られる本プログラムが、今年度も5月22日から応募受付を開始したと、業務実施機関である角川アスキー総合研究所が発表した。

 『異能vation』というプログラムは、ICT(情報通信技術)分野において”破壊的価値”を創造する、”奇想天外”で”アンビシャス”な技術課題への挑戦を支援することが目的。選考通過者は300万円を上限とする金銭的支援や、スーパーバイザーからの助言などの人的支援を受けることができる。選考基準においては、その挑戦の成功が重要視されるのではなく、むしろ価値ある失敗に挑戦することを恐れず、技術課題へのアプローチや解決の道筋を示せるチャレンジャーを奨励するというのが本プログラムの文化と言える。プログラムの概要については『異能vation』公式サイト (http://www.inno.go.jp) をお読みいただきたい。

今年からは”こだわりの技術”や”面白いアイデア”だけを応募することも可能に

 本年度のプログラムに関する注目点は、技術課題に挑戦する”人”を支援する『破壊的な挑戦部門』の公募がこれまで通り継続されるとともに、新たに”アイデアそのもの”を公募する『ジェネレーションアワード部門』が設けられているという点。

画像は角川アスキー総合研究所の発表資料より

 新設の『ジェネレーションアワード部門』は、ICT分野における”面白いアイデア”や”実現してほしい技術課題”を募り、プログラムを介してそうした発想と人・企業がつながることにより、新しいイノベーションの芽を創出しようというのが狙いのようだ。こちらの新設部門では、選考を経て『異能ジェネレーションアワード』の表彰が行われるほか、プログラム協力協賛企業から副賞(20万円)および企業特別賞などが提供される予定とのこと。

 『異能ジェネレーションアワード』は、下記に挙げる分野ごとに表彰が行われることになっている。アイデアなら何でもありというわけではさすがになく、あくまでも”ICTに関する分野”に関するアワードであるという点には留意いただきたい。

『異能ジェネレーションアワード』審査分野(ICTに関する)

  1. 情報通信
  2. 宇宙
  3. 医療
  4. 教育
  5. 農業・漁業・林業などの第一次産業と流通
  6. セキュリティ
  7. センシング・データ
  8. 電波とその有効利用
  9. 映像・音声
  10. バイオテクノロジー
  11. 防災
  12. 流通
  13. ロボット・AI(人工知能)
  14. IoT (Internet of Things)
  15. アプリ
  16. その他業務実施機関が思い付きもしない分野

“他薦”という応募方法で誰にでも参加できるのが『異能vation』

 実施される両部門ともに、応募方法は”自薦”のほか、”他薦”という方法ができることも『異能vation』プログラムの特徴だ。被推薦者(技術課題へ挑戦する本人やアイデアを持っている当人のこと)に対して「推薦しておいたよ」と伝えない、というルールを守れば、他薦システムによって誰にでも応募することができる。

 アイデア自体の応募をすることも可能となり、さらに多くの人たちにチャンスがひらけた今年度の『異能vation』プログラム。公募の締切は2017年6月30日(金)となっているので、応募を検討されている方は、お早めに。

■関連サイト



カテゴリートップへ

Go言語と並列処理(2)


Goならわかるシステムプログラミング
第17回

Go言語によるプログラマー視点のシステムプログラミング

今回は、Go言語の並行・並列処理のかなめともいえるgoroutine(ゴルーチン)周りを掘り下げていきます。

goroutineは、前回の記事で軽く触れたように、軽量スレッドと呼ばれるものです。 そこで、まずはこの軽量スレッドと通常のOSのスレッドがどう違うのかを説明します。

そのうえで、goroutineの低レベルな機能を扱うためのruntimeパッケージ、 goroutineのデータ競合を発見するRace Detecter、 さらに高度な非同期処理を書くときに必要になるsyncパッケージおよびsync/atomicパッケージの使い方を紹介します。

スレッドとgoroutineの違い

スレッドとは、プログラムを実行するための「もの」であり、OSによって手配されます。

プログラムから見たスレッドは、「メモリにロードされたプログラムの現在の実行状態を持つ仮想CPU」です。この仮想CPUのそれぞれに、スタックメモリが割り当てられています。

一方、OSやCPUから見たスレッドは、「時間が凍結されたプログラムの実行状態」です。この実行状態には、CPUが演算に使ったり計算結果や状態を保持したりするレジスタと呼ばれるメモリと、スタックメモリが含まれます。

OSの仕事は、凍結状態のプログラムの実行状態を復元して、各スレッドを順番に短時間ずつ処理を再開させることです。 その際の順番や、一回に実行する時間タイムスライスは、スレッドごとに設定されている優先度によって決まります。 実行予定のスレッドはランキューと呼ばれるリストに入っており、なるべく公平に処理が回る用にスケジューリングされます。 複数のプログラムは、このようにして、時間分割してCPUコアにマッピングされて実行されるのです。

スレッドがCPUコアに対してマッピングされるのに対し、goroutineはOSのスレッド(Go製のアプリケーションから見ると1つの仮想CPU)にマッピングされます。 この点が、通常のスレッドとGo言語の軽量スレッドであるgoroutineとの最大の違いです。 両者にはほかにも次のような細かい違いがあります。

  • OSスレッドはIDを持つが、goroutineは指定しなければ実際のどのスレッドにマッピングされるかは決まっておらず、IDなども持たない
  • OSスレッドの1〜2MBと比べると、初期スタックメモリのサイズが小さく(2KB)、起動処理が軽い
  • 優先度を持たない
  • タイムスライスで強制的に処理が切り替わることがないが、コンパイラが「ここで処理を切り替える」という切り替えポイントを埋め込むことで切り替えを実現している
  • (IDで一意にgoroutineで特定できないため)外部から終了のリクエストを送る仕組みがない

GoのランタイムはミニOS

OSが提供するスレッド以外に、プログラミング言語のランタイムでスレッド相当の機能を持つことには、どんなメリットがあるのでしょうか。 Go言語の場合、「機能が少ない代わりにシンプルで起動が早いスレッド」として提供されているgroutineを利用できることで、次のようなメリットが生まれています。

  • 大量のクライアントを効率よくさばくサーバーを実装する(いわゆるC10K)ときに、クライアントごとに1つのgoroutineを割り当てるような実装であっても、リーズナブルなメモリ使用量で処理できる
  • OSのスレッドでブロッキングを行う操作をすると、他のスレッドが処理を開始するにはOSがコンテキストスイッチをして順番を待つ必要があるが、Goの場合はチャネルなどでブロックしたら、残ったタイムスライスでランキューに入った別のgoroutineのタスクも実行できる
  • プログラムのランタイムが、プログラム全体の中でどのgoroutineがブロック中なのかといった情報をきちんと把握しているため、デッドロックを作ってもランタイムが検知してどこでブロックしているのかを一覧で表示できる

goroutineは、OSのスレッドと比べると機能的にはシンプルですが、OSのカーネルが行っていることとGo言語のランタイムが行っていることは、大雑把に次のように対応しています。

用語の対応表
M: Machine 物理CPUコア
P: Process スケジューラ(ランキュー)
G: goroutine プロセス

つまり、Go言語のランタイムは、goroutineをスレッドとみなせば、OSと同じ構造であると考えることができます。

実際、Goのランタイムの内部には、OSがスレッドのスケジューラを持っているのと同様に、goroutineのスケジューラがあります。 具体的には、OSのスレッド(M)ごとに、タスクであるgoroutine(G)のリストがあり、実行中のスレッド上で順番にタスクをどんどん切り替えて実行していきます。 ランキューなどのスレッドが行う作業を束ねるものは、Process(P)と呼ばれます(システムプログラミングにおけるプロセスとは別物です)。 グローバルなタスクのキューもあり、暇になったプロセスはそこからタスクを取得してきます。 また、仕事に偏りがあると、それを平準化する機構も持っています。 また、現代のカーネルはCPUのM個のコア数に対して同時にN個の複数の処理ができるM:Nモデルになっていますが、Go言語のgoroutineもOSスレッドの数Mに対してM:Nモデルになっています。

なお、Goの内部実装についてはネットを探すといくつか情報があります。 さらに詳しいことは、これらの情報を参照してください。

runtimeパッケージのgoroutine関連の機能

軽量スレッドであるgoroutineを使うには、前回説明したように、goを付けて関数呼び出しを行うだけです。 しかし、場合によっては、ベースとなるOSのスレッドに対して何らかの制限を課すといった、より低レベルの操作をしたいこともあります。 runtimeパッケージには、そのようなときに使える低レベルの関数がいくつかあります。

なお、runtimeパッケージにはgoroutineのプロファイリング用の関数もいくつかありますが、集計機能などを自分で実装する必要があります。 goroutineのプロファイルを行うときは、ドキュメントにあるように、runtime/pprofなどの既成のパッケージを利用すべきです。

runtime.LockOSThread() / runtime.UnlockOSThread()

runtime.LockOSThread()を呼ぶことで、現在実行中のOSスレッドでのみgoroutineが実行されるように束縛できます。 さらに、そのスレッドが他のgoroutineによって使用されなくなります。 これらの束縛は、runtime.UnlockOSThread()を呼んだり、ロックしたgoroutineが終了すると解除されます。

この機能が必要になる状況としては、メインスレッドでの実行が強制されているライブラリ(GUIのフレームワークや、OpenGLとその依存ライブラリなど)をGo言語で利用する場合が挙げられます。 「現在実行中のスレッド」が確実にメインスレッドかどうかは、実行中にはわかりませんが、mainパッケージのinit()関数は確実にメインスレッドで実行されるため、それを利用してメインスレッドを固定することができます。

Goのランタイムでは、シグナルを受け取るスレッドを固定するためにこれらの関数を使っています。

runtime.Gosched()

現在実行中のgoroutineを一時中断して、他のgoroutineに処理を回します。 goroutineには、OSスレッドとは異なり、タスクをスリープ状態にしたり復元したりする機能がありません。ランキューの順番が回ってきたら、何ごともなく処理が再開します。

この関数は、ロックを使わずに目的の変数が変更されるのを待つといった用途で使えるかもしれませんが、実際にはあまり使うこともないでしょう。 Go言語のソースコードを見ても、ガベージコレクタが自発的に処理を手放すことでGCの停止時間を短縮させるのに使っているぐらいです。

runtime.GOMAXPROCS(n) / runtime.NumCPU()

ウェブに残る古いGo言語の解説記事では、このruntime.GOMAXPROCS()をよく見かけると思います。 これは、同時に実行するOSスレッド数(I/Oのブロック中のスレッドは除く)を制御する関数です。 Go 1.4までは、デフォルトのPROC数が1となっており、マルチコアを活用するには必ずこの関数を呼ぶ必要がありました。 runtim.NumCPU()でCPU数が分かるので、これをruntime.GOMAXPROCSに渡すのが定石でした。

Go 1.5からは、デフォルトでruntime.GOMAXPROCS()が設定されるようになったので、特別な場合を除いてわざわざ設定する必要はありません。 しかし、最速を狙おうとすると、このデフォルト値の半分に設定するほうがスループットが上がる場合があります。 現代のCPUのいくつかは、余剰のCPUリソースを使って1コアで2つ以上のスレッドを同時に実行する機構(ハイパースレッディングや、SMT(Simultaneous Multi-Threading))を備えています。 そのような機構を利用している場合、1コアで2つのヘビーな計算を同時に実行すると、CPUコアのリソースを食い合ってパフォーマンスが上がらないことがあります。 筆者が執筆で使っているMacBookPro(Core i7の2014モデル)の場合、runtime.NumCPU()は8を返しますが、これも物理コア数が4で、SMT機能による論理コア数がこの返り値となっています。

次の表は、実際にGo言語でCPUヘビーな計算を並列に行わせて(それぞれのgoroutineでは同じ計算をしている)、完了にかかる時間を計測した結果です。 1から4とくらべて、8並列の場合のパフォーマンスが落ちていることが分かります。

CPU不可の高い処理(円周率10万桁計算)の終了時間比較
1 11.834
2 11.971
4 12.294
8 15.181

Go言語のruntime.NumCPU()はSMTを含めた論理コア数を返しますが、物理コア数を返すAPIはありませんので、プログラム側から自動設定はできません。 GOMAXPROCS環境変数によっても設定できるので、実行時にこの環境変数で設定するほうがよいでしょう。 また、goroutineを特定のコアに張り付けてリソースの食い合いが起きないように制御する機能は提供されていませんが、現代のOSのスケジューラは十分に賢いので、4並列で重い計算を行う場合は自動で別のコアに分散して計算してくれます。

Race Detector

Go言語には、データ競合を発見する機能があります。 この機能は、Race Detectorと呼ばれ、go buildgo runコマンドに-raceオプションを追加するだけで使えます。

Race Detectorを有効にしてGoプログラムを実行すると、次のようなメッセージが表示され、 競合が発生した個所と、競合した書き込みを行ったgoroutine、そのgoroutineの生成場所が分かります。

==================
WARNING: DATA RACE
Read at 0x0000011a7118 by goroutine 7:
  main.main.func1()
      /Users/shibu/.../mutex2.go:25 +0x41
 
Previous write at 0x0000011a7118 by goroutine 6:
  main.main.func1()
      /Users/shibu/.../mutex2.go:25 +0x60
 
Goroutine 7 (running) created at:
  main.main()
      /Users/shibu/.../mutex2.go:26 +0x93
 
Goroutine 6 (finished) created at:
  main.main()
      /Users/shibu/.../mutex2.go:26 +0x93
==================

syncパッケージ

前回の記事で紹介したように、チャネルとselectの2つの文法があればgoroutine間の同期には事足ります。 しかし、すでに他の言語で書かれている実績あるコードをGo言語で再実装する場合など、他言語にはないチャネルとselectで書き直すのは大変です。 そのようなときのために、並列処理をサポートするためのsyncパッケージが提供されています。

syncパッケージは、おそらく既存のAPIを参考にして実装されており、 多くのOSなどで並行・並列処理を同期させるための機能として提供されているものがいくつか含まれています。 参考までに、多くのOSでサポートされているPThreadのAPIとsyncパッケージの機能を比較してみます。

ネイティブスレッドのAPIとの比較
ロック pthread_mutex_* sync.Mutex
RWロック pthread_rwlock_* sync.RWMutex
複数スレッドの終了待ち なし sync.WaitGroup
条件変数 pthread_cond_* sync.Cond
一度だけ実行 pthread_once sync.Once
スレッドローカルストレージ pthread_key_*など なし
スピンロック pthread_spin_* なし
オブジェクトプール なし sync.Pool

上記の表を見ると、Goのsyncパッケージに欠けている機能がいくつかあります。

スレッドローカルストレージは、スレッドごとの領域にデータを保存できる機能ですが、利用するにはスレッドを識別できる必要があります。 goroutineはOSのスレッドとは異なり、IDを持たないため、スレッドローカルストレージが使えません。

スピンロックは、ビジーループでロックを獲得するロックです。 goroutineの軽量スレッドは、ブロックしてしまってもコンテキストスイッチが軽いため、スピンロックの重要性があまりなく、この機能が提供されていません。

なお、ロックとRWロックは機能として提供されていますが、ブロックせずにトライできないか試してみるpthread_mutex_try_lock()や、タイムアウト時間の設定ができるpthread_mutex_timedlock()は存在しません。

sync.Mutex / sync.RWMutex

マルチスレッドプログラミングでは、「メモリ保護のためにロックを使う」といった説明をされることがあります。 これはスレッドが同じメモリ空間で動くためですが、実際に保護するのは実行パスであり、メモリを直接保護するわけではありません。 sync.Mutexは、実行パスに入ることが可能なgoroutineを、排他制御によって制限するのに使います。

sync.Mutexを使うと、「メモリを読み込んで書き換える」コードに入るgoroutineが1つに制限されるため、不整合を防ぐことができます。 この、同時に実行されると問題がおきる実行コードの行(1行とは限らないが、プログラミング言語のブロックより小さいこともある)を、クリティカルセクションと呼びます。 マップや配列に対する操作はアトミックではないため、複数のgoroutineからアクセスする場合には保護が必要です。

次のコードでは、IDをインクリメントするコードが同時に1つしか実行されないようにしています。 マルチスレッドプログラミングでは「コードのどの箇所でコンテキストスイッチが発生しても不整合が置きないようにする」のが鉄則です。 id変数をCPUが読み込んだタイミングでコンテキストスイッチが発生すると、同じid値を参照するスレッドが複数できてしまい、同じidが生成されてしまいます。

package main
 
import (
    "fmt"
    "sync"
)
 
var id int
 
func generateId(mutex *sync.Mutex) int {
    // Lock()/Unlock()をペアで呼び出してロックする
    mutex.Lock()
    id++
    mutex.Unlock()
    return id
}
 
func main() {
    // sync.Mutex構造体の変数宣言
    // 次の宣言をしてもポインタ型になるだけで正常に動作します
    // mutex := new(sync.Mutex)
    var mutex sync.Mutex
 
    for i := 0; i < 100; i++ {
        go func() {
            fmt.Printf("id: %d\n", generateId(&mutex))
        }()
    }
}

sync.Mutexは内部に整数を2つ持つ構造体です。 Go言語では、構造体作成時にはかならずメモリが初期化されるため、上記の例のように特別な初期化を行わずに使えます。 ただし、値コピーしてしまうとロックしている状態のまま別のsync.Mutexインスタンスになってしまうため、他の関数に渡すときは必ずポインタで渡すようにします。コードの静的チェックツールのgo vetを実行すると、このsync.Mutexのコピー問題は発見できます。

Go言語には、そのコードブロックを抜けるときに忘れずに後処理を行うdeferがあるので、これと組み合わせて使うのがほとんどでしょう。 メモリ確保と解放、ファイルのオープンとクローズ、ロックの獲得と解放などの対となる動作はなるべく不可分に記述する、連続して書くというのが、間違いを防ぐためのベストプラクティスとして多くの言語で採用されています。

func generateId(mutex *sync.Mutex) int {
    // 多くの場合は次のように連続して書く
    mutex.Lock()
    defer mutex.Unlock()
    id++
    return id
 } 

Go言語Wikiには、Mutexとチャネルの使い分けについて次のようにまとめられています。

  • チャネルが有用な用途:データの所有権を渡す場合、作業を並列化して分散する場合、非同期で結果を受け取る場合
  • Mutexが有用な用途:キャッシュ、状態管理

sync.Mutexにはsync.RWMutexというバリエーションがあります。 この構造体にはsync.Mutexと同じLock()Unlock()に加えて、RLock()RUnlockというメソッドがあります。 Rが付くほうは、読み込み用のロックの取得と解放で、 「読み込みはいくつものgoroutineが並列して行えるが、書き込み時には他のgoroutineの実行を許さない」という方式でのロックが行えます。 Mutexの用途のうち、読み込みと書き込みがほぼ同時に行われるような状態管理の場合はsync.Mutexが、複数のgoroutineで共有されるキャッシュの保護にはsync.RWMutexが適しています。

なお、上記のコードにはバグがあります。 このバグを直す方法はいくつかありますが、一番簡単なのが、次節で説明するsync.WaitGroupを使う方法です。

sync.WaitGroup

sync.Mutex並に使用頻度が高いのがsync.WaitGroupです。 sync.WaitGroupは、多数のgoroutineで実行しているジョブの終了待ちに使います。

package main
 
import (
    "fmt"
    "sync"
)
 
func main() {
    var wg sync.WaitGroup
 
    // ジョブ数をあらかじめ登録
    wg.Add(2)
 
    go func() {
        // 非同期で仕事をする(1)
        fmt.Println("仕事1")
        // Doneで完了を通知
        wg.Done()
    }()
 
    go func() {
        // 非同期で仕事をする(1)
        fmt.Println("仕事2")
        // Doneで完了を通知
        wg.Done()
    }()
 
    // すべての処理が終わるのを待つ
    wg.Wait()
    fmt.Println("終了")
}

Add()メソッドを呼ぶとジョブ数が追加されます。 Add()メソッドは、必ずgoroutineなどを作成する前に呼びましょう。

Done()メソッドを呼ぶと残りジョブ数がデクリメントされていきます。 Wait()メソッドはすべてのジョブが完了するのを待つメソッドです。

チャネルよりもsync.WaitGroupのほうがよいのは、ジョブ数が大量にあったり、可変個だったりする場合です。 100以上のgoroutineのためにチャネルを大量に作成して終了状態を伝達することもできますが、これだけ大量のジョブであれば、数値のカウントだけでスケールするsync.WaitGroupのほうがリーズナブルです。

前節のサンプルコードは、終了待ちをしていないため、main()が終了したらプログラムが完了してしまいます。 sync.WaitGroup構造体を使えば、簡単に全タスクの終了待ちができます。

sync.WaitGroupも変数宣言だけで使えます。 値コピーしてしまうと正しく動作しない点もsync.Mutexと同じです。

sync.Once

sync.Onceは、一度だけ関数を実行したいときに使います。 初期化処理を一度だけ行いたいときに使う場合が多いでしょう。

package main
 
import (
    "fmt"
    "sync"
)
 
func initialize() {
    fmt.Println("初期化処理")
}
 
var once sync.Once
 
func main() {
    // 三回呼び出しても一度しか呼ばれない。
    once.Do(initialize)
    once.Do(initialize)
    once.Do(initialize)
}

Go言語には、init()という名前の関数がパッケージ内にあると、それが初期化関数として呼ばれる機能があります。 sync.Onceではなくinit()を使うほうが、初期化処理を呼び出すコードを書かなくても実行され、コード行数も減るので、シンプルです。 sync.Onceをあえて使うのは、初期化処理を必要なときまで遅延させたい場合でしょう。

sync.Cond

sync.Condは条件変数と呼ばれる排他制御の仕組みです。 これもロックをかけたり解除したりすることでクリティカルセクションを保護します。 sync.Condの用途には次の2つがあります。

  • 先に終わらせなければいけないタスクがあり、それが完了したら待っているすべてのgoroutineに通知する(Broadcast()メソッド)
  • リソースの準備ができ次第、そのリソースを待っているgoroutineに通知をする(Signal()メソッド)

後者の用途の場合、Goであればチャネルで用が済むので、主に使うことになるのは前者の用途でしょう。 Goの標準ライブラリでは、TLSやHTTP/2のライブラリがサーバーとのハンドシェイクが完了したり、サーバーからレスポンスがきたタイミングでワーカースレッドを起こすのに、sync.Condが使われています。

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    var mutex sync.Mutex
    sync.NewCond(&mutex)
 
 
    for _, name := range []string{"A", "B", "C"} {
        go func(name string) {
            // ロックしてからWaitメソッドを呼ぶ
            mutex.Lock()
            defer mutex.Unlock()
            // Broadcast()が呼ばれるまで待つ
            cond.Wait()
            // 呼ばれた!
            fmt.Println(name)
        }(name)
    }
 
    fmt.Println("よーい")
    time.Sleep(time.Second)
    fmt.Println("どん!")
    // 待っているgoroutineを一斉に起こす
    cond.Broadcast()
    time.Sleep(time.Second)
}

チャネルの場合、待っているすべてのgoroutineに通知するとしたらクローズするしかないため、一度きりの通知にしか使えません。 sync.Condであれば、何度でも使えます。また、通知を受け取るgoroutineの数がゼロであっても複数であっても同じように扱えます。

sync.Pool

sync.Poolは、オブジェクトのキャッシュを実現する構造体です。 一時的な状態を保持する構造体をプールしておいて、goroutine間でシェアできます。 sync.Poolは当然ながらgoroutineセーフです。この構造体は、fmtパッケージの一時的な内部出力バッファなどを保持する目的で導入されました。

sync.Poolの使い方は簡単で、Put()メソッドでキャッシュしたいデータを追加します。 Get()で、キャッシュからデータを取り出します。 出し入れするデータはinterface{}型なので、どのような要素でも入れられます。プールが空のときは、プール作成時に設定した関数で作ったインスタンスが返ります。

package main
 
import (
    "fmt"
    "sync"
)
 
func main() {
    // Poolを作成。Newで新規作成時のコードを実装
    var count int
    pool := sync.Pool{
        New: func() interface{} {
            count++
            return fmt.Sprintf("created: %d", count)
        },
    }
 
    // 追加した要素から受け取れる
    // プールが空だと新規作成
    pool.Put("manualy added: 1")
    pool.Put("manualy added: 2")
    fmt.Println(pool.Get())
    fmt.Println(pool.Get())
    fmt.Println(pool.Get()) // これは新規作成
}

なお、内部では要素はキャッシュでしかなく、(他の言語で言うところの)WeakRefのコンテナとなっています。 そのため、ガベージコレクタが稼働すると、保持しているデータが削除されます。 sync.Poolは、消えては困る重要なデータのコンテナには適しません。

package main
 
import (
    "fmt"
    "runtime"
    "sync"
)
 
func main() {
    var count int
    pool := sync.Pool{
        New: func() interface{} {
            count++
            return fmt.Sprintf("created: %d", count)
        },
    }
 
    // GCを呼ぶと追加された要素が消える
    pool.Put("removed 1")
    pool.Put("removed 2")
    runtime.GC()
    fmt.Println(pool.Get())
}

sync/atomicパッケージ

sync/atomicパッケージは不可分操作と呼ばれる操作を提供しています。

これはCPUレベルで提供されている「1つで複数の操作を同時に行う命令」などを駆使したり、提供されていなければ正しく処理が行われるまでループするという命令を駆使して「確実に実行される」ことを保証している関数として提供されています。 途中でコンテキストスイッチが入って操作が失敗しないことが保証されます。

次の表のように6つのデータ型に対して、5つの操作が提供されています。

不可分操作
int32 AddInt32 CompareAndSwapInt32 LoadInt32 StoreInt32 SwapInt32
int64 AddInt64 CompareAndSwapInt64 LoadInt64 StoreInt64 SwapInt64
uint32 AddUint32 CompareAndSwapUint32 LoadUint32 StoreUint32 SwapUint32
uint64 AddUint64 CompareAndSwapUint64 LoadUint64 StoreUint64 SwapUint64
uintptr AddUintptr CompareAndSwapUintptr LoadUintptr StoreUintptr SwapUintptr
unsafe.Pointer CompareAndSwapPointer LoadPointer StorePointer SwapPointer

最初のsync.Mutexのサンプルをatomicを使って表現すると次のようになります。

var id int64
 
func generateId(mutex *sync.Mutex) int {
    return atomic.AddInt64(&id, 1)
}

sync.Mutexsync.Cond、チャネルなどを使っても、複数のgoroutineがアクセスしてロックされると、コンテキストスイッチが発生します。 こちらのロックフリーな関数を使えばコンテキストスイッチが発生しないため、うまく用途が合えば今回紹介した機能の中では最速です。

これ以外に、任意のデータ型(interface{})に対するLoad()メソッドとStore()メソッドによる、アトミックな変数読み書きを提供するatomic.Value構造体もあります。

まとめと次回予告

スレッドやgoroutineの仕組みと、Goの並行・並列処理をサポートするツールやライブラリを見てきました。 前回紹介したように、Go言語は組み込みで「安全に非同期な待ち合わせをする機能」を提供しています。 しかし、Go言語の非同期サポートの強力さを実感するのは、goroutineがデッドロックしたときに稼働中のgoroutineがどこでブロックしているかを教えてくれたり、競合状態を検出するオプションがあったり、パニック時にきちんとスタックトレースが出てくれる点です。 こうした強力な機能のおかげで、Go言語ではマルチスレッドを駆使したコードのデバッグが極めて簡単です。

次回は並列処理のパターンを取り上げます。

脚注

  1. https://github.com/golang/go/wiki/LockOSThread
  2. 最近、AMDの最新CPUであるRYZENのベンチーマークでも、この問題が話題になりました。「4gamer:Ryzenはなぜ「ゲーム性能だけあと一歩」なのか? テストとAMD担当者インタビューからその特性と将来性を本気で考える」: http://www.4gamer.net/games/300/G030061/20170425122/
  3. https://golang.org/doc/articles/race_detector.html
  4. C++のスマートポインタとデストラクタ、例外処理を持つ言語のtry/finallyブロック、Rubyのブロック構文やPythonのwith構文など、身の回りでたくさん発見できるでしょう。
  5. https://github.com/golang/go/wiki/MutexOrChannel
  6. http://qiita.com/tenntenn/items/7c70e3451ac783999b4f
  7. https://ja.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E5%88%86%E6%93%8D%E4%BD%9C


カテゴリートップへ

この特集の記事