Рекурсия дорогая для Firefox

РЕДАКТИРОВАТЬ: происходит только в Firefox! (Я использую 22.0) См. Сравнение браузера внизу.

Я пытаюсь создать эффект "Затухание до черного" на холсте, копируя данные пикселей и постепенно меняя значения альфа-канала с 255 на 0 (фон черный).

function fadeToBlack () {
    if(typeof this.recursion === 'undefined' || this.recursion === 0) {
        this.recursion = 1;
        this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
        this.imageDataArray = this.imageData.data;
        this.pixelCount = this.imageDataArray.length/4;
        this.fadeToBlack();
    }
    else if (this.recursion <= 15){
        console.time('Change alpha ' + this.recursion);
        for (var i = 0; i < this.pixelCount; i++){
            this.imageDataArray[i * 4 + 3] = 255 - 255 / 15 * this.recursion;
        }
        console.timeEnd('Change alpha ' + this.recursion);
        this.ctx.putImageData(this.imageData, 0, 0);
        this.recursion++;
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 50);
    }
    else {
        this.recursion = 0;
    }
};

Я думал, что это будет очень дорого (1280 * 1024 = 1310720 итераций!), Но, как вы можете видеть из журнала консоли ниже, это было на удивление быстро, за исключением первой итерации.

Change alpha 1: 543ms
Change alpha 2: 16ms
Change alpha 3: 6ms
Change alpha 4: 16ms
...

Любопытно, если я просто задержу вторую итерацию fadeToBlack (первая итерация пиксельной манипуляции)...

function fadeToBlack () {
    if(typeof this.recursion === 'undefined' || this.recursion === 0) {
        this.recursion = 1;
        this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
        this.imageDataArray = this.imageData.data;
        this.pixelCount = this.imageDataArray.length/4;
        //This is the only difference!
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 0);
    }
    else if (this.recursion <= 15){
        console.time('Change alpha ' + this.recursion);
        for (var i = 0; i < this.pixelCount; i++){
            this.imageDataArray[i * 4 + 3] = 255 - 255 / 15 * this.recursion;
        }
        console.timeEnd('Change alpha ' + this.recursion);
        this.ctx.putImageData(this.imageData, 0, 0);
        this.recursion++;
        setTimeout(function(){
            this.fadeToBlack();
        }.bind(this), 50);
    }
    else {
        this.recursion = 0;
    }
};

Происходит что-то волшебное.

Change alpha 1: 16ms
Change alpha 2: 16ms
Change alpha 3: 6ms
Change alpha 4: 6ms
...

Так что, черт возьми, здесь происходит?

РЕДАКТИРОВАТЬ: Я проверил это в нескольких браузерах, и вот результаты в миллисекундах для всех 15 итераций.

Browser  |Recursive  |Asynchronous
=========+===========+============
Firefox  |1652†      |1136
Chrome   |976        |978
Opera    |12929      |13855
IE       |855        |854

† Первая итерация была очень дорогой (500 мс).

1 ответ

Решение

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

TL; DR setTimeout: асинхронный вызов (независимый), рекурсия: синхронный (зависимый).

графическая демонстрация:

Другие вопросы по тегам