我在 Github explore 上搜索时发现了一个密码生成器(omgopass)(https://github.com/omgovich/omgopass),据说它比其他替代品要快得多。比 600 倍快password-generator(https://www.npmjs.com/package/password-generator)。
这是基准测试omgopass显示:
看到这个后,我记得几周前我做了一个密码生成器,并且没有执行任何基准测试,所以我决定用其他库测试我的方法。
令我惊讶的是,它表现得相当好,在相同的基准测试中获得第二名,如上所示。即使不尝试也很好。
使用我的 pass 生成器进行基准测试 ( passGenny)(https://github.com/nombrekeff/pass-genny):
注意事项
该基准测试并不能反映库的质量或开发人员的技能,以真正确保应该进行更多的测试和基准测试。
此外,每个库的功能也有所不同,有些是可读的,有些则不可读。有些使用加密进行随机,有些则不使用。
话虽这么说,
让我们让passGenny更快
我决定尝试一下,并尝试优化它,让我们看看原始代码:
class PasswordGenerator { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'.split(''); static symbolsChars = '<>[]{}=?()&%$#@!¡?¿*_-.:;,'.split(''); static numbersString = '0123456789'.split(''); constructor(options = {}) { this.options = { uppercase: true, lowercase: true, symbols: false, numbers: true, readable: false, length: 12, ...options, }; } updateOptions(newOptions = {}) { this.options = { ...this.options, ...newOptions, }; } random(min = 0, max = 10) { return Math.floor( Math.random() * (max - min) + min ); } _getCharactersForOptions() { const combinedCaracters = []; if (this.options.lowercase) combinedCaracters.push(...PasswordGenerator.lowerCaseChars); if (this.options.uppercase) combinedCaracters.push(...PasswordGenerator.upperCaseChars); if (this.options.symbols) combinedCaracters.push(...PasswordGenerator.symbolsChars); if (this.options.numbers) combinedCaracters.push(...PasswordGenerator.numbersString); return combinedCaracters; } generate() { let combinedCaracters = this._getCharactersForOptions(); let password = ''; for (let c = 0; c < this.options.length; c++) { password += combinedCaracters[this.random(0, combinedCaracters.length)]; } return password; } }
这个类的作用是,从一组选项中生成密码。它通过将(选项)允许的所有字符组合到一个数组中来实现此目的,然后迭代密码的长度(由选项定义),并从该数组中获取随机字符。
够简单吧?现在,我认为我们可以对此进行一些优化,好吗?
优化1
好的,我注意到的第一件事是,在 中_getCharactersForOptions,我使用数组来保存有效字符。使用扩展运算符将它们附加到combinedCaracters数组中。
这有点多余,因为我们可以一直使用字符串。连接字符串比组合数组便宜得多。
让我们看看我们可以改变什么。
首先我们需要改变存储字符的方式,我们不需要分割它们:
class PasswordGenerator { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'; static symbolsChars = '<>[]{}=?()&%$#@!¡?¿*_-.:;,'; static numbersString = '0123456789'; // ... more code }
太好了,现在我们来修改_getCharactersForOptions方法:
class PasswordGenerator { _getCharactersForOptions() { let combinedCaracters = ''; if (this.options.lowercase) combinedCaracters += PasswordGeneratorFast1.lowerCaseChars; if (this.options.uppercase) combinedCaracters += PasswordGeneratorFast1.upperCaseChars; if (this.options.symbols) combinedCaracters += PasswordGeneratorFast1.symbolsChars; if (this.options.numbers) combinedCaracters += PasswordGeneratorFast1.numbersString; return combinedCaracters; } }
请注意我们现在如何返回一个字符串,而不是一个数组。
让我们看看它在基准测试中的表现如何
妈的,没想到变化这么大,几乎翻了一倍。
正如您所看到的,在这种特殊情况下,字符串的性能比数组好得多。
可是等等
我想我可以进一步优化它,你可能已经注意到,使用相同的选项,结果_getCharactersForOptions总是相同的。这意味着我们不需要连接每个密码上的字符串,我们只需要在选项更改时生成它们。
我们可以通过多种方式来解决这个问题,使用记忆化(可能更好)、围绕对象创建代理或我接下来将向您展示的简单方法。
优化2
我要做的是,将选项设为私有,并迫使人们使用updateOptions方法更改选项。这将允许我标记选项是否已更改。
让我们看一下完整的示例,然后我将对其进行分解:
class PasswordGeneratorFast2 { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'; static symbolsChars = '<>[]{}=?()&%$#@!¡?¿*_-.:;,'; static numbersString = '0123456789'; constructor(options = {}, randomFn) { this._options = { uppercase: true, lowercase: true, symbols: false, numbers: true, readable: false, length: 12, ...options, }; this._random = randomFn || mathRandom; this._combinedCharacters = ''; this._optionsChanged = true; this._getCharactersForOptions(); } updateOptions(newOptions = {}) { this._options = { ...this._options, ...newOptions, }; this._optionsChanged = true; } generate() { const combinedCaracters = this._getCharactersForOptions(); const length = combinedCaracters.length; let password = ''; for (let c = 0; c < this._options.length; c++) { password = password.concat(combinedCaracters[this._random(0, length)]); } return password; } _getCharactersForOptions() { // If options have not changed, we can return the previoulsy combined characters if (!this._optionsChanged) return this._combinedCharacters; let combinedCaracters = ''; if (this._options.lowercase) combinedCaracters += PasswordGeneratorFast1.lowerCaseChars; if (this._options.uppercase) combinedCaracters += PasswordGeneratorFast1.upperCaseChars; if (this._options.symbols) combinedCaracters += PasswordGeneratorFast1.symbolsChars; if (this._options.numbers) combinedCaracters += PasswordGeneratorFast1.numbersString; // Update and mark options as not changed this._combinedCharacters = combinedCaracters; this._optionsChanged = false; return this._combinedCharacters; } }
我们添加, 指示自上次调用_optionsChanged以来选项是否已更改。_getCharactersForOptions
我们将最后一个组合字符存储在_combinedCharacters
我们修改_getCharactersForOptions,这样如果选项没有改变,我们返回最后生成的_combinedCharacters
我们改变password +=(password.concat()在我的测试中,它 concat 的表现比 += 更好)
就是这样,让我们看看它是如何做到的:
如果你问我的话,令人印象深刻的是,我们传球的速度比吉尼快了一倍,以相当大的优势取得了第一。如果我们像omgovich那样表述它,它比密码生成器passGenny(https://www.npmjs.com/package/password-generator)快 2,444 倍
从中可以得到什么?
保持简单可以等同于高性能
如果不需要,不要使用数组
检查是否每次都需要执行操作
如果您需要性能,有时较小的事情会产生最大的差异
PD:我不是性能专家,所以我可能会错过一些重要的事情,如果我错过了某些内容或误解了结果,请告诉我。
网友反馈留言1
您还可以进行一些改进(主要针对 DX,而不是针对性能):
使用 使私有成员真正成为私有成员#。
使用 getter 和 setter。
将静力学移至常量。
的经典类过程this.generate = this.generate.bind(this);,因此人们可以在地图等内容中使用该函数而不会出现问题。
应用这些建议,代码看起来像这样(我添加了 JSDocs 以获得更好的 DX):
/** * @typedef PasswordGeneratorOptions * @property {number} [length=12] * @property {boolean} [lowercase=true] * @property {boolean} [numbers=true] * @property {typeof mathRandom} [randomFunction=mathRandom] * @property {boolean} [symbols=false] * @property {boolean} [uppercase=true] *//** * @param {number} min * @param {number} max */const mathRandom = (min, max) => Math.floor(Math.random() * (max - min) + min);const uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";const lowercaseChars = "abcdefghijklmnopqrstuvwxyz";const symbolsChars = "<>[]{}=?()&%$#@!¡?¿*_-.:;,";const numbersString = "0123456789";export class PasswordGeneratorFast { /** @type {PasswordGeneratorOptions} */ #options = { length: 12, lowercase: true, numbers: true, randomFunction: mathRandom, symbols: false, uppercase: true }; #characters = ""; #optionsChanged = true; set options(options) { this.#optionsChanged = true; this.#options = { ...this.#options, ...options }; } get options() { return this.#options; } get characters() { if (this.#optionsChanged) { this.#characters = (this.#options.lowercase ? lowercaseChars : "") + (this.#options.uppercase ? uppercaseChars : "") + (this.#options.symbols ? symbolsChars : "") + (this.#options.numbers ? numbersString : ""); this.#optionsChanged = false; } return this.#characters; } /** @param {PasswordGeneratorOptions} options */ constructor(options = {}) { this.options = options; this.generate = this.generate.bind(this); } generate() { const { characters } = this; const length = characters.length; let password = ""; for (let char = 0; char < this.#options.length; char++) { password = password.concat( characters[this.#options.randomFunction(0, length)] ); } return password; }}
性能方面我的可能是最差的(没有测试它,但我认为 setter/getter 的性能不如仅使用方法,我可能是错的),但除了性能之外,我们总是必须考虑 DX,并且从我的从角度来看,在处理类时,getter 和 setter 提供了更好的 DX。我个人更喜欢只使用函数,甚至不去上课。
网友反馈留言1
我的主要原因主要是用法:文章来源:https://www.toymoban.com/article/377.html
import { Something } from "./Something"; console.log(Something.aValue); // vs import { aValue } from "./Something"; console.log(aValue);
我知道,超级利基,但是以前从类中有用的封装现在我从模块中获得:D文章来源地址https://www.toymoban.com/article/377.html
到此这篇关于优化 JavaScript - 密码生成器(速度提高 2.15 倍)的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!