プロジェクトで開発したC++プログラミング学習環境の動作している様子
はじめに
本研究では,Webブラウザ上で動作するC++実行環境の開発を行った.この実行環境は“未来教室プロジェクト”で制作しているプログラミング初学者向けC++プログラミング学習環境の,実行部分を担うものである.まず本プロジェクト全体の概要と本研究が果たす役割について説明する.
未来教室プロジェクト
本研究室の担当教員である沼田哲史准教授は本学でいくつかのプログラミングの講義を担当している.その中には1年生向けに開講されている講義もあり,プログラミングの未経験者から経験者まで幅広い層の学生が受講している.このような多様な受講生に対応し,とくに初学者が扱いやすく,学びやすい環境を提供する必要性から本プロジェクトが始動した.
本プロジェクトにおける担当範囲
本プロジェクトの目的は,プログラミング初学者が基礎的な知識を効率的に学び,基本的な技術を習得する支援を行うことである.そのために,私たちが製作しているプログラミング学習環境はWebブラウザ上で動作する仕様となっている.そのためソフトウェアのインストールや環境構築といった手間を省き,即座に学習を始められる.またWebブラウザを利用することで,オペレーティングシステムや端末の種類に依存しない学習環境を実現している.これは,さまざまな受講生が参加する講義において大きな利点となる.そのほかにもプログラム実行時や終了時には,利用者の学習を支援するためにメモリ空間の表示を行っている.
本プログラミング学習環境は,ソースコードの入力のサポートからプログラムの実行,そして実行結果やメモリ空間の表示を行っている.本学習環境の全体の流れを図1に示す.本学習環境は3人体制で開発をしており,これらの実装はそれぞれ担当範囲を決め,役割を分担しながら開発を行っている.

本プロジェクトでは,担当範囲を3つの範囲に分けて役割を分担しながら開発を行っている.1つ目は,C++実行環境の実装である.プリプロセッサが出力したソースコードをバイトコードへの変換やそのバイトコードの実行部分の実装を行う.2つ目は,プリプロセッサとライブラリ関数の実装である.ソースコードに含まれているコメントアウトの除去やインクルード文の処理,マクロの展開などのプリプロセッサの実装を行っている.さらにはユーザが自身のソースコードで利用できる,数学系関数やランダム関数などのライブラリ関数のサポートも行う.3つ目は,ユーザインタフェースの実装である.「ユーザからのソースコードの入力」や「実行結果やメモリ空間の表示」などを実装している.
C++実行環境の概要
標準的なC++実行環境の全体像
C++プログラムを実行するには,ソースコードをコンピュータが解釈可能な命令に変換し,実行可能な形式にする必要がある.標準的なC++実行環境では,プリプロセス,コンパイル,リンク,そして実行の4つの段階に分かれ,それを図2に示す.ここでは各工程の役割について説明する.

プリプロセスは,ソースコードの前処理を行う段階である.具体的には,コメントの除去,#includeディレクティブの展開によるヘッダファイルの結合,#defineで定義されたマクロの置換,および条件付きコンパイルディレクティブの解釈を行う.これらの処理により,余計な情報が削除され,コンパイル可能な形式のソースコードが生成される.
コンパイルでは,プリプロセス済みソースコードを機械が実行可能な形式の目的言語に変換する.この段階では,ソースコードを字句解析,構文解析,意味解析することでプログラムの正しさを確認した後,コード生成を行う.標準的なC++環境では,コンパイルによってプラットフォームのプロセッサが直接解釈可能な機械語が生成され,オブジェクトファイルとして出力される.一方,特定の用途ではバイトコードなどの中間言語が生成されることもある.バイトコードは仮想マシン上で効率的に実行できるという利点がある.
リンクは,コンパイラによって生成された複数のオブジェクトファイルを結合し,実行可能なプログラムを作成する工程である.リンクの主な役割は,外部関数や変数への参照を解決し,異なるソースファイル間での重複定義を確認し,結合を行うことである.リンクには静的リンクと動的リンクが存在する.静的リンクでは必要な情報がすべて実行ファイルに含まれる.一方,動的リンクでは外部ライブラリを実行時に読み込む.さらにリンクでは,プログラムの初期化処理やメイン関数の呼び出しを行うためのスタートアップルーチンの追加や,メモリ空間へのコードやデータのマッピングも行われる.
実行では,リンクによって生成されたプログラムがオペレーティングシステムによってメモリに読み込まれ,必要なリソースが割り当てられる.メモリ上のプログラムは,CPUによって命令が順次解釈・実行される.必要に応じてデータの読み書きが行われ,エラーが発生した場合にはエラーメッセージが出力される.
以上のように,C++プログラムの実行にはプリプロセスがソースコードの前処理,コンパイラが変換,そしてリンクが結合というように,各段階が特定の役割を果たしている.これらが連携することでプログラムの正しい動作が実現される.
標準的な実行環境と本実行環境の違い
本研究で開発したC++実行環境は,標準的な実行環境と同様にプリプロセス,コンパイル,リンク,そして実行の4段階でプログラムを実行する.しかしコンパイルとリンクにおいて少し異なる点がある.標準的なコンパイルではソースファイルを一つの翻訳単位として扱い,それに対応したオブジェクトファイルを生成する.そしてリンク時にそれらを結合し実行可能ファイルを生成する.一方,本実行環境のコンパイルではソースファイル全体を単一の翻訳単位として扱う点は同じであるが,オブジェクトファイルではなく,各関数に対応したバイトコードを生成する.そしてリンク時には,関数単位のバイトコードを結合し,プログラム全体で一つのバイトコードにまとめる.この違いを図3に示す.このような違いが生じた背景についてさらに詳しく述べる.

標準的な実行環境はコンパイルとリンク,実行がそれぞれ独立している.そのためコンパイラやリンクでは,ソースコードに対応した命令のデータだけでなく,次のステップの処理に必要な情報を含めたファイルを生成する必要がある.それに対して本実行環境ではプリプロセスからコンパイル,リンク,実行が一つのプログラムとして連動して動作する.それにより,各ステップが処理に必要な情報を参照できるため,コンパイルやリンクの出力としてそれらの情報を埋め込み,共有する必要がない.またこの情報は各ステップの処理に参照するだけでなく,エラー発生時にも用いることができる.これによりエラーメッセージの柔軟性を高めることができ,プログラミング初学者がデバッグを行いやすい環境を作ることができるようになる.本実行環境は実装のシンプル性や効率性,柔軟性を重視した結果,今回のような設計となった.これが標準的なコンパイラやリンカがオブジェクトファイルや実行可能ファイルを出力するのに対して,本実行環境ではバイトコードを出力する理由である.
また標準的な実行環境と本実行環境では,リンクで結合する際の単位も異なる.標準的なリンクではソースファイルを一つの結合単位としているのに対して,本実行環境では関数を一つの結合単位としている.この違いは本プロジェクトのプログラミング学習環境では,複数ソースファイルに分割したプログラムの実行をサポートしていないからである.それは本実行環境を限られた時間で開発するにあたり,本機能の優先順位を低く設定したからである.ヘッダファイルの作成の仕方やソースファイルの分割方法などの理解はプログラミングを行う上で欠かせないものである.しかし本学習環境はプログラミング初学者を基本的にターゲットとし,実行されるプログラムも基礎的なものを想定している.そのため複数ソースファイルのサポートを行うことより,扱えるC++の言語仕様を増やすこと優先した.その結果結合単位が異なるという違いが生じた.
以上のように,標準的な実行環境と本実行環境の違いは,それぞれの目的や対象とするユーザ層に基づいた設計選択の結果である.本プログラミング学習環境は,プログラミング初学者が基礎的なプログラムを効率よく学習できることを目的としている.そして本実行環境もそのニーズに合わせた設計方針を選択した.
プリプロセスとUIとの連携
初学者に向けたプログラミング学習環境を実現するために本実行環境は,他のプロジェクトメンバーが担当するプリプロセスやユーザインフェースと様々な連携を行っている.この連携の詳細について説明する.
まずプリプロセスからは,プリプロセス済みのソースコードに加えて,インクルードされたファイル名の情報も受け取っている.これは本学習環境でユーザが利用することのできるライブラリ関数の実装を効率的に行うためである.ライブラリ関数はあらかじめ用意された関数であり,インクルード文を記述すると使用できる.本実行環境ではライブラリ関数の処理をTypeScriptで記述し,実行時に参照を行う関数テーブルに保存している.TypeScriptで記述できることにより,TypeScriptのライブラリ関数をそのまま転用でき,効率的に実装を行うことができる.sinf()というライブラリ関数を例に,ライブラリ関数の実行の流れを図解したのが図4である.

しかしこの実装方法では,インクルードを行わなくてもライブラリ関数を使用できるという問題が生じた.この問題を解決するために関数テーブルを「関数の処理を記述しておく関数テーブル」と「実行時に参照する関数テーブル」の2つに分けた.そして実行時には,「関数の処理を記述しておく関数テーブル」から「実行時に参照する関数テーブル」に情報をコピーするようにした.そしてこの関数テーブルに登録された各関数の情報のコピーは,プリプロセスから受け取ったインクルードされたファイル名をもとに,コピーするか否かの判断を行っている.このような連携を行うことでライブラリ関数をTypeScriptで実装することができ,ライブラリ関数の実装を効率的に進めることが可能となった.
また,本プログラミング学習環境はメモリ空間を図5のように表示できるという特徴がある.この表示を行うために,本実行環境はUIに様々な情報を提供している.

具体的にはメモリの各アドレスに対して次の6つの情報を用意し,それらを提供している.
- 格納されている値(0x00~0xFFまでの数値)
- データ型(intやfloatなど)
- データ構造種別(変数か配列)
- データの名前(変数名,配列名)
- データのID(変数や配列に割り振った個別の番号)
- セグメントの情報(スタックやコードなどのメモリ空間の領域の名前)
データ型,データ構造種別,データの名前に関しては,何も情報が無ければnullを入れることもある.図5に示したようなメモリ空間の色分けや各種情報の表示を可能にするために,本実行環境はこれらの情報の提供を行っている.
C++実行環境の設計
本実行環境におけるC++のプログラムの実行できるようにするまでの流れは「字句解析と構文解析」「抽象構文木の生成」「意味解析」「バイトコードの生成」「バイトコードの実行」の5つである.この5つの工程を図6に示す.各工程について説明する.

字句解析と構文解析および抽象構文木の生成
本実行環境の字句解析と構文解析は,ANTLR4というパーサジェネレータが生成した字句解析器(Lexer)と構文解析器(Parser)を使用した.ANTLR4を利用するためには,解析対象の文法規則を専用の形式で記述したグラマーファイルが必要である.しかし,ANTLR4はC++のグラマーファイルを公開しており,それを利用することができる.そして本実行環境を実装しているTypeScriptで作成された,LexerとParserの生成も可能であり,これらを本実行環境に組み込むことでLexerとParserの実装時間を大幅に短縮できる.これらの理由から,ANTLR4を用いることで字句解析および構文解析の実装コストを削減できると判断し,本実行環境ではANTLR4を採用した.
そして構文解析を終えるとグラマーファイルに記述された文法に沿った構文木が生成される.図7に示すのは生成される構文木の例である.図7 (a)のソースコードから図7 (b)の構文木が生成され,その構文木はS式と呼ばれる記法で記述した.図7で,同色で囲まれている箇所がソースコードに対応する構文木である.たとえば,図7 (a)の赤枠で囲われた箇所は戻り値の宣言であり,図7 (b)のdeclSpecifierSeqから始まる構文木がそれに対応する.図7では関数定義に対応する構文木にフォーカスしているため,青色で囲われた関数の本文に対応する構文木の表示は省略している.この構文木から抽象構文木を生成する.

抽象構文木は構文木の不要なノードを除去し,意味のあるノードのみにした木構造のことである.抽象構文木は構文解析時の構文木とバイトコードの中間表現となっており,この中間表現を挟むことで,木構造をトラバースする際の処理の数を大幅に削減でき,ノード同士の情報の受け渡しも容易になることで,意味解析やバイトコードの生成を行いやすくする.図8では構文木から抽象構文木の変換を示す.図8 (a)の構文木から図8 (b)の抽象構文木が生成される.図8も図7と同様に同色の箇所が対応している.バイトコードの生成を行う前の段階では,この抽象構文木を使用して意味解析を行っている.意味解析では,文法だけでは判断できない誤りの検出やバイトコードの生成に必要なデータの構築などを行う.たとえば,「型の不一致の検出」や「定義されていない,変数の参照や関数呼び出しの発見」などを行う.また関数や変数の定義の有無を確認するために,意味解析の段階で変数や関数を管理する記号表への登録も行う.これらの処理を行うためにも,抽象構文木には子ノードの情報のほかに,様々な情報を持たせている.たとえば,式を表すノードはその式の評価時の型情報を保持している.そして抽象構文木からバイトコードを生成する.

バイトコードの生成と実行
本実行環境のように,ソースコードの解析から実行までの一連の手順がまとめて行われる環境の場合,前節で述べた抽象構文木を直接解釈しながら,実行を行うことも可能である.しかし本環境ではその設計手法は選択せず,抽象構文木からバイトコードを生成し,実行する設計とした.まずこのような設計を行なった理由について述べる.
本プログラミング学習環境の特徴の一つとしてメモリ空間の表示を行うという点は,前述した通りである.これはプログラミング言語のC言語やC++で使用される,ポインタと呼ばれるメモリのアドレスに関する概念の理解を助けるためである.本学習環境では仮想的なメモリ空間を用意し,その情報を視覚的に理解しやすい形式で表示することで学習者の理解を助けている.仮想的なメモリ空間を用意するということは,ソースコードのデータもその仮想的なメモリ空間に格納する必要がある.そのため,各命令がバイト単位で形成され,メモリ上に配置可能なバイトコードに変換を行い,実行するという設計を行った.
本実行環境で変換されるバイトコードにはCommon Intermediate Language (CIL)を採用している.CILの他にもバイトコードの候補としては,WebAssembly (Wasm)やJavaバイトコード,CASL/COMETなどが挙げられていた.その中でもCILを採用した理由は大きく2つある.それは「命令セットが高水準で豊富な点」と「扱えるプリミティブ型が豊富な点」の2つである.CASL/COMETやWasmはCILに比べて,扱えるプリミティブ型が少なく,命令セットがシンプルであった.そしてJavaバイトコードは扱えるプリミティブ型がCILに比べて少なかった.これらの点から本実行環境で扱うバイトコードにはCILを選択した.CILのニーモニックコード例とバイトコード例を図9に示す.ニーモニックとはバイトコードを人が理解しやすい形式に置き換えたものである.CILは,C#などのプログラミング言語で中間言語として用いられている,スタックベースのバイトコードである.CILはネイティブコードに変換されるか,Common Language Runtime (CLR)と呼ばれる仮想機械で実行される.CLRはCommon Language Infrastructure (CLI)というMicrosoftが策定して仕様に基づいて実装されており,その仕様はECMA-335として標準化され公開されている.しかし本研究で開発した本実行環境ではCILの命令セットを使用しているものの,この仕様通りに実装を行っているわけではない.ECMA-335を参考にしながら,本プログラミング学習環境の目的に合わせて,簡略化した実装を行った.

そして変換したバイトコードを実行する.前述した通りCILはスタックベースのバイトコードであり,各命令はスタック上で行われる.本実行環境はメモリ空間のスタック領域を使用し,これらのバイトコードの命令を実行している.また実行時には実行マシンが,コンパイルやリンクで作成した情報を必要に応じて参照し,バイトコードを実行する.具体的にはライブラリ関数の処理やユーザ定義関数のエントリポイント,変数のアドレスや型情報などを参照している.これによりバイトコードの実行を実現している.
C++実行環境の実装と課題分析
本実行環境はWebブラウザ上での動作を実現するため,プログラミング言語のTypeScriptで開発している.ここでは本プログラミング学習環境で実行できるC++の言語仕様と前述した,「字句解析と構文解析」「抽象構文木の生成」「意味解析」「バイトコードの生成」「バイトコードの実行」の5つの工程の実装について説明する.また本学習環境で計測したプログラムの実行時間をもとに課題の分析を行う.
実装内容
対応するC++の言語仕様
本プロジェクトの限られた時間の中では,すべてのC++の言語仕様を実装することは困難であった.そのため本プログラミング学習環境では,プログラミング初学者の学習に必要になるであろう言語仕様を優先的に実装した.具体的には,本学で開講されている1年生向けのプログラミングの講義の内容を参考にし,実装する言語仕様を選定した.またC++はプログラミング言語のC言語から派生した言語であり,C 言語との互換性を持たせるために,C言語から引き継がれている言語仕様や慣習が数多く存在する.本実行環境ではこれらの実装も優先的に行った.それは本プログラミング学習環境をC言語の学習にも使用できるようになるからである.またシンプルな構造の言語仕様が多く,実装のコストが少ないというメリットもあった.ここではこれらの本学習環境で対応している言語仕様について詳しく述べる.
データ型
本学習環境では以下のデータ型が使用できる.
- 整数型
- char (signed / unsigned)
- short (signed / unsigned)
- int (signed / unsigned)
- 浮動小数点型
- float
- ポインタ型
float型の内部形式はIEEE 754規格の単精度浮動小数点形式に準拠しており,符号部が1ビット,指数部が8ビット,仮数部が23ビットで表現される.また,int型やfloat型,short型などのマルチバイトのデータはリトルエンディアン形式で扱われる.まずエンディアンとは複数バイトあるデータのそれぞれのバイトを並べる順番のことを指す.そして本実行環境で採用しているリトルエンディアンとは下位のバイトから順番に並べる形式である.それぞれのエンディアンの違いを図10で示す.メモリ空間表示のUIではこのリトルエンディアンで配置されたデータを直接見ることができ,学習者はエンディアンの理解を深めることができる.

変数
変数は,グローバル変数とローカル変数を使用することができる.グローバル変数はグローバルスコープに宣言された変数であり,どの関数からでもアクセスすることができる変数である.そしてローカル変数は関数定義内で宣言された変数であり,関数の中でのみアクセス可能な変数である.
制御構文
本学習環境では以下の制御構文が使用できる.
- 条件分岐構文
- if
- switch
- 繰り返し構文
- for
- while
- do while
switchの中ではbreak文を使用して構文からの脱出を行うことができる.またbreak文がない場合はフォールスルーが起こる.繰り返しではbreak文に加えてcontinue文が使用できる.
関数
本プログラミング学習環境ではユーザは,関数の定義と呼び出し,プロトタイプ宣言を行うことが可能である.これらの関数はユーザ定義関数といい,本学習環境ではユーザ定義関数の他にライブラリ関数も使用することができる.これは実行環境側で用意している関数であり,関数の内部の処理はTypeScriptで記述されている.ライブラリ関数は指定されたインクルード文を記述することにより,使用可能となる.
そのほかの言語仕様
上記の他にも本学習環境では配列や構造体が使用できる.配列は1次元配列のみのサポートとなっている.また演算に関しては,5則演算や比較演算が使用でき,変数などに対してはインクリメントやデクリメントなどの演算も使用できる.また関数や変数にはポインタ取得演算子も使用できるが,間接参照のサポートはできていないため,printf()関数によるアドレスの表示のみが可能である.
字句解析と構文解析および抽象構文木の生成
本実行環境では字句解析と構文解析は,ANTLR4が生成した字句解析器(Lexer)と構文解析器(Parser)を使用して行っている.そしてParserが出力する構文木をトラバースしながら,抽象構文木を生成する.この時の具体的な実装について述べる.
構文木のトラバースにはANTLR4が提供するListenerクラスを用いている.このクラスはParserを生成する際にANTLR4によって自動的に生成され,構文木の各ノードに対応するメソッドを提供する.本実行環境ではこのクラスを拡張したカスタムListenerクラスを実装し,各ノードの処理を行っている.Listenerクラスは構文木のトラバース時に各ノードに対応したenterメソッドとexitメソッドを呼び出す.enterメソッドはノードに入ったタイミングで呼び出され,exitメソッドはノードを抜けるタイミングで呼び出される.基本的には,この2種類のメソッドと「ノード間でデータを共有するためのスタック」,「解析状態を管理するスタック」で抽象構文木の生成が行われる.解析状態を管理するスタックは構文木の解析の進行状況を示し,抽象構文木のノード生成を適切に行うために利用される.enterメソッドで解析状態を管理するスタックに新たに状態を積み,exitメソッドではその状態に応じてデータ共有用のスタックからデータの取得や新しくノードを生成し,スタックに積むといった処理を行う.これにより,構文木から抽象構文木の生成を行っている.
意味解析
意味解析では抽象構文木をトラバースし,プログラムを意味的に正しいかどうかの検証を行っている.この工程では,定義された変数などを管理するためのEnvironmentクラスを作成していく.そして抽象構文木の変数宣言に対応するノードではEnvironmentクラスにそれらの情報を登録していく.そして変数への代入や変数の参照を表すノードでは,Environmentクラスから検索を行い,もし発見できなければエラーを出力する.またこれと同様の処理を関数に対しても行っている.その他にも型情報のチェックなども行っている.これは型検査と呼ばれ,型の不一致によるエラーを出力したりする.C++ではint型の変数にfloat型を代入しようとすると型は同じではないが,暗黙の型変換が行われる.しかしint型にポインタ型などの型変換が不可能な型を代入しようとすると型の不一致でエラーが出力される.このような型のチェックを行うために,式として利用されるノードには型情報の設定を行っている.この設定された型情報は後述のバイトコードの生成でも使用される.
バイトコードの生成と実行
本実行環境は抽象構文木からバイトコードの生成を行う.この実装を行うために,様々なクラスを用意した.それはCILInstructionクラス,CILAsmSequenceクラス,そしてCILAsmBuilderクラスである.まずCILInstructionクラスは,バイトコードの命令を表すためのクラスである.このクラスはそれぞれの命令に対応するクラスを用意するためのスーパークラスである.このクラスから派生した各命令のクラスは自身のバイトコードを出力するto_bytecodes()メソッドを持っている.次にCILAsmSequenceクラスはCILInstructionクラスのシーケンスを保持するためのクラスである.そしてCILAsmBuilderクラスはバイトコードの生成を行うクラスである.このクラスは関数定義ごとの抽象構文木を入力とし,CILAsmSequenceクラスを出力する,buildCILAsmSeq()メソッドを持っている.CILAsmSequenceクラスが持つ各命令のクラスに対して,to_bytecodes()メソッドを呼び出しその出力を結合することで,コンパイルの出力である,関数ごとのバイトコードが生成される.リンクではこの関数ごとのバイトコードを結合し,プログラム全体のバイトコードを生成している.
バイトコードの実行はCLRExecutorクラスが行う.このクラスにはコンパイル時やリンク時などの情報やメモリ空間の情報などが渡されており,それらの情報を用いながら,バイトコードを実行していく.バイトコードの実行はCLRExecutorクラスのexecuteCILBytecodes()メソッドが行う.このメソッドはメモリ空間に格納されている,バイトコードのデータを順に読み込み,命令のオペレーションコードに応じた処理を行っていく.オペレーションコードの識別にはswitch文を使用しており,この処理をプログラムの終了を表すCILのret命令が呼び出されるまで繰り返し行う.
実行速度に関する課題と分析
本プログラミング学習環境でプログラムの実行時間を計測し,そのデータをもとに課題の分析を行なった.本計測で使用したプログラムはフィボナッチ数列を求めるプログラムである.このプログラムは再帰関数の学習を行う際の題材にされやすく,本学習環境でも実行される可能性が高いことから,本測定のテストプログラムとして採用した.再帰関数を使用した方法と繰り返しを使用した方法の2種類のプログラムを用意した.再帰関数を使用したプログラムは
// 再帰関数を使用したフィボナッチ数列を求めるプログラム
#include <stdio.h>
int fibRecursive(int n) {
if (n <= 1) return n;
int a = fibRecursive(n - 1);
int b = fibRecursive(n - 2);
return a + b;
}
int main() {
int n = 20; // 級数
int result = fibRecursive(n);
printf("%d\n", result);
return 0;
}
であり,繰り返しを使用したプログラムは
// 繰り返しを使用したフィボナッチ数列を求めるプログラム
#include <stdio.h>
int fibIterative(int n) {
if (n <= 1) return n;
int a = 0; int b = 1; int c;
for (int i = 2; i <= n; ++i) {
c = a + b;
a = b;
b = c;
}
return b;
}
int main() {
int n = 20; // 級数
int result = fibIterative(n);
printf("%d\n", result);
return 0;
}
である.これらを本学習環境で実行させ,実行時間を計測した.その結果を表1に示す.実行時間の単位はミリ秒である.級数が20の時の実行時間は本学習環境は72487ミリ秒であったが,Xcodeで同様のプログラムを実行すると実行時間は0.1ミリ秒以下である.Webブラウザ上で動作する本学習環境とXcodeのような商用のC++実行環境を直接比較することは適切でないが,本学習環境の実行速度が極めて低いことは明らかである.
表1 フィボナッチ数列の各級数の計算時間
| 級数 | 10 | 12 | 14 | 16 | 18 | 20 |
| 実行時間(再帰関数) [ms] | 654.8 | 1,576.9 | 4,064.8 | 10,629.8 | 27,964.8 | 72,487.9 |
| 実行時間(繰り返し) [ms] | 84.6 | 94.8 | 100.7 | 111.7 | 130.3 | 139.6 |
本学習環境の実行速度を低下させている要因の一つとして,メモリ空間表示のための情報を共有するメソッドが挙げられる.このメソッドを呼び出さなかった場合の実行時間を表2に示す.表1と比較するとそれぞれの実行時間は10分の1以下になっている.本学習環境の実行性能を向上させるためには,この情報共有の方法を見直し,効率的な設計を行う必要がある.本学習環境は学習を目的としたものであり,実行速度の向上は直接の目標ではないものの,過剰な実行時間は学習者の使用に支障をきたす可能性がある.そのため,学習環境としての実用性を確保するためには,実行時のパフォーマンス改善が重要な課題である.
表2 情報共有メソッドを削除した際のフィボナッチ数列の各級数の計算時間
| 級数 | 10 | 12 | 14 | 16 | 18 | 20 |
| 実行時間(再帰関数) [ms] | 64.4 | 139.1 | 347.7 | 871.2 | 2,258.9 | 5,866.9 |
| 実行時間(繰り返し) [ms] | 4.8 | 4.5 | 4.5 | 4.7 | 5.5 | 4.7 |
おわりに
研究成果のまとめ
本研究は,“未来教室プロジェクト”というプロジェクトのなかで行われた.本プロジェクトでは,C++のプログラミング学習環境を開発を行った.本学習環境はプログラミング初学者が基礎的な知識を効率的に学び,C++の基本的な技術を習得するための支援を目的としている.本プロジェクトは3人体制で開発を行っており,それぞれの担当範囲は「C++実行環境の実装」「プリプロセスやライブラリ関数の実装」「ユーザインタフェースの実装」となっている.本研究では「C++実行環境の実装」を担当した.また他のプロジェクトメンバーが実装した.プリプロセスやユーザインタフェースとの連携も行った.
本実行環境はプリプロセス後のソースコードを字句解析と構文解析を行い,抽象構文木の生成を行う.そして抽象構文木をバイトコードに変換し,バイトコードを実行するという流れで実行される.バイトコードを実行するという設計は,メモリ空間の表示を行うために,プログラムをメモリ空間上に配置する必要があったため,このような設計となった.そのバイトコードには命令セットや扱えるプリミティブ型の豊富な点からCILというスタックベースのバイトコードを採用した.また字句解析と構文解析にはANTLR4を使用し,字句解析器と構文解析木を自動生成することで実装の効率化を図った.
実装にはTypeScriptを使用し,抽象構文木の生成からバイトコードの生成・実行までを一貫して行えるシステムを開発した.本実行環境で対応可能なC++の言語仕様は,一部のデータ型,制御構文,変数,配列,関数,構造体である.また本学習環境では,メモリ空間を可視化するユーザインタフェースを提供しており,その表示を行うための仮想的なメモリ空間に関する情報共有も行った.
また開発した本実行環境の実行時間の測定や分析も行った.この実験により,本実行環境の実行速度が著しく遅いことが明らかとなった.本学習環境は学習を目的としたものであり,実行速度の向上は直接の目標ではないものの,過剰な実行時間は学習者の使用に支障をきたす可能性がある.そのため,学習環境としての実用性を確保するためには,実行時のパフォーマンス改善が重要な課題であることが示された.
通常,C++の開発環境を整えるには,コンパイラや統合開発環境などのインストールに加え,各種設定を行う必要がある.一方で,本研究で開発したC++実行環境は,Webブラウザ上で動作し,特別な環境構築を必要とせずにC++プログラムを実行できる.これはプログラミング初学者の環境構築の負担を軽減し,手軽にC++を実行できるため,プログラミング初学者にとって有用である.また本学習環境の特徴の一つである,メモリ空間の視覚化を実現するための実装も行った.これにより,プログラムの実行過程を視覚的に把握しやすくなり,初学者がメモリの概念を理解する際の助けとなる.以上のことから,後述する課題や改善点はあるものの,本研究の成果はプログラミング初学者の学習支援に寄与すると考えられる.
今後の課題と改善点
本研究ではC++のプログラミング学習環境を構築し,一定の成果を得たが,さらなる学習効果向上のためには,前述した実行時のパフォーマンスの課題の他にも,様々な課題の解決が必要である.まず言語仕様の拡張が挙げられる.現在対応している機能は基本的な要素に限られており,クラスやテンプレート,例外処理などの高度な機能のサポートが求められる.これにより,実践的なプログラミング体験を提供でき,学習者のスキル向上に寄与すると考えられる.次に字句解析や構文解析時のエラー処理の改善である.現状ではエラー発生時に十分な情報がユーザに提供されず,エラーの原因を特定することが困難な場合がある.そのためエラー原因の特定や発生箇所の提示を正確に行う仕組みが必要である.また初学者に配慮した分かりやすいエラー内容の提示と修正方法の提案が求められる.これにより,エラー発生時のデバッグにかかる時間を削減でき,学習の効率を上げることができる.これらの課題解決を通じ,初学者が効率的に学べる学習環境を目指し,改善に取り組む必要がある.

コメント