[解決済み] なぜbindはクロージャより遅いのか?
質問
以前の投稿者からの質問 JavascriptのFunction.bindとClosureの比較:どのように選択するのですか?
で、一部このような回答がありましたが、これはbindの方がclosureより速いはずということらしいです。
スコープトラバーサルとは、異なるスコープに存在する値 (変数,オブジェクト) を取得するために手を伸ばしたとき、したがって オーバーヘッドが追加されます(コードの実行速度が遅くなります)。
bind を使うと、既存のスコープを持つ関数を呼び出すことになります。 スコープの走査は行われません。
2 つの jsperfs は、bind は実際には 閉鎖 .
上記へのコメントとして投稿されたものです
と、書くことにしました。 私自身のjsperf
では、なぜ bind はこんなに遅いのでしょうか (chromium では 70% 以上)?
速くなく、クロージャが同じ目的を果たすことができるので、bindは避けるべきなのでしょうか?
どのように解決するのですか?
Chrome 59 の更新: 以下の回答で予測したように、新しい最適化コンパイラでバインドが遅くなることはなくなりました。以下はそのコードです。 https://codereview.chromium.org/2916063002/
ほとんどの場合、それは重要ではありません。
アプリケーションを作成しているのでなければ
.bind
がボトルネックになるようなアプリケーションを作るのでなければ、私は気にしないでしょう。ほとんどの場合、読みやすさの方がパフォーマンスよりもずっと重要です。私は、ネイティブの
.bind
を使用することは、通常、より読みやすく、保守しやすいコードを提供することになります。
しかし、そうです、重要なときには
.bind
はより遅いです。
はい。
.bind
はクロージャよりもかなり遅いです - 少なくとも Chrome では、少なくとも現在の方法で実装されている
v8
. 私は個人的に、パフォーマンスの問題でNode.JSに切り替えなければならなかったことがあります(より一般的には、クロージャはパフォーマンスを重視する状況ではちょっと遅いです)。
なぜかというと なぜなら
.bind
アルゴリズムは、関数を別の関数でラップして
.call
または
.apply
. (面白いことに、これは toString を [ネイティブ関数] に設定した関数も返します)。
これには仕様の観点と、実装の観点の2つの見方があります。両方を観察してみましょう。
まず 仕様で定義されているバインドアルゴリズムを見てみましょう。 :
- Targetをこの値とします。
- IsCallable(Target)がfalseの場合、TypeError例外を投げる。
- thisArgの後に提供されたすべての引数値(arg1、arg2など)を順番に並べた新しい(空の可能性がある)内部リストをAとする。
...
(21. Fの内部メソッド[[DefineOwnProperty]]に引数 "arguments"、PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, falseを指定して呼び出します。
(22.Fを返す。
かなり複雑なようだ、ラップよりもずっと。
次に が Chrome でどのように実装されているか見てみましょう。 .
確認しましょう
FunctionBind
をv8(chrome JavaScript engine)のソースコードで確認してみましょう。
function FunctionBind(this_arg) { // Length is 1.
if (!IS_SPEC_FUNCTION(this)) {
throw new $TypeError('Bind must be called on a function');
}
var boundFunction = function () {
// Poison .arguments and .caller, but is otherwise not detectable.
"use strict";
// This function must not use any object literals (Object, Array, RegExp),
// since the literals-array is being used to store the bound data.
if (%_IsConstructCall()) {
return %NewObjectFromBound(boundFunction);
}
var bindings = %BoundFunctionGetBindings(boundFunction);
var argc = %_ArgumentsLength();
if (argc == 0) {
return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
}
if (bindings.length === 2) {
return %Apply(bindings[0], bindings[1], arguments, 0, argc);
}
var bound_argc = bindings.length - 2;
var argv = new InternalArray(bound_argc + argc);
for (var i = 0; i < bound_argc; i++) {
argv[i] = bindings[i + 2];
}
for (var j = 0; j < argc; j++) {
argv[i++] = %_Arguments(j);
}
return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
};
%FunctionRemovePrototype(boundFunction);
var new_length = 0;
if (%_ClassOf(this) == "Function") {
// Function or FunctionProxy.
var old_length = this.length;
// FunctionProxies might provide a non-UInt32 value. If so, ignore it.
if ((typeof old_length === "number") &&
((old_length >>> 0) === old_length)) {
var argc = %_ArgumentsLength();
if (argc > 0) argc--; // Don't count the thisArg as parameter.
new_length = old_length - argc;
if (new_length < 0) new_length = 0;
}
}
// This runtime function finds any remaining arguments on the stack,
// so we don't pass the arguments object.
var result = %FunctionBindArguments(boundFunction, this,
this_arg, new_length);
// We already have caller and arguments properties on functions,
// which are non-configurable. It therefore makes no sence to
// try to redefine these as defined by the spec. The spec says
// that bind should make these throw a TypeError if get or set
// is called and make them non-enumerable and non-configurable.
// To be consistent with our normal functions we leave this as it is.
// TODO(lrn): Do set these to be thrower.
return result;
ここでは、実装の中で高価なものをたくさん見ることができます。すなわち
%_IsConstructCall()
. これはもちろん仕様に従うために必要なことですが、多くの場合、単純なラップよりも遅くなることもあります。
もう一つの注意点として
.bind
はまた少し違っていて、Function.prototype.bindを使って作られたFunctionオブジェクトはprototypeプロパティや[[Code]]、[[FormalParameters]]、[[Scope]]の内部プロパティを持っていない、と仕様書には書かれています"
関連
-
[解決済み] なぜGoogleはJSONレスポンスにwhile(1);を前置するのでしょうか?
-
[解決済み] ループ内のJavaScriptクロージャ - シンプルな実用例
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] JSONPとは何か、なぜ作られたのか?
-
[解決済み] <は<=より速いのか?
-
[解決済み] \0-9]よりも効率が悪い
-
[解決済み] JavaScriptでjson-objectのキーを取得する [重複].
-
[解決済み] Node.jsのES6クラスをrequireで作る
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] パラメータを持つ関数をパラメータとして渡す?
-
[解決済み] TraceurでES6クラスのプライベートメソッドを実装する方法【重複あり
-
[解決済み] ジェスト あるクラスの特定のメソッドをモックする方法
-
[解決済み] JavaScript で範囲を作成する - 奇妙な構文
-
[解決済み] jqueryでdivの要素がオーバーフローしていないかチェックする
-
[解決済み] node.jsで文字列のsha1ハッシュを取得するにはどうすればよいですか?
-
[解決済み] JavaScriptでの大文字小文字を区別しない正規表現
-
[解決済み] イテレータでmap()を使用する
-
[解決済み] Chromeのwebkitインスペクタで「Unsafe JavaScript attempt to access frame with URL...」というエラーが継続的に発生する。
-
[解決済み] Javascript の parseInt() で先頭のゼロを削除する。