接上一次内容。
阮一峰老师在微博发了一个问题(原题见此),一个十位数,每一位的数字都不相同,且必须满足以下条件,求该十位数。
上一次使用了标准的循环来解题,当时解题需要 7.2 秒左右。
时隔这么久,可能和浏览器更换、升级、插件数量等有关,同样的算法现在是 8.1 秒左右。
当时就在想,如果用 worker 来实现多线程,是否可以更快。
书写过程没什么,本质计算逻辑还是和之前一样。所以就直接贴成品代码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const compute = (thread, start, end, next) => { const result = []
let count = 0
const update = () => { count++ if (count === thread) { next && next(result) } }
const task = (start, end) => { const rnd = Math.random().toString().substring(2) const myWorker = new Worker('worker.js', { name: 'worker-' + rnd }) myWorker.postMessage([start, end]) myWorker.addEventListener('message', (event) => { result.push(...event.data) update() }, false) }
const step = (end - start) / thread for (let i = 0; i < thread; i++) { task(start + step * i, start + step * (i + 1)) } }
const d1 = new Date()
const start = 1000000000 const end = 10000000000 compute(window.navigator.hardwareConcurrency, start, end, (arr) => { const d2 = new Date() console.log('cost:', (d2 - d1) / 1000, 'data:', arr) })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function find (name, start, end) { const d1 = new Date() console.log(`[${name}]START:`, d1.toJSON())
const result = [] const s = new Set() let newStart = start while (newStart % 10 !== 0) newStart++
for (let i = newStart; i < end; i = i + 10) { if ( (((i / 100000000) | 0) % 2 === 0) && (((i / 10000000) | 0) % 3 === 0) && (((i / 1000000) | 0) % 4 === 0) && (((i / 100000) | 0) % 5 === 0) && (((i / 10000) | 0) % 6 === 0) && (((i / 1000) | 0) % 7 === 0) && (((i / 100) | 0) % 8 === 0) && (((i / 10) | 0) % 9 === 0) ) { s.clear() for (let j = 0; j < 10; j++) s.add(i.toString().substring(j, j + 1)) if (s.size === 10) { result.push(i) console.log(`[${name}]FIND:`, i) } } }
const d2 = new Date() console.log(`[${name}]END:`, d2.toJSON()) console.log(`[${name}]COST:`, (d2 - d1) / 1000) return result }
self.addEventListener('message', function (e) { const [start, end] = e.data const result = find(self.name, start, end) self.postMessage(result) self.close() }, false)
|
唯独要注意的是,只有浏览器才支持 worker。node 是不支持的。 node 有自己专属的多线程写法。所以要准备一个 html 文件,引入 main.js
查看。
至于上面 main.js
的 window.navigator.hardwareConcurrency
,经过我测试,发现只有当线程数和笔记本逻辑处理器相同时候,速度最快。我的是双核四线程,这里就是 4。启动过多的 worker 不能提升速度,反而创建 worker 还会耽误时间,不过影响不是特别大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [worker-15395305864062148]START: 2021-02-21T04:04:42.559Z [worker-9661691444549567]START: 2021-02-21T04:04:42.578Z [worker-04593373545932056]START: 2021-02-21T04:04:42.804Z [worker-35648471652046143]START: 2021-02-21T04:04:42.855Z [worker-9661691444549567]FIND: 3816547290 [worker-15395305864062148]END: 2021-02-21T04:04:46.761Z [worker-15395305864062148]COST: 4.202 [worker-9661691444549567]END: 2021-02-21T04:04:46.882Z [worker-9661691444549567]COST: 4.304 [worker-35648471652046143]END: 2021-02-21T04:04:46.963Z [worker-35648471652046143]COST: 4.108 [worker-04593373545932056]END: 2021-02-21T04:04:46.994Z [worker-04593373545932056]COST: 4.19 cost: 4.546 data: [3816547290]
|
同样的算法,只需要 4.5 秒左右了,速度提升 40% 多。还是挺明显的。
小结
我的平时工作中,页面几乎不会用到大量数据计算,所以 worker 几乎没有用户之地。
非要用到的话,估计也是利用 worker 可以后台运算,不卡主线程而已。工作中觉得能用到的地方只有全国地址表筛选、金融理财等历史数据预处理。本身这两个地方数据量不大,最多也就在几万的数量级,而且还几乎没有什么数据预处理,所以现在也没用到 worker。
至于使用多线程提升性能,方案靠谱,但仍然是需要数据量非常大的时候才有效。如果本身就是 5 秒的话,改用 worker 估计也就是 4 秒。提升就没那么明显了。但前端什么时候会有这么大量的数据?
一开始我本以为一个线程 8 秒,两个线程求解 4 秒多就行了。四个线程岂不是不到 3 秒就完事了。现在来看还是想多了。不过我仍然感觉和逻辑处理器核心个数有关,之前的算法是一个核心在运行,现在可以用到两个核心(笔记本只有两个核心)了。
–END–