From 6be73feaeb31dbcc1547da648341c0b6f0d2d2bb Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 8 Jun 2016 08:18:26 -0700 Subject: [PATCH] crypto,tls: perf improvements for crypto and tls getCiphers Improve performance of crypto.getCiphers, getHashes, getCurves and tls.getCiphers by consolidating filterDuplicates logic, adding caching of output, and streamlining filterDuplicates implementation. Benchmarks: crypto.getCiphers n=1 v6.2.1 = 2559.3, new = 15890 ...... -83.89% crypto.getCiphers n=5000 v6.2.1 = 3516.3, new = 24203000 ... -99.99% tls.getCiphers n=1 v6.2.1 = 3405.3, new = 14877 ...... -77.11% tls.getCiphers n=5000 v6.2.1 = 6074.4, new = 24202000 ... -99.97% PR-URL: https://github.com/nodejs/node/pull/7225 Reviewed-By: Fedor Indutny Reviewed-By: Brian White --- benchmark/crypto/get-ciphers.js | 20 ++++++++++++++++++ lib/crypto.js | 36 +++++++++------------------------ lib/internal/util.js | 31 ++++++++++++++++++++++++++++ lib/tls.js | 16 +++++---------- 4 files changed, 65 insertions(+), 38 deletions(-) create mode 100644 benchmark/crypto/get-ciphers.js diff --git a/benchmark/crypto/get-ciphers.js b/benchmark/crypto/get-ciphers.js new file mode 100644 index 0000000000..257c9af2fd --- /dev/null +++ b/benchmark/crypto/get-ciphers.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [1, 5000], + v: ['crypto', 'tls'] +}); + +function main(conf) { + const n = +conf.n; + const v = conf.v; + const method = require(v).getCiphers; + var i = 0; + + common.v8ForceOptimization(method); + bench.start(); + for (; i < n; i++) method(); + bench.end(n); +} diff --git a/lib/crypto.js b/lib/crypto.js index 4bce371701..9ffff06f7f 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -632,41 +632,23 @@ exports.randomBytes = exports.pseudoRandomBytes = randomBytes; exports.rng = exports.prng = randomBytes; -exports.getCiphers = function() { - return filterDuplicates(getCiphers()); -}; - - -exports.getHashes = function() { - return filterDuplicates(getHashes()); -}; +exports.getCiphers = internalUtil.cachedResult(() => { + return internalUtil.filterDuplicateStrings(getCiphers()); +}); +exports.getHashes = internalUtil.cachedResult(() => { + return internalUtil.filterDuplicateStrings(getHashes()); +}); -exports.getCurves = function() { - return filterDuplicates(getCurves()); -}; +exports.getCurves = internalUtil.cachedResult(() => { + return internalUtil.filterDuplicateStrings(getCurves()); +}); Object.defineProperty(exports, 'fips', { get: getFipsCrypto, set: setFipsCrypto }); -function filterDuplicates(names) { - // Drop all-caps names in favor of their lowercase aliases, - // for example, 'sha1' instead of 'SHA1'. - var ctx = {}; - names.forEach(function(name) { - var key = name; - if (/^[0-9A-Z\-]+$/.test(key)) key = key.toLowerCase(); - if (!ctx.hasOwnProperty(key) || ctx[key] < name) - ctx[key] = name; - }); - - return Object.getOwnPropertyNames(ctx).map(function(key) { - return ctx[key]; - }).sort(); -} - // Legacy API Object.defineProperty(exports, 'createCredentials', { configurable: true, diff --git a/lib/internal/util.js b/lib/internal/util.js index 8e7e51aab9..e3d9a66b3b 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -121,3 +121,34 @@ exports.normalizeEncoding = function normalizeEncoding(enc) { } } }; + +// Filters duplicate strings. Used to support functions in crypto and tls +// modules. Implemented specifically to maintain existing behaviors in each. +exports.filterDuplicateStrings = function filterDuplicateStrings(items, low) { + if (!Array.isArray(items)) + return []; + const len = items.length; + if (len <= 1) + return items; + const map = new Map(); + for (var i = 0; i < len; i++) { + const item = items[i]; + const key = item.toLowerCase(); + if (low) { + map.set(key, key); + } else { + if (!map.has(key) || map.get(key) <= item) + map.set(key, item); + } + } + return Array.from(map.values()).sort(); +}; + +exports.cachedResult = function cachedResult(fn) { + var result; + return () => { + if (result === undefined) + result = fn(); + return result; + }; +}; diff --git a/lib/tls.js b/lib/tls.js index 80ea0d7697..695edd8c5a 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -1,6 +1,7 @@ 'use strict'; -require('internal/util').assertCrypto(exports); +const internalUtil = require('internal/util'); +internalUtil.assertCrypto(exports); const net = require('net'); const url = require('url'); @@ -21,16 +22,9 @@ exports.DEFAULT_CIPHERS = exports.DEFAULT_ECDH_CURVE = 'prime256v1'; -exports.getCiphers = function() { - const names = binding.getSSLCiphers(); - // Drop all-caps names in favor of their lowercase aliases, - var ctx = {}; - names.forEach(function(name) { - if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase(); - ctx[name] = true; - }); - return Object.getOwnPropertyNames(ctx).sort(); -}; +exports.getCiphers = internalUtil.cachedResult(() => { + return internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true); +}); // Convert protocols array into valid OpenSSL protocols list // ("\x06spdy/2\x08http/1.1\x08http/1.0")