一个十位数... - worker 解法尝试

接上一次内容。

阮一峰老师在微博发了一个问题(原题见此),一个十位数,每一位的数字都不相同,且必须满足以下条件,求该十位数。

上一次使用了标准的循环来解题,当时解题需要 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
// main.js
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
// worker.js
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.jswindow.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–