node.jsでrequireを適切に使用する

まえがき


少し前に、私が現在取り組んでいるプロジェクトは、モジュラーシステムES2015を使用し始めました。 このJavaScriptテクノロジーに焦点を当てるつもりはありません。というのも、この記事はそれについてではなく、テクノロジーがどのように私を1つの考えに導いたかについてです。


多くの人が知っているように、ES2015モジュールは、 pythonや他の多くのプログラミング言語と構文が非常に似ているスクリプトのインポート/エクスポートです。 例:


 // Helper.js export function includes(array, variable) { return array.indexOf(variable) !== -1; } // main.js import {includes} from 'Helper'; assets(includes([1,2,3], 2), true); 

JavaScriptモジュールに興味がある人なら誰でも、モジュールの最上位(コード付きファイル)でのみインポートとエクスポートが可能であることを知っています。


次の粗雑なコード例はエラーの原因になります。


 // sendEmail.js export default function sendEmails(emails_list) { import sender from 'sender'; export sender; //  - } 

 Exception: SyntaxError: import/export declarations may only appear at top level of a module 

ES2015モジュールとは異なり、 node.jsモジュラーシステムでは、どのネストレベルでもインポートとエクスポートが可能です。


node.js同様のコードはエラーをスローしません:


 // sendEmail.js module.exports = function sendEmails(emails_list) { const sender = require('sender'); exports.sender = sender; //  - } 

このメソッドの利点は、ハンドラーで必要なモジュールが明示的に内部にインポートされ、モジュールの名前空間を詰まらせないことです(インポートされたモジュールが1つのハンドラーでのみ必要な場合、これは特に重要です)。 モジュールデータのエクスポートが遅れる可能性もあります。


主な欠点:


  1. 対応するハンドラーを呼び出すときにのみ、モジュールが存在しないことについて学習します
  2. インポートされたモジュールへのパスが変更される可能性があり、各インポート場所で変更が発生します(たとえば、モジュールでは、 lodash/object/defaultsさまざまなハンドラーで使用されているため、 lodash/defaultsを接続する必要がある4.xバージョンにアップグレードすることにました)。

デブリーフィング


node.jsが使用されるほとんどのタスク-フロントエンドまたはメインWebサーバー、およびnode.js高負荷node.js頻繁に発生します。 サーバーのスループットは可能な限り高くする必要があります。


帯域幅測定


Apacheの素晴らしいユーティリティabは、 Webサーバーのスループットを測定するために使用されます。 あなたがそれに慣れていないなら、私はそれをすることを強くお勧めします。


Webサーバーのコードは、ハンドラーを除いて同じです。
テストは、 express基づいて作成されたifnodeモジュールを使用してnode.js 6.0実行されました


モジュールをハンドラーに直接インポートする


コード:


 const app = require('ifnode')(); const RequireTestingController = app.Controller({ root: '/', map: { 'GET /not_imported': 'notImportedAction' } }); RequireTestingController.notImportedAction = function(request, response, next) { const data = { message: 'test internal and external require' }; const _defaults = require('lodash/object/defaults'); const _assign = require('lodash/object/assign'); const _clone = require('lodash/lang/clone'); response.ok({ _defaults: _defaults(data, { lodash: 'defaults' }), _assign: _assign(data, { lodash: 'assign' }), _clone: _clone(data) }); }; 

結果:


 $ ab -n 15000 -c 30 -q "http://localhost:8080/not_imported" Server Hostname: localhost Server Port: 8080 Document Path: /not_imported Document Length: 233 bytes Concurrency Level: 30 Time taken for tests: 4.006 seconds Complete requests: 15000 Failed requests: 0 Total transferred: 6195000 bytes HTML transferred: 3495000 bytes Requests per second: 3744.32 [#/sec] (mean) Time per request: 8.012 [ms] (mean) Time per request: 0.267 [ms] (mean, across all concurrent requests) Transfer rate: 1510.16 [Kbytes/sec] received Percentage of the requests served within a certain time (ms) 50% 6 66% 7 75% 8 80% 8 90% 10 95% 15 98% 17 99% 20 100% 289 (longest request) 

ファイルの先頭にモジュールをインポートする


コード:


 const app = require('ifnode')(); const _defaults = require('lodash/object/defaults'); const _assign = require('lodash/object/assign'); const _clone = require('lodash/lang/clone'); const RequireTestingController = app.Controller({ root: '/', map: { 'GET /already_imported': 'alreadyImportedAction' } }); RequireTestingController.alreadyImportedAction = function(request, response, next) { const data = { message: 'test internal and external require' }; response.ok({ _defaults: _defaults(data, { lodash: 'defaults' }), _assign: _assign(data, { lodash: 'assign' }), _clone: _clone(data) }); }; 

結果:


 $ ab -n 15000 -c 30 -q "http://localhost:8080/already_imported" Server Hostname: localhost Server Port: 8080 Document Path: /already_imported Document Length: 233 bytes Concurrency Level: 30 Time taken for tests: 3.241 seconds Complete requests: 15000 Failed requests: 0 Total transferred: 6195000 bytes HTML transferred: 3495000 bytes Requests per second: 4628.64 [#/sec] (mean) Time per request: 6.481 [ms] (mean) Time per request: 0.216 [ms] (mean, across all concurrent requests) Transfer rate: 1866.83 [Kbytes/sec] received Percentage of the requests served within a certain time (ms) 50% 5 66% 6 75% 6 80% 7 90% 8 95% 14 98% 17 99% 20 100% 38 (longest request) 

結果分析


ファイルの先頭にモジュールをインポートすると、1回のリクエストの時間が約23% (!)(ハンドラーに直接インポートする場合と比較して)短縮されました。これは非常に重要です。


結果のこのような大きな違いは、 require関数の動作にrequireます。 インポートする前に、 requireは要求されたコンポーネントへの絶対パスを見つけるためのアルゴリズムを指します(アルゴリズムはnode.jsのドキュメントで説明されています)。 パスが見つかったら、モジュールがキャッシュされているかどうかを確認するrequireます。キャッシュされていない場合は、 通常のfs.readFileSync.jsおよび.json形式で呼び出しドキュメント化されprocess.dlopenいないprocess.dlopen呼び出して C ++モジュールを読み込みます。


注:ハンドラーにモジュールを直接インポートする場合のキャッシュを「ウォームアップ」しようとしました( abユーティリティを実行する前に、モジュールは既にキャッシュされていました)-パフォーマンスが1〜2%向上しました。


結論


node.jsをサーバーとして使用する場合(TCP / UDPとHTTP(S)のどちらでも違いはありません)、次のようになります。


  1. モジュールの読み込みに関連する不要な同期操作(非同期サーバーとしてnode.js 使用する主なアンチパターンの 1つ)を回避するために、ファイルの先頭ですべてのモジュールをインポートする必要があります。
  2. 要求されたモジュールの絶対パスの計算にリソースを浪費することはできません(これがパフォーマンスの損失の主な場所です)。


Source: https://habr.com/ru/post/J303352/


All Articles