Skip to content

制限事項

以下は、現在の実装における既知の制限事項のリストです。制限には、セマンティクスの観点からの欠点、性能の制限、実装の制限(これは避けられない)などがあります。

些細なバグは「長期的なバグ」でない限り、リストアップしていません。

再入可能性なし

単一のDuktapeヒープ、つまり同じガベージコレクタを共有するコンテキストは、リエントラントではありません。C/C++のスレッドで、特定のDuktapeヒープに対して一度にDuktape APIを呼び出せるのは1つだけです(ただし、呼び出すスレッドは時間の経過とともに変更される可能性があります)。スレッド化」を参照してください。

文字列とバッファの制限

内部表現では、文字列の最大長は 2**31-1 (0x7fffffff) バイト(文字ではありません) までです。16ビットのコードポイントは最悪の場合UTF-8の3バイトにエンコードされるため、 動作が保証される最大文字列長は約0.7G文字となります。

バッファの値も 2**31-1 (0x7fffffff) バイトに制限されます。

プロパティの制限

オブジェクトは最大で DUK_HOBJECT_MAX_PROPERTIES (内部定義) を持つことができます。現在のところ、この制限は 0x7ffffff です。

配列の制限

配列のアイテム・インデックスが 2**31-1 の制限 (0x7ffffff) を超えると、Duktape には配列のセマンティクスに関するいくつかの既知のバグが発生します。

空のマッチを超える正規表現量化子

正規表現エンジンは、空のマッチ上で量化子が使用されたときに行き詰まりますが、最終的には内部再帰(または実行ステップ)制限で救済されます。たとえば、次のコードは "no match" の結果を生成するはずですが、代わりに内部再帰の制限にヒットします。

sh
$ duk
duk> t = /(x*)*/.exec('y');
RangeError: regexp executor recursion limit
    at [anon] (duk_regexp_executor.c:145) internal
    at exec () native strict preventsyield
    at global (input:1) preventsyield

Duktapeはロケールを完全にサポートしていません。

Duktape はローカル時刻の概念をサポートしていますが、ロケールに関連する他の機能、例えば、ロケール固有の日付書式、ロケール固有の文字列比較、ロケール/言語固有の Unicode 規則(トルコ語、アゼリ語、リスアニア語のケース変換規則など)はサポートされていません。

Unicodeの大文字・小文字変換は、ロケールや文脈に依存しない。

E5 15.5.4.16から15.5.4.19項では、Unicode SpecialCasing.txtのコンテキストとロケール処理を要求しています。しかし、Duktapeは現在、"current locale "の概念を持っていない。

デフォルトでないプロパティ属性を使用した場合の配列のパフォーマンス

すべての配列要素は、書き込み可能で、列挙可能で、設定可能であることが期待されています(新しいプロパティのデフォルト・プロパティ属性)。この仮定が一時的にでも破られた場合、オブジェクトの「配列部分」全体は永久に放棄され、配列エントリーは「エントリー部分」に移動されます。この場合、使用されているすべての配列インデックスを明示的な文字列キー("0"、"1 "など)としてインターフェイスすることになります。これはコンプライアンス上の問題ではありませんが、パフォーマンスを低下させます。

Object.defineProperty() を用いて要素を書き込む際の配列の性能

Object.defineProperty() を使用して数値添字の配列要素を書き込む場合、現在の実装では内部の「配列部分」を放棄しているため、後の配列アクセスが非常に遅くなります。これを避けるには、a[123] = 321 のような直接代入で配列要素を記述してください。

グローバル/評価コードに生成されるバイトコードは、関数コードより遅い。

グローバルコードや評価コード用に生成されたバイトコードは、変数をレジスタに静的に割り当てることができず、明示的な名前ベースの変数読み取り/書き込みアクセスが使用されます。関数コード用に生成されたバイトコードにはこの制限がありません。ほとんどの変数はレジスタに静的に割り当てられ、アクセスには直接レジスタ参照が使用されます。

これは、トップレベルのグローバル/評価コードを長時間実行するのでなければ、些細な問題です。回避策は簡単で、トップレベルから呼び出す関数の中にコードを入れておくことです。

javascript
function main() {
    // ...
}
main();

また、この目的のために無名関数を使用するイディオムもよくあります。

javascript
(function () {
    // ...
})();

関数テンポラリは、予想以上に長くガベージコレクションのために生き続けるかもしれません

ECMAScript の関数は、固定されたレジスタのセットでバイトコードにコンパイルされます。いくつかのレジスタは引数や変数バインディングのために予約され、他のものはテンポラリとして使用されます。すべてのレジスタはガベージコレクションの観点から生きているとみなされ、関数が実際にはもう参照できない古い値を含む一時的なレジスタであってもです。このような一時的なレジスタは、他の式の評価によって上書きされるか、関数が終了するまで到達可能であると見なされます。関数の終了は、ガベージコレクションを確実にするために、唯一容易に予測できる条件です。

もし、非常に長い時間実行し続ける関数があるならば、その関数には必要最小限の変数とテンポラリしかないはずです。例えば、次のようなコード構成にすることができます。

javascript
function runOnce() {
    // run one iteration, lots of temporaries
}

function foreverLoop() {
    for (;;) {
        runOnce();
    }
}

これは通常、長時間稼働する関数がなければ問題にはならない。

関数インスタンスはマークアンドスイープによってのみガベージコレクションされます

ECMAScript のすべての関数インスタンスは、デフォルトでは、関数のために自動的に作成されたプロトタイプオブジェクトとの参照ループ内にあります。関数インスタンスの prototype プロパティは prototype オブジェクトを指し、prototype のコンストラクタのプロパティは関数インスタンスに戻ります。このような参照ループを収集できるのは、今のところマークアンドスイープだけです。参照カウントのみで構築した場合(推奨しません)、関数インスタンスがメモリをリークしているように見えることがあります。メモリは、関連するヒープが破棄されたときに解放されます。

参照ループを手動で解除することができます (ただし、これは少し面倒です)。

javascript
var f = function() { };
var g = function() { };
var h = function() { };
Duktape.fin(f, function() { print('finalizer for f'); });
Duktape.fin(g, function() { print('finalizer for g'); });
Duktape.fin(h, function() { print('finalizer for h'); });

// not collected until heap destruction in a reference counting only build
f = null;            // not collected immediately

// break cycle by deleting 'prototype' reference (alternative 1)
g.prototype = null;
g = null;            // collected immediately, finalizer runs

// break cycle by deleting 'constructor' reference (alternative 2)
h.prototype.constructor = null;
h = null;            // collected immediately, finalizer runs

// mark-and-sweep triggers finalizer for 'f'
Duktape.gc();

内部技術的な理由により、名前付き関数式は内部環境レコードオブジェクトとの参照ループの中にもあります。このループはユーザーコードから抜け出すことができず、マークアンドスイープのみがこのような関数を収集することができます。通常の関数宣言や無名関数にはこの制限はありません。例

javascript
var fn = function myfunc() {
    // myfunc is in reference loop with an internal environment record,
    // and can only be collected with mark-and-sweep.
}

Duktape 2.x以降、マーク・アンド・スイープは常に有効になっており、参照ループに参加しているオブジェクトは最終的に解放されます。設定オプションにより、定期的な「自発的」(緊急ではない)マーク&スイープを無効化することで、時間に敏感な環境での収集の一時停止を減らすことができます。

非標準的な関数の「呼び出し元」プロパティの制限

DUK_USE_NONSTD_FUNC_CALLER_PROPERTY が与えられると、 Duktape は、例えば V8 や Spidermonkey と同様に、非標準の関数インスタンスの呼び出し側プロパティを更新します。しかし、いくつかの制限事項があります。

  • evalコードから(非厳密な)関数が呼び出された場合、Duktapeはcallerに、evalコードが非厳密であればnull、厳密であればeval(組み込み関数evalの参照)をセットします。これは、例えばV8の動作とは異なっています。
  • コルーチンとcallerは相性が悪く、コルーチンのコールスタックが完全に巻き戻された後でもcallerがnullでない状態で残っていることがあります。また、コルーチンがそのコールスタックが巻き戻される前にガベージコレクションされた場合、そのコールスタック内の関数の呼び出し元のプロパティは、今更更新されません。

詳細については、内部の test-bi-function-nonstd-caller-prop.js テストケースを参照してください。

デバッガー休止状態でのガベージコレクション

デバッガー・サポートが有効で、デバッガー・セッションがアクティブで、Duktape が一時停止しているとき、現在いくつかの制限があります。

一時停止中はガベージ・コレクションが無効になるため、Duktape.gc() および duk_gc() への呼び出しは黙って無視されます。