Оптимизация рендеринга холста

#javascript #performance #canvas #d3.js #optimization

#javascript #Производительность #холст #d3.js #оптимизация

Вопрос:

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

Для 1600 элементов (600 пикселей x 600 пикселей и размер ячейки 15 пикселей) анимация плавная. Для 3600 элементов это не так.

 mazeSelection.selectAll('cell')
    .data(root.descendants())
    .enter()
    .append('cell')
    .attr('radius', 0)
    .attr('cx', d => d.data.ix * cellSize   0.5 * cellSize,)
    .attr('cy', d => d.data.iy * cellSize   0.5 * cellSize)
    .transition().duration(2000)
    .attr('radius', cellSize/2)
    .transition().duration(3000)
    .attr('radius', cellSize/4)
    .tween('position', function(d) {
        const i = d3.interpolate([this.getAttribute('cx'), this.getAttribute('cy')], [width   d.x, cellSize * 0.5   d.y]);
        return (t) => {
            [d.cx, d.cy] = i(t);
            this.setAttribute('cx', d.cx);
            this.setAttribute('cy', d.cy);
        };
    });

context.fillStyle = "white";

let timer = d3.timer(function redraw() {
    // clear maze
    context.beginPath();
    context.fillRect(0, 0, width * 2, height);

    //drawMaze(grid, context, width, height, cellSize);
    context.beginPath();

    // here we must use a function to have access to "this"
    mazeSelection.selectAll('cell')
        .each(function (d) {
            const radius = this.getAttribute('radius'),
                x = this.getAttribute('cx'),
                y = this.getAttribute('cy');

            context.moveTo(x, y);
            context.arc(x, y, radius * 0.5, 0, 2 * Math.PI);

            (d.children || []).forEach(child => {
                context.moveTo(x, y);
                context.lineTo(child.cx, child.cy);
            });
        });

    context.stroke();
    context.fill();
});
 

Комментарии:

1. Вы создаете здесь элементы DOM, лучше всего избегать этого и создать некоторый список структур javascript, а затем выполнить итерацию по этому.

2. Спасибо за ответ. У меня есть 2 вопроса: 1. Несмотря на то, что я использую append, элемент не отображается в дереве DOM, когда я использую chrome dev tools. Неужели они настолько плохи? 2. Может быть, вы знаете, как применить переход без создания элементов DOM?

3. 1. вы можете создавать больше, чем в дереве DOM, инструменты Chrome dev могут показать вам только тот, который вы прикрепили к документу. 1b) да 2. перекодируйте в соответствии с моим первоначальным комментарием.

4. 1. Я понимаю. 2. Не могли бы вы привести какой-нибудь простой пример? D3 имеет удобный метод переноса при выборе, и я не знаю, как его использовать, ничего не добавляя.

Ответ №1:

Вы должны пойти по старой школе, чтобы получить максимальную производительность.

Я проигнорирую первую половину, поскольку я не уверен, что вы с ней делаете. И я предполагаю, что это такое в .each итерации

Что вы делаете неправильно с точки зрения производительности, как указано в вашем коде ниже

 // why use a let in global scope????
let timer = d3.timer(function redraw() {
    context.beginPath();
    context.fillRect(0, 0, width * 2, height);
    context.beginPath();

    // You wrote "here we must use a function to have access to "this""
    // What 'this'? I assume it is the iterated cell `this`. Just use the 
    // cell referance, you dont have to use `this`
    mazeSelection.selectAll('cell')
        .each(function (d) {     // dont use callbacks to iterate. For loops are much quicker
            // on chrome const and let are half the speed of var 
            const radius = this.getAttribute('radius'),  // why use getAttribute, a pointless and very CPU expensive way to get an object's property
                x = this.getAttribute('cx'),
                y = this.getAttribute('cy');
            // why even assign the above properties to a const when you can use them directly

            context.moveTo(x, y);
            context.arc(x, y, radius * 0.5, 0, 2 * Math.PI);

            // use a for loop and don't test for the existence of children 
            // by creating a new Array if they don't exist and then calling
            // the array method forEach, a complete waste of CPU time
            (d.children || []).forEach(child => {
                context.moveTo(x, y);
                context.lineTo(child.cx, child.cy);
            });
        });

    context.stroke();
    context.fill();
});
 

Если вы попробуете выполнить следующие итерации объектов, вы получите дополнительную производительность. Тем не менее, 3000 все еще могут быть недоступны.

 var timer = d3.timer(function redraw() {
    context.beginPath();
    context.fillRect(0, 0, width * 2, height);
    context.beginPath();

    var cells = mazeSelection.selectAll('cell')
    for(var i = 0; i < cells.length; i   ){
        var d = cell[i];
        var x = d.x;  // create a local referance as x amp; y may be used many times
        var y = d.y;
        context.moveTo(x, y);
        context.arc(x, y, d.radius * 0.5, 0, 2 * Math.PI);
        if(d.children){
            var c = d.children;
            for(var j = 0; j < c.length; j  ){    
                context.moveTo(x, y);
                context.lineTo(c[j].cx, c[j].cy);
            }
        }

        context.stroke();
        context.fill();
    };
 

Если отображаемые вами дуги имеют примерно одинаковый размер, вы можете получить некоторое увеличение производительности, отобразив дуги на внеэкранном холсте (листе спрайтов), а затем нарисовав дуги, drawImage(spriteSheet,... хотя в зависимости от оборудования / браузера это может быть незначительным или значительным увеличением производительности

Комментарии:

1. Спасибо за ответ! 1. Что именно не так с let в глобальной области видимости? 2. Я не думаю, что атрибут является свойством ячейки, поскольку, как вы показали, он не определен. 3. Дуги имеют одинаковый размер, но они перемещаются. Могу ли я использовать drawImage в этой ситуации?

2. 2. Хорошо, я должен просто использовать property() вместо attr() в d3.

3. @JulianRubin let вдвое быстрее var , чем в Chrome, и поскольку между let и var в GS нет разницы, зачем использовать let? Выведите на консоль то, что вы повторяете console.log(this) , и узнайте, что this есть.. Да, вы можете использовать drawImage для перемещения спрайтов. Вы должны использовать прямую ссылку, а не вызов функции (свойство или attr), они просто проверяют.

4. @JulianRubin, это крошечная микрооптимизация (хотя все равно полезно знать). На вашу реальную проблему указал комментарий Роберта. Не используйте элементы DOM для canvas с d3. Используйте прямые объекты js. В вашем коде не должно быть no select , no append no attr .

5. @Kaiido знаете ли вы какой-нибудь способ использовать переходы d3 с объектами js? Я не знаю, как упорядочить свойства объекта для изменения с переходами. Я делаю select и attr, потому что именно так я обычно меняю значения данных с течением времени.