昨日の記事はcharsbarさんでした。
本日はyukikimotoが書きます。リレー形式でつなげていくのは、楽しいですね!
この記事はSPVMの日本語公式ドキュメントです。書き始めですが、数時間で、書けるところまで書きます。
Perl Advent Calender 2018をきっかけにに作成されました。
SPVMは、Perlの数値計算の遅さを改善するために開発されました。数値計算と配列の演算を高速化することができます。
仕様が完成した後にベンチマークテストを始める予定ですが、数値計算と配列の演算に対して、Perlの30倍、C言語の1/2の速度を、最初の目標としています。
二つ目の目的は、C/C++のバインディングを簡単にすることです。XSやInline::Cは、C/C++のバインディングという点で、非常に難しいと感じています。
簡単なAPIを使って、メモリ安全に、C/C++のバインディングができる機能を提供しています。
三つ目の目的は、単体で実行可能な実行ファイルを生成することです。
SPVMは、実行ファイルを生成する機能を持っているので、ランタイムなしに、プログラムの実行が可能で、配布することもできます。
共有ライブラリ(ダイナミックリンクライブラリ)の読み込みにも対応しています。
SPVMのソースコードには、MITライセンスが適用されるので、MITライセンスの元で、商用利用が可能です。
たとえば、ボタンなどのGUI部品を備えた、配布可能なWindowsアプリケーションが作成できる予定です。
どのような人をSPVMのユーザーとして想定していますか。
Perlは、遺伝子解析などのビオテクノロジーの分野で利用されていますが、配列操作の遅さに不満を感じている研究者の方がいます。
配列操作を簡単に高速化でき、必要であればC/C++をバインディングし、openMPやSIMDなどの並列計算ができればと考えている研究者の方を想定しています。
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
C99で追加された新しいC言語の数学関数がすべて利用できます。
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の言語仕様の詳細です。
コメントは「#」で始まり改行で終わります。
# コメント
パッケージを定義するには以下の構文を使用します。
package パッケージ名 { }
パッケージ名は「大文字」で始まる必要があります。
「パッケージ名」の後に「:」をつなげてその後ろに「デスクリプタ」を指定することができます。
package パッケージ名 : デスクリプタ { }
パッケージの定義のサンプルです。
# パッケージ名のみ package Point { }
# パッケージ名とデスクリプタ package Point : public { }
パッケージデスクリプタ
パッケージで指定できるデスクリプタの一覧です。
デスクリプタ名 | 役割 |
---|---|
public | このパッケージに対するnewキーワードが他のパッケージから利用できます。 |
private | このパッケージに対するnewキーワードが他のパッケージから利用できません。デフォルトの設定です。 |
interface | このパッケージは「インターフェイス」になります。 |
value_t | このパッケージは「値型」になります。 |
pointer_t | このパッケージは「ポインタ型」になります。 |
「public」と「private」の両方のデスクリプタが指定された場合は、コンパイルエラーになります。
「interface」「value_t」「pointer_t」のひとつより多くが同時に指定されている場合は、コンパイルエラーになります。
パッケージ内部で定義できるもの
パッケージ内部では「use」「パッケージ変数」「フィールド」「列挙」「サブルーチン」が定義できます。
package Foo { # use use Point; # パッケージ変数 our $VAR int; # フィールド has var : int; # 列挙 enum { CONST_VAL } # サブルーチン sub foo : int ($num : int) { } }
SPVMの整数型は以下の4つです。
型名 | 説明 | 範囲 |
---|---|---|
byte | 8bit符号付整数 | -128~127 |
short | 16bit符号付整数 | -32768~32767 |
int | 32bit符号付整数 | -2147483648~2147483647 |
long | 64bit符号付整数 | -9223372036854775808~9223372036854775807 |
縮小型変換とは、数値型において広い型から狭い型への変換が行われる場合に適用される変換の規則のことです。
型の順序は狭い方から「byte」「short」「int」「long」「float」「double」です。
拡大型変換とは、数値型において狭い型から広い型への変換が行われる場合に適用される変換の規則のことです。
型の順序は狭い方から「byte」「short」「int」「long」「float」「double」です。
10進数表現
整数リテラルの数値部は「0~9」の1つ以上の連続した文字で表現されます。
先頭に「+」あるいは「-」の符号をつけることができます。
整数リテラルの型は、デフォルトでは「int型」になります。
整数リテラルがint型で表現できる数値の範囲を超えている場合は、コンパイルエラーになります。
末尾に「L」あるいは「l」のサフィックスをつけることで「long型」の整数リテラルを表現できます。
long型の整数リテラルの場合は、long型で表現できる数値の範囲を超えている場合は、コンパイルエラーになります。
区切り文字として「_」を使用することができます。区切り文字は意味を持ちません。
不正な整数リテラルの表現はコンパイルエラーになります。
整数リテラルがbyte型の変数に代入される場合、あるいはbyte型のサブルーチンの引数として渡される場合で、byte型で表現できる数値の範囲を超えていない場合は、縮小型変換によって、byte型に自動的に変換されます。範囲を超えている場合は、コンパイルエラーとなります。
整数リテラルがshort型の変数に代入される場合、あるいはshort型のサブルーチンの引数として渡される場合で、short型で表現できる数値の範囲を超えていない場合は、縮小型変換によって、short型に自動的に変換されます。範囲を超えている場合は、コンパイルエラーとなります。
整数リテラルのサンプルです。
123 +123 -123 123L 123l 123_456_789 -123_456_789L
16進数表現
整数リテラルの数値部は16進数を使って表現することができます。
数値部を16進数を使って表現するときは「0x」から始めます。
その後ろに「0~9」「a~f」「A~F」のひとつ以上の連続した文字が続きます。
不正な16進数表現は、コンパイルエラーになります。
整数リテラルを16進数で表現したサンプルです。
0x3b4f -0x3F1A 0xDeL 0xFFFFFFFF_FFFFFFFF
8進数表現
整数リテラルの数値部は8進数を使って表現することができます。
数値部を8進数を使って表現するときは「0」から始めます。
その後ろに「0~7」のひとつ以上の連続した文字が続きます。
不正な8進数表現は、コンパイルエラーになります。
整数リテラルを8進数で表現したサンプルです。
0755 -0644 0666L 0655_755
2進数表現
整数リテラルの数値部は2進数を使って表現することができます。
数値部を2進数を使って表現するときは「0b」から始めます。
その後ろに「0」か「1」のひとつ以上の連続した文字が続きます。
不正な2進数表現は、コンパイルエラーになります。
整数リテラルを2進数で表現したサンプルです。
0b0101 -0b1010 0b110000L 0b10101010_10101010
浮動小数点リテラルは「符号部」「数値部」「指数部」「サフィックス」から構成されます。
浮動小数点リテラルには「10進数浮動小数点リテラル」と「16進数浮動小数点リテラル」があります。
「符号部」は「+」か「-」で表現されます。「符号部」の存在は、任意です。「符号部」が存在する場合は、先頭にある必要があります。
「10進数浮動小数点リテラル」は、数値部が「一桁以上の10進数字」で始まる必要があります。
「10進数字」とは「0~9」のことです。
「10進数浮動小数点リテラル」は、数値部に「小数点」が含まれているか、含まれていない場合は「指数部」あるいは「サフィックス」が必要です。
「小数点」とは「.」のことです。
「16進数浮動小数点リテラル」は、数値部が「0x」あるいは「0X」で始まり、その後ろに「一桁以上の16進数字」が続く必要があります。
16進数字とは「0~9」「a~f」「A~F」のことです。
「16進数浮動小数点リテラル」は、「数値部」に「小数点」を含むことができます。
「16進数浮動小数点リテラル」は、「指数部」が必要です。
「指数部」は「指数表現」と「符号付10進整数」で構成されます。
「指数表現」は「10進数浮動小数点リテラル」の場合は「e」あるいは「E」、「16進数浮動小数点リテラル」の場合は「p」あるいは「P」になります。
末尾に「f」あるいは「F」のサフィックスをつけると、浮動小数点リテラルの型は「float型」になります。
末尾に「d」あるいは「D」のサフィックスをつけると、浮動小数点リテラルの型は「double型」になります。
サフィックスが省略された場合は、浮動小数点リテラルの型は「double型」になります。
浮動小数点リテラルが「float型」の場合はC標準の「strtof関数」を使って、文字列からfloat型への変換が行われます。変換が失敗した場合は、コンパイルエラーになります。
浮動小数点リテラルが「double型」の場合はC標準の「strtod関数」を使って、文字列からdouble型への変換が行われます。変換が失敗した場合は、コンパイルエラーになります。
無限大を表現する浮動小数点リテラルはありません。標準関数である「INFINITY関数」「INFINITYF関数」を使用してください。
非値を表現する浮動小数点リテラルはありません。標準関数である「NAN関数」「NANF関数」を使用してください。
浮動小数点リテラルのサンプルです
1.32 -1.32 1.32f 1.32F 1.32e3 1.32e-3 1.32E+3 1.32E-3 0x3d3d.edp0 0x3d3d.edp3 0x3d3d.edP3 0x3d3d.edP-3f
未定義は「undef」で表現されます。
undef
未定義値は、任意のオブジェクト型の変数に代入することができます。
未定義値はオブジェクト型の値と「==」「!=」演算子を使用して、比較することができます。未定義値は、生成されたオブジェクトと等しくない事が保証されます。
未定義は条件部で使われた場合は、偽になります。
未定義値は、エクステンションにおいてC言語の値として利用された場合は、0と等しくなることが保証されます。
Perlと異なる点は、SPVMにおいては「undef」は関数ではなくリテラルであるということです。
条件部とは、条件判定が行われる部分のことです。SPVMでは、以下の部分が条件部となります。
if文のかっこの中。
if (条件部) { }
unless文のかっこの中。
unless (条件部) { }
forのかっこの中の二つ目。
for (初期化;条件部;次の値;) { }
whileのかっこの中。
while (条件部) { }
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には、符号なし整数型はありません。
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 : interface { 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);
サブルーチンのオーバーロードはありません。サブルーチンは、サブルーチン名で一意的に識別されます。
採用されていない最も大きな理由は、Perl自体が型を持たないために、PerlからSPVMのサブルーチンへ渡す値の型が決定できないためです。
このためSPVMでは、サブルーチン名によって、戻り値と引数の型がわかるように設計されています。
はい、浮動小数点の表現方法、および演算は、処理系に依存します。
コア機能、標準関数、標準モジュールについては、作者が決定を行っています。その範囲の中であれば、開発への参加が可能です。
バグ報告、ベンチマーク、言語評価、ブログなどでの紹介は歓迎です。
「README」の中に開発手順が記載されています。
木本裕紀(kimoto.yuki@gmail.com)
moti(motohiko.ave@gmail.com)
バグ報告はGitHubのIssueで行うことができます。
2018年12月12日
明日はaeroastroさんです。