This video has a BAS barrage script of 15+MB that needs to be parsed, which will cause the page to freeze for about 7 seconds. During this time, the UI is frozen and the experience is very poor. If we use Web Workers for optimization and put the parsing into Web Workers for execution, we can avoid the page freeze caused by blocking the UI thread.
Single-threaded
Use parse to simulate the parsing function
index.js
function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod'
}
console.log(parse(1000));
At this time, the page will freeze for 1 second, and then output 'DIYgod'.
Using Web Workers
index.js
const wk = new Worker('worker.js');
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
worker.js
function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
onmessage = function (e) {
postMessage(parse(e.data));
}
This is the most basic usage of Web Workers. index.js passes 1000 to worker.js, worker.js parses in the background for 1000 ms, and then passes the result 'DIYgod' back to index.js. This way, the parsing will no longer occupy the main JavaScript thread, avoiding page freezing.
Embedded Worker
In the previous step, we loaded two js files, index.js and worker.js, referenced index.js in HTML, and then index.js loaded worker.js. What if we don't want to create a separate Worker file?
index.js
const workerBlob = new Blob([`function parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
onmessage = function (e) {
postMessage(parse(e.data));
}`], { type: 'application/javascript' });
const workerURL = URL.createObjectURL(workerBlob);
const wk = new Worker(workerURL);
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
URL.createObjectURL(blob) creates a DOMString that contains a URL representing the blob.
Open the Network tab in the console, and you will see that the browser has loaded a strange link to a js file like blob:http://example.com/16215a1e-21d4-450c-b441-070e1981b69d
, and the content of this js file is exactly the string content we passed to workerBlob.
This URL is unique and its lifecycle is bound to the document in the window that created it. As long as the page exists, the URL will remain valid.
Using webpack worker-loader
In the previous step, we put the js code in a string, which cannot be split into modules and is not conducive to later maintenance. If the project is using webpack, installing worker-loader can solve this problem.
index.js
import WK from 'worker-loader?inline=true&fallback=false!./worker.js';
const wk = new WK();
wk.postMessage(1000);
wk.addEventListener('message', (e) => {
console.log(e.data);
});
worker.js
import Parse from './parse.js';
self.addEventListener('message', (e) => {
self.postMessage(Parse(e.data));
});
parse.js
function Parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
export default Parse;
Just use worker-loader to reference the worker.js module, and the rest will be automatically handled by worker-loader. The final compiled result is similar to our code in the previous step.
Compared with not using Web Workers:
index.js
import Parse from './parse.js';
console.log(Parse(1000));
parse.js (unchanged)
function Parse (time) {
const start = new Date();
while(new Date() - start < time) {}
return 'DIYgod';
}
export default Parse;
In this way, the original parsing module does not need to be modified, it is non-intrusive, just add a worker.js intermediate module and change the calling method, which is also very convenient for maintenance.
Performance
If I put a calculation into 4 Workers, will the calculation be 4 times faster?
No, it will not only be 4 times faster, but it will also become slower.
Web Workers are not for shortening calculation time, but for avoiding UI thread freezing. Actions such as creating threads, thread scheduling, and data transmission will make the calculation slightly slower than single-threaded.
I recorded the time it took to parse 100 barrage in the video at the beginning with different numbers of Workers, and took the average of 7 records:
Number of Workers | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 10 |
---|---|---|---|---|---|---|---|---|
Average time (ms) | 6085 | 8216 | 6310 | 6388 | 6483 | 6317 | 6475 | 7233 |
The parsing speed without using Workers is the fastest, the speed of 1 Worker is significantly slower than the others, and the speeds of 2, 3, 4, 5, and 6 Workers are not significantly different, but as the number of Workers increases, the speed gradually slows down.
I also tested videos with fewer barrage, and the results were similar for 1, 2, 3, 4, and 5 Workers.
Finally, I decided to use 2 Workers for parsing.
The optimization result is amazing. There is no need to wait for the parsing to complete before performing other operations. You can also play the video while parsing. The only difference is that the unprocessed barrage will not be displayed until the parsing is complete.