Webworker canvas的性能很糟糕

Webworker canvas performance terrible

本文关键字:性能 canvas Webworker      更新时间:2023-09-26

我正在尝试使用webworkers来渲染动画mandelbrot缩放的部分帧,因为有很多计算涉及,而且因为这可以很容易地分成块,这应该是并行处理的理想情况。

但是无论我尝试什么,我都没有得到任何性能作为工人使用额外cpu的回报。与非工作版本相比,在Chrome中我的基准测试速度稍慢,在Firefox中则慢得多。

我的猜测是,将图像数据传输到网络工作者是非常昂贵的,我尝试只是接收原始数据,并使用它来渲染帧,但结果是一样的。我不认为这是向工人发送和接收图像数据的理想方式(事实上,我只需要接收它,但我无法在工人内部创建一个缓冲区,可以直接用于画布)。因此,就目前而言,发送任何大量数据都会造成真正的瓶颈。

亲爱的stackoverflow,请帮我回答这两个问题:我在这里做错了什么,还有什么可以改进的?

可以在这里找到worker的演示,并在jsfiddle上找到非worker版本的参考。

代码如下:

"use strict";
/*global $*/
$(function() {
    var mandelbrot = new Mandelbrot();
});
var Mandelbrot = function() {
    // set some values
    this.width = 500;
    this.height = 500;
    this.x_center = -1.407566731001088;
    this.y_center = 2.741525895538953e-10;
    this.iterations = 250;
    this.escape = 4,
    this.zoom = 10;
    this.count = 0;
    this.worker_size = 10;
    this.received = 0;
    this.refresh = true;
    //let's go - create canvas, image data and workers
    this.init();
    //start animation loop
    this.animate();
};
Mandelbrot.prototype = {
    init: function() {
        var self = this;
        //create main canvas and append it to div
        var container = $("#content");
        this.canvas = document.createElement("canvas");
        this.canvas.width = this.width;
        this.canvas.height = this.height;
        container.append(this.canvas);
        //create imagedata
        this.context = this.canvas.getContext("2d");
        this.image = this.context.getImageData(0, 0, this.width, this.height);
        this.data = new Int32Array(this.image.data.buffer);
        //create imagedata for webworkers
        this.worker_data = this.context.getImageData(0, 0, this.width, this.height / this.worker_size);
        //create webworkers drop them in array
        this.pool = [];
        for (var i = 0; i < this.worker_size; i++) {
            this.pool[i] = new Worker("js/worker.js");
            this.pool[i].idle = true;
            this.pool[i].id = i;
            //on webworker finished 
            this.pool[i].onmessage = function(e) {
                self.context.putImageData(e.data, 0, self.height / self.worker_size * e.target.id);
                self.received++;
            };
        }
    },
    iterate: function() {
        for (var i = 0; i < this.pool.length; i++) {
            this.pool[i].postMessage({
                image: this.worker_data,
                id: this.pool[i].id,
                worker_size: this.worker_size,
                width: this.width,
                height: this.height,
                x_center: this.x_center,
                y_center: this.y_center,
                iterations: this.iterations,
                escape: this.escape,
                zoom: this.zoom
            });
        }
    },
    animate: function() {
        requestAnimationFrame(this.animate.bind(this));
        //poor man's benchmark over 250 frames
        if (this.count === 0) {
            console.time("timer");
        } 
        if (this.count === 250) {
            console.timeEnd("timer");
        }
        //refresh at init, then refresh when all webworkers are done and reset
        if (this.received === this.worker_size | this.refresh) {
            this.received = 0;
            this.refresh = false;
            this.count++;
            this.zoom *= 0.95;
            this.iterate();
        }
    }
};

和worker.js:

self.onmessage = function(e) {
    "use strict";
    var x_step = e.data.zoom / e.data.width;
    var y_step = e.data.zoom / e.data.height;
    var y_start = e.data.height / e.data.worker_size * e.data.id;
    var y_end = e.data.height / e.data.worker_size;
    var data = new Int32Array(e.data.image.data.buffer);
    for (var y = 0; y < y_end; y++) {
        var iy = e.data.y_center - e.data.zoom / 2 + (y + y_start) * y_step;
        for (var x = 0; x < e.data.width; x++) {
            var rx = e.data.x_center - e.data.zoom / 2 + x * x_step;
            var zx = rx;
            var zy = iy;
            var zx2 = 0;
            var zy2 = 0;
            for (var i = 0; zx2 + zy2 < e.data.escape && i < e.data.iterations; ++i) {
                zx2 = zx * zx;
                zy2 = zy * zy;
                zy = (zx + zx) * zy + iy;
                zx = zx2 - zy2 + rx;
            }
            data[y * e.data.width + x] = (255 << 24) | (i << 16) | (i << 8) | i;
        }
    }
    self.postMessage(e.data.image);
};

问题是你正在迭代父图片中的每个像素。如果将迭代限制在两张图像中较小的那张,速度就会快得多。此外,如果您平铺绘图,每个平铺可以在一个单独的web worker中处理,从而增加图像的每个部分的托盘化。我写的是:http://robertleeplummerjr.github.io/CanvasWorker/这正是你想要的

我实际上在这个实验中做了同样的事情,这是一个位移过滤器:

http://www.soundstep.com/blog/experiments/displacement-js/heart/http://www.soundstep.com/blog/2012/04/25/javascript-displacement-mapping/

我在滤镜中创建了一个worker,并在将它们发布回主应用程序之前一起计算像素。基本上是迭代worker中的所有像素。

在worker之前,我有一个循环4 getImageData,这不能在worker中完成。

所以,总的来说,没有worker我得到70%的CPU,有worker我得到90%的CPU。

我认为不能在worker中完成的操作,例如"getImageData"answers"putImageData",加上拥有worker本身的事实,比没有worker需要更多的CPU。

如果我们能够发送其他类型的数据,那么我们可以在worker中执行getImageData和putImageData,这可能会更好。

不确定是否有其他方式发送和接收字节来处理和重建画布内容。

http://typedarray.org/concurrency-in-javascript/