SPVMの開発の主な目的を教えてください。
SPVMは、Perlの数値計算のパフォーマンスを改善することを目標として開発されています。数値計算と配列の演算を高速化することが第一の目標です。
仕様がほぼ確定した後にベンチマークテストを始める予定ですが、数値計算と配列の演算に対して、Perlの30倍、C言語の1/2の速度を、最初の目標としています。
C/C++のバインディングを簡単にすることが二つ目の目標です。
さらなる数値計算の高速化において必要になるのは、C/C++のライブラリをSPVMから利用できることです。専門分野のための自作のC/C++ライブラリや、openMPやSIMDやGPUを扱えるオープンソースのC/C++ライブラリをバインディングできると、数値の並列計算などでさらなる高速化が可能になります。
C言語のソースファイルを配置するだけで、C言語を記述できる機能を提供します。
設定ファイルを記述することで、gcc,clangのコンパイラオプションの制御を可能にします。ExtUtils::MakeMakerの使い方に近い記述を目指します。
SPVMのビルドディレクトリにinclude、libディレクトリを配置すれば、そこに置かれた、ヘッダーファイル、静的ライブラリ、共有ライブラリを利用できるようにします。
SPVMとC言語で、データをやり取りするための仕様化されたAPIを提供します。
すでに生成された共有ライブラリなどのバイナリファイルの後方互換性が、SPVMのバージョンアップによって、壊れないように設計されます。
単体で実行可能な実行ファイルを生成することが、三つ目の目標です。
SPVMは、実行ファイルを生成する機能を持っているので、ランタイムなしに、プログラムの実行が可能です。単体で実行可能なので配布することもできます。
共有ライブラリ(ダイナミックリンクライブラリ)の読み込みにも対応します。
ボタンなどのGUIを備えたWindowsのネイティブアプリケーション、スマートフォンで動くiOSネイティブアプリケーションが作成できるようにします。
どのような人をSPVMのユーザーとして想定していますか。
Perlは強力な文字列処理を利点として、従来より遺伝子解析などの生命情報科学の分野で利用されてきましたが、近年はデータ取得技術の革新によって利用できる遺伝子情報が爆発的に増加したため、配列操作の遅さに不満を感じている研究者の方がいます。
配列操作を簡単に高速化でき、必要であればC/C++をバインディングし、openMPやSIMDなどの並列計算ができればと考えている研究者の方を想定しています。
Perlは本当に型のない言語です。Perlは内部的には型を持っていますが、数値型と文字列型は、コンテキストに応じて、予測しづらい方法で変換されてしまいます。
Perlの整数演算はオーバーフローが発生すると、浮動小数点型に変換されます。これは、便利ですが、数値計算の結果の予測はしづらくなります。
Perlの整数演算は、コンパイラオプションによって、32bitあるいは64bitになります。浮動小数点演算は、float型を持たず、double型しか持ちません。
Perlの数値演算は正しい結果を返しますが、どの型になるかは、コンパイラオプションや内部の動作に依存し、予想がしづらいものとなっています。
SPVMは、静的型を持つ言語あり、32bitと64bitの整数演算、floatとdoubleの浮動小数点演算を区別して実行することができます。
SPVMを使うと、Perlの文法で、数値計算と配列演算を速くすることができます。
IoTの開発者は、ハードウェアからWebまでの幅広い知識が必要になります。大量の分野の知識が必要となるため、これを簡単に実現するライブラリがあれば便利です。
C/C++、アセンブラを使って、センサーのデバイスドライバを作成。HTTPクライアントライブラリを使って、Webにアクセス。開発環境で、実行ファイルを作成して、それぞれの機器にそのままコピー。このような一連の機能をSPVMは提供する予定です。
画像処理には、大量の配列演算が必要です。Perlは、配列のデータ構造を言語仕様として持たないため、不満を感じている方がいます。
SPVMは、連続した領域を持つ配列のデータ構造を提供しているので、DirectXやOpenGM、OpenCVなどと組み合わせて、画像処理をしたいユーザーに向いています。
AI・機械学習には、配列演算が必要です。Perlは、配列のデータ構造を言語仕様として持たないため、不満を感じている方がいます。
SPVMは、連続した領域を持つ配列のデータ構造を提供しているので、機械学習ライブラリとの相性が高いです。
SPVMは、JSONモジュール、非同期IOをサポートしたHTTPクライアント/サーバーをコアで提供する予定です。実行ファイルを生成可能なので、小さなLinuxサーバーを構築し、実行ファイルをコピーして、マイクロサービスを運用可能です。
SPVMは、リファレンスカウント方式のGCを採用しているので、FullGCが起こらず、非同期IOの性能に大きく関わるレイテンシを小さくできます。
SPVMはCPANモジュールです。cpanまたはcpanmを使ってインストールできます。
cpan SPVM cpanm SPVM
SPVMは連続したデータ領域を持つ配列型を提供しています。
Perlの数値計算の弱点の一つは、型を固定して計算することができないことです。ある操作によって浮動小数点に変換されたり、文字列型に変換されたりします。SPVMでは、仕様化された数値型と計算規則を持っているので、どの型によって計算されるかを正確把握することができます。
C99で追加された新しいC言語の数学関数がすべて利用できます。
SPVMの言語仕様は、メジャーバージョンごとに、仕様化されています。仕様を元に、独自にコンパイラを開発することが可能です。
SPVMのサブルーチンはコンパイル時に、機械語へコンパイルすることができます。
SPVMの文法は覚えやすく簡単になるように設計されています。
文法の95%以上はPerlの文法を採用しています。Perlをすでに学んでいるユーザーであれば、2時間あれば、すべての文法を覚えてしまうことができるでしょう。
型の種類、型の規変換規則は、明確です。型推論は、常に右から左へ行われます。
SPVMのチュートリアルです。
最初の簡単な例として配列の和を求めてみましょう。
「MyMath.spvm」というファイルファイルをlibディレクトリの中に作成してください。SPVMのソースファイルの拡張子は「.spvm」です。以下の内容を記述しましょう。
# lib/MyMath.spvm package MyMath { sub sum : int ($nums : int[]) { my $total = 0; for (my $i = 0; $i < @$nums; $i++) { $total += $nums->[$i]; } return $total; } }
SPVMではパッケージ構文を使用して、パッケージを作成します。Perlのパッケージブロック構文と同じ文法です。
# パッケージ構文 package MyMath { }
パッケージのブロックの中ではサブルーチンの定義を行うことができます。
package MyMath { sub sum : int ($nums : int[]) { } }
SPVMは静的型言語ですので、サブルーチンの宣言の中で、戻り値の型、引数名と引数の型を記述します。
戻り値は「int型」。引数は「int型の配列」です。int型は、32bit符号付整数を表現します。
配列の和を計算している部分をみてみましょう。
my $total = 0; for (my $i = 0; $i < @$nums; $i++) { $total += $nums->[$i]; } return $total;
和の計算方法の見た目は、Perlでfor文を使って書いたものとまったく同じです。
このようにSPVMではMPerlと同じ文法で記述できるのがひとつの特徴です。Perlユーザーが、新しい文法を覚える負担ができるだけ小さくなるように設計されています。
変数の宣言では型推論を使って、型を省略することができます。
my $total = 0;
SPVMの型推論は、右辺の型が確定しているときに、左辺の型宣言を省略することができます。
数値リテラルの「0」は「int型」ですので、「$total」の型も「int型」になります。以下の記述と同じ意味になります。
my $total : int = 0;
配列の長さは「@」を使うことで取得できます。
@$nums
SPVMには、コンテキストはなく「@」は常に配列の長さを返します。
次にSPVMで書かれたサブルーチンをPerlの側から呼び出してみましょう。
「sum.pl」というファイルを作成して、以下の内容を記述してください。
SPVMで書かれた「MyMath」パッケージの「sum」サブルーチンを呼び出して、配列の和を計算します。
use FindBin; use lib "$FindBin::Bin/lib"; use SPVM 'MyMath'; my $sp_nums = SPVM::new_iarray([3, 6, 8, 9]); my $total = MyMath->sum($sp_nums); print $total . "\n";
まず最初にlibディレクトリをモジュールの検索パスに追加します。
# libディレクトリをモジュールの検索パスに追加 use FindBin; use lib "$FindBin::Bin/lib";
次にSPVMモジュールを読み込みます。
# SPVMモジュールを読み込み use SPVM 'MyMath';
「use SPVM 'SPVMモジュール名'」という記述で、SPVMモジュールを読み込むことができます。
次にSPVMの配列を作成します。
# int型配列の作成 my $sp_nums = SPVM::new_iarray([3, 6, 8, 9]);
SPVM::new_iarray関数を使うと、配列のリファレンスを渡して、SPVMのint型の配列を作成することができます。
MyMathパッケージのsumサブルーチンを呼び出してみましょう。
# MyMathパッケージのsumサブルーチンを呼び出し my $total = MyMath->sum($sp_nums);
SPVMのサブルーチン呼び出しは、Perlのクラスメソッドの呼び出しになることに注意してください。「絶対名 MyMath::sub」で呼び出すことはできません。
sum関数の戻り値は「int型」ですが、SPVMの整数型は、自動的にPerlのスカラ型に変換されます。
SPVMの言語仕様は以下のドキュメントで解説されます。
SPVMエクスチェンジAPI仕様は以下のドキュメントで解説されます。SPVMエクスチェンジAPIとは、Perl言語からSPVMのデータ構造を作成、SPVMのサブルーチンを呼び出すためのAPIのことです。
SPVMの標準関数の一覧です。
sub INFINITY : double ()
無限大を「double型」で返します。C標準の「INFINITYマクロ」の単純なラッパーです。
sub INFINITYF : float ()
無限大を「float型」で返します。C標準の「INFINITYマクロ」の単純なラッパーです。
sub NAN : double ()
非値を「double型」で返します。C標準の「NANマクロ」の単純なラッパーです。
sub NANF : float ()
非値を「float型」で返します。C標準の「NANマクロ」の単純なラッパーです。
SPVMでは以下のようにサブルーチンを呼び出すことができません。
Foo::bar();
常に、アロー演算子を使う必要があります。
Foo->bar();
SPVMの世界に閉じるのであれば、この二つを同じものとして扱うことができるのですが、Perlの世界においては、二つの表現は意味が異なります。
SPVMのひとつの目標は、SPVMにおける呼び出しとPerlにおける呼び出しを完全に一致させることです。
SPVMの世界でアロー演算子で呼び出せるものが、Perlの世界でもアロー演算子で呼び出せるという単純な規則を設けることで、呼び出しを完全に一致させています。
C99は、加算演算子・減算演算子・乗算演算子における整数演算のオーバーフロー時の動作について定義していないので、処理系依存になります。
ほぼすべての環境においては、2の補数表現を使って、整数を表現し、加算と減算を行いますので、2の補数表現で演算を行った場合の結果になることが期待されます。
はい。ライブラリがC89、C11、C++の各仕様で書かれていても利用できます。C99準拠というのは、SPVM自体のソースコード自体において適用されるだけです。
通常は、定数畳み込みの最適化は、行われませんが、プリコンパイルされたサブルーチンでは、定数畳み込みがCコンパイラによって試みらるでしょう。
演算子の優先順位はPerlをベースにして作成されており、ほぼPerlの優先順位だと考えて大丈夫です。
ひとつの違いは、SPVMには型キャストがあることです。型キャストは「単項演算子」の優先順位よりもひとつ低く、「乗算演算子」の優先順位よりも一つ高くなっています。
以下の例ではキャストが先に行われます。
(long)3 + 1;
これを、戻り値にすることは可能なはずですが、現在は実装されていません。
実装されていない理由は、一時変数の増加とオペレーションコードの増加が見込まれ、最適な形で実装することが、現在の作者の実力では困難であることです。
現在の内部実装では、条件判定した結果は、内部的な条件フラグに保存されます。
戻り値が欲しい場合は、次のように記述してください。
my $flag : int; if ($num > 3) { $flag = 1; } else { $flag = 0; }
SPVMは柔軟で、十分な後方互換性を保つことを目標に現在、設計しています。
いくつかの分野で、プログラムが正しく記述できること、パフォーマンスの要件を満たすことが必要です。1.0のリリースは、この要件が満たされた後になります。
デバイスドライバや、Open CV、Open GL、SIMD、 Open MP、GPUなどのC/C++ライブラリとの連携の確認。
HTTPSのリクエストを処理できるHTTPクライアント/サーバーライブラリ。
Windows APIを利用したネイティブWindowsアプリケーションの作成。
少なくともデバイスドライバやC/C++ライブラリと連携でき、WebにHTTPSで接続できる、ネイティブアプリケーションが作成できることの確認が必要です。
言語仕様と文法についてはPerlを主に参考にしています。
Perl 6の文法・キーワードを部分的に採用しています。「has」「native」「ro」「rw」「wo」など。
SPVMのバーチャルマシンの初期実装は、JavaVMを参考にして、可変長バイト命令を解釈するスタック型VMとして作成されました。現在のSPVMは、64bitの固定長命令を解釈するレジスター型VMとなっています。
数値型と数値計算においては、Javaの計算規則を参考にしました。
ボクシング、アンボクシング、可変長引数については、Javaを参考にしました。
コールバックについては、C言語の関数ポインタを参考にしました。
レジスタ型VMを採用している最も大きな理由は、SPVMのオペレーションコードを、C言語のソースコードに変換するときに、1対1で対応させることができるためです。gccの最適化が適用できます。
残念なことですが、列挙に利用できるるのはint型だけです。
他の型の定数を利用したい場合は、定数を返すサブルーチンを定義してください。
sub FOO : double () { return 3.14; }
一つの定数を返すサブルーチンは、定数としてインライン展開されることが仕様上で保証されているので、パフォーマンスを気にせず利用できます。
SPVMにおいては、メソッドの呼び出し、クラスメソッドの呼び出しについては、かっこの省略が可能です。サブルーチン名だけを指定した場合においては、かっこの省略ができません。
これは、サブルーチン名だけでは、識別子名がサブルーチンであることを、ソースコードの中で、決定できないためです。パッケージ名やフィールド名との区別ができません。
他のソースファイルを解析することによって、かっこを省略する構文は、理論的には可能ですが、SPVMでは、単一ファイルの中で、静的な構文解析を完了できるということを重要視しました。
gcc 4.3で確認しており、保証される最低のバージョンはgcc 4.3です。C99がサポートされている必要があります。
残念ながら、SPVMには、符号なし整数型はありません。
Unix、Linux、macOS、Windowsに対応しています。
コンテキストは存在しません。関数の呼び出しには括弧が必要です。三項演算子はありません。シングルクォートは、文字定数です。
標準関数や標準モジュールは、Perlとは完全に異なっています。
サブルーチンは、必ずメソッドか、クラスメソッドになります。サブルーチンの絶対名での呼び出しはできません。
モジュールの拡張子は「spvm」です。
型はすべて静的型です。サブルーチン呼び出しは、コンパイル時に解決されます。配列は静的です。動的配列とハッシュは、モジュールとして提供されます。
スレッドは、コアではサポートされていませんが、エクステンションを使ったユーザーモジュールを作成することで、間接的に利用できます。
SPVMはシングルスレッドで動くように設計されています。シングルスレッドは、利用者にとって簡単で安全な設計です。
スレッドの機能はSPVMのコアにはありませんが、CやC++のスレッドライブラリを利用して、エクステンションから利用することはできます。
エクステンションでは、スレッド用のSPVMの実行環境を生成して、スレッド上で、利用できます。
SPVMのモジュールとして作成すれば、SPVMからサブルーチンを通して、間接的にスレッドを利用できます。
エクステンションとは、SPVMからC/C++の関数を呼び出すための仕組みのことです。
プリコンパイルとは、SPVMのサブルーチンを、コンパイル時に機械語に変換する仕組みのことです。機械語に変換されたサブルーチンは、高速に実行できます。
precompileが指定されたサブルーチンを含むモジュールファイルは、コンパイル時に、Cのソースコードに変換されます。
precompile sub sum : int ($num1 : int, $num2 : int) { return $num1 + $num2; }
Cのソースコードは、ビルドディレクトリの中に作成されます。
生成されたCのソースコードは、Perlをコンパイルしたコンパイラ(通常はgccかclang)によって、機械語(.oの拡張子を持つオブジェクトファイル)にコンパイルされます。
機械語に変換された後、各OSで呼び出すことのできる共有ライブラリ(.soや.dll)にリンクされます。
プリコンパイルする場合に、必要となるディレクトリのことです。
ビルドディレクトリを利用することをSPVMに教えるにはSPVM::BuildDirモジュールを使用する必要があります。
use SPVM::BuildDir;
スクリプトがあるディレクトリの「spvm_build」というディレクトリがデフォルトのビルドディレクトリ名になります。
ビルドディレクトリ名を自分で指定したい場合は、次のようにします。
use FindBin; use SPVM::BuildDir "$FindBin::Bin/mydir;
SPVMでは、コールバック型とは、実装を持たないメソッドが一つだけ定義されているパッケージのことをいいます。
コールバック型のひとつの例は、標準モジュールである「SPVM::Comparable」です。
package SPVM::Comparable : callback_t { sub compare : int ($self : self, $object1 : object, $object2 : object); }
機能としては、C言語の関数ポインタに似ています。
残念なことですが、SPVMにはジェネリクスはありません。SPVMは、コンパイル時の型決定性よりも、型の簡単さを選択しました。
コンテナの要素は、汎用オブジェクト型で定義してください。汎用オブジェクトから実際のオブジェクトを取得するためには、型キャストが必要です。
sub add : void ($self : self, $object : ojbect) { ... } sub get : object ($self : self, $index : int) { ... }
my $list = SPVM::List->new; $list->add("hello!"); my $str = (string)$list->get(0);
残念ですが、継承はありません。SPVMでは「汎用オブジェクト型」「汎用オブジェクト配列型」「コールバック型」を使って、ポリモーフィズムを実現します。
サブルーチンのオーバーロードはありません。サブルーチンは、サブルーチン名で一意的に識別されます。
採用されていない最も大きな理由は、Perl自体が型を持たないために、PerlからSPVMのサブルーチンへ渡す値の型が決定できないためです。
このためSPVMでは、サブルーチン名によって、戻り値と引数の型がわかるように設計されています。
はい、浮動小数点の表現方法、および演算は、処理系に依存します。
二つの連続したアンダーラインは、エクステンションにおいて、パッケージ名とサブルーチン名の区切りとして利用されるためです。
エクステンションはC言語で書かれます。
# SPVMのサブルーチン package Foo::Bar { sub baz : void () { } }
// エクステンションにおける関数名 SPNATIVE__Foo__Bar__baz(SPVM_ENV* env, SPVM_VALUE* stack) { }
SPVMの「Foo::Barパッケージのbazサブルーチン」はエクステンションの「SPNATIVE__Foo__Bar__baz」に対応します。これは1対1に対応し、相互に名前の変換が可能です。
残念ながら、SPVMからPerlのサブルーチンを呼び出すことはできません。
一度代入した値を変更できなくする機能はありませんが、他の機能の組み合わせで実現することができます。
コンパイル時定数で数値型の場合
定数サブルーチンを使用します。
package Foo { sub VAL : double () { return 5.1234; } }
コンパイル時定数でオブジェクト型の場合
パッケージ変数を定義します。パッケージ変数に読み込み用のアクセッサを定義します。BEGINブロックを使って、パッケージ変数を初期化します。
package Foo { our $POINT : ro int; BEGIN { $POINT = Point->new; } }
はいできます。
パッケージ変数を定義します。BEGINブロックを使って、パッケージ変数を初期化します。singletonメソッドで、オブジェクトを返します。
package Foo { our $SINGLETON : Foo; BEGIN { $SINGLETON = new Foo; } sub singleton : Foo () { return $SINGLETON; } }
コア機能、標準関数、標準モジュールについては、作者が決定を行っています。その範囲の中であれば、開発への参加が可能です。
バグ報告、ベンチマーク、言語評価、ブログなどでの紹介は歓迎です。
「README」の中に開発手順が記載されています。
木本裕紀(kimoto.yuki@gmail.com)
moti(motohiko.ave@gmail.com)
バグ報告はGitHubのIssueで行うことができます。