Производительность холста Webworker ужасна

Я пытаюсь использовать веб-воркеры для рендеринга частей кадров для анимированного мандельброта, так как требуется много вычислений, и поскольку это можно легко разделить на блоки, это должно быть идеальной ситуацией для параллельной обработки.

Но что бы я ни пытался, я не получаю никакой производительности в обмен на дополнительный процессор, который используют рабочие. По сравнению с не рабочей версией, в Chrome мой бенчмарк несколько медленнее, в Firefox - намного медленнее.

Я предполагаю, что передача данных изображения в веб-воркеры невероятно дорога, я пытался просто получать необработанные данные и использовать их для рендеринга кадров, но результат почти такой же. Я не думаю, что это идеальный способ отправки и получения данных изображения рабочим процессам (на самом деле мне нужно только получить их, но я не смог создать буфер внутри рабочих процессов, который можно было бы использовать для холста напрямую) . Таким образом, отправка любого серьезного объема данных создает реальное узкое место.

Уважаемый stackoverflow, помогите, пожалуйста, ответить на два вопроса: что я здесь делаю неправильно и что можно улучшить?

Демонстрационную версию можно найти здесь для рабочей версии, а для справки - нерабочую версию на jsfiddle.

Код выглядит следующим образом:

"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();

        }
    }
};

и рабочий.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);

};

person Nuoun    schedule 13.04.2013    source источник


Ответы (2)


Проблема в том, что вы перебираете каждый пиксель родительского изображения. Если вы ограничите итерацию меньшим из двух изображений, все будет намного быстрее. Кроме того, если вы разбиваете рисунок на мозаику, каждая плитка может обрабатываться в отдельном веб-воркере, что увеличивает паллетизацию каждой части изображения. Я написал это: http://robertleeplummerjr.github.io/CanvasWorker/, который делает именно то, что вы хотите .

person Robert Plummer    schedule 04.05.2015

На самом деле я пробовал то же самое в этом эксперименте, это фильтр смещения:

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

Я создал работника в фильтре и вычисляю пиксель вместе, прежде чем отправлять их обратно в основное приложение. В основном итерация по всем пикселям внутри рабочего.

Перед воркером у меня в цикле 4 getImageData, в воркере этого сделать нельзя. Хром занимает около 15% процессора, несмотря ни на что.

Итак, в целом я получаю 70% ЦП без воркера и 90% ЦП с воркером.

Я предполагаю, что действия, которые нельзя выполнить в рабочем потоке, такие как «getImageData» И «putImageData», а также наличие самого рабочего процесса, требуют больше процессорного времени, чем отсутствие рабочего процесса.

Вероятно, было бы лучше, если бы мы могли отправлять другие типы данных, чтобы мы могли выполнять getImageData и putImageData внутри воркера.

Не уверен, что есть другой способ отправки и получения байтов для обработки и восстановления содержимого холста.

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

person Soundstep    schedule 08.07.2013