evalが必ずしも悪いわけではない理由

Javascriptの非常に興味深い最適化手法を共有したいと思います。



仮説的な状況を見てみましょう。 サーバーのスタックがあり、いつでも独自の気まぐれで「OK」または「ダウン」状態にできると仮定します。 サーバーインターフェイスでは、名前と現在のステータスのみを確認できます。 しかし、サーバーがどのように配置され、どこから来たのかはわかりませんが、サーバーにアクセスすることはできません。 このコードをサーバーのコンストラクターにしてください:



var Server = function(name){ this.name = name; this.ping = function(){return Math.round(Math.random())? 'ok' : 'down';}; };
      
      





オブザーバーというコンポーネントがあり、彼に委任されたサーバーを監視し、リクエストに応じてすべてのサーバーの状態のハッシュテーブルを提供するとします。 そして、一部の悪魔は、常にこのコンポーネントを激しく引っ張り、サーバーの状態を監視しています。 それをオブザーバーのコンストラクターにします。



 var CasualObserver = function(){ var stack = []; this.add = function(server){ stack.push(server); return this; }; this.check = function(){ var hashTable = {}; for(var i = 0, ln = stack.length; i < ln; i++){ hashTable[stack[i].name] = stack[i].ping(); } return hashTable; } };
      
      





あなたがこのコードを委任されたプログラマーであり、丁寧に、既存のインターフェースを維持しながら最適化するように頼んだプログラマーであるとします。



さて、あなたは座ってこれらの14行を見つめており、頭の中を回転する(皮肉な)唯一の考えは、「しばらくの間交換する価値があるか、それでも価値がないか」ということです。 この思考は正しいものと間違ったものの両方です(一種の思考の重ね合わせ)。 正しいのは、ここで最も時間のかかる操作がサイクルであることです。 間違っているのは、最適化の方法ではなく、それを取り除く方法について考える必要があるということです。 既に用意されているものを操作して、各要素に対して.checkを呼び出すことができるのに、なぜハッシュテーブルを動的に作成する必要があるのですか?



 this.check = function(){ return { stack[0].name : stack[0].ping(), stack[1].name : stack[1].ping(), stack[2].name : stack[2].ping() }; }
      
      





これは、すぐにサイクルを取り除く方法です。 もちろん、あなたは私を愚か者と見なし、こう言います-「ええ、ヴァン。 ただし、3台だけでなく、任意の数のサーバーを使用できます。 また、JSには反射神経がありません。」



JavaScriptでプリミティブなリフレクションが可能であることをご存知ですか? (ご存知ですか?それでは、以下に進みます)はい、はい、JSを使用すると、実行時に既存のコードを変更して新しいコードを作成できます。



 var sum = new Function('a', 'b', 'return alert(a + b);'); sum(2, 3);
      
      





同じ成功で、同じ目的のために、誰もが嫌われたevalを使用できます。 これについての詳細はすでにHabrの他の投稿に書かれています。

ええ、おそらくあなたはすでにすべてを自分で理解しているのですか? もちろん! オブザーバーのコンストラクターを作成してみましょう。オブザーバーは自分でcheckメソッドを作成します。



 var SelfModifyObserver = function(){ var stack = []; this.add = function(server){ stack.push(server); var code = 'return {'; for(var i = 0, ln = stack.length; i < ln; i++){ code += stack[i].name + ':' + 'stack[' + i + '].ping(),'; } code += '};'; this.check = eval('(function(){' + code +'});'); return this; }; this.check = function(){return {};} };
      
      





ワイルドに見える、私はあなたに完全に同意します。 しかし、それは機能します!





このアプローチが実証されているかどうかを確認しましょう。 ラテンアルファベットの各文字に1つずつ、25のサーバーをオブザーバーに追加する小さな関数を作成し、1秒あたり何回サーバーをチェックできるかを確認します。



 var benchmark = function(instance, note){ for(var i=65; i<=90; i++){ instance.add(new Server(String.fromCharCode(i))); } var stamp = new Date().getTime(); var iterations = 0; while(new Date().getTime() - stamp <= 1000){ instance.check(); iterations++; } console.log(iterations + ' iterations per second for ' + note); return true; };
      
      





Node.jsでこれをテストする方法がないことを認めざるを得ませんが、オフィスでのテストでは、このようなソリューションの生産性の向上が私のサーバーで約30%であることが示されました。



そして、これは私のラップトップ上のクロムの結果です:



なるほど! ほとんど空のスペースから30%を絞り出した!



興味をお持ちでしたらありがとうございます。



このアイデアは、JSConf EU 2012のレポートの1つで取り上げられました。



合計:投稿は、他のオブジェクトを観察するためにオブザーバーを正しく記述する方法についてではなく、サイクルを取り除く方法についてではありません。 そして、パフォーマンスを向上させるために、外出先でjavascriptがコードを最適化する方法。



このアプローチを取ったレポート。

そのテキスト版。



All Articles