Offscreen convertToBlob очень медленный - PullRequest
0 голосов
/ 17 апреля 2020

Я экспериментировал с холстом HTML некоторое время, и этот код ниже занимает 1 секунду до конечного значения sh, что очень медленно:

    const offscreen = new OffscreenCanvas(640, 360); 
    const ctx = offscreen.getContext('2d'); // Chrome only
    const bitmap = e.data.data;
    ctx.drawImage(bitmap, 0, 0);
    let start = performance.now();
    offscreen.convertToBlob({ type: 'image/jpeg', quality : 0.5}).then(function(blob) {
        let now = performance.now();
        console.log('Convert to blob took: ' + (now-start) + ' milliseconds'); // e.g log: 1085.1800000000512 milliseconds
    });

Код выполняется в веб-работник.

Как лучше всего повысить скорость в этом контексте? Мой код неверен?

1 Ответ

0 голосов
/ 18 апреля 2020

Это ошибка в Chromium, которая была исправлена ​​ этим коммитом (доступно в Chrome v83).

Сообщение от этого коммита:

Не планируйте незанятые задачи для рабочих

Бесполезные задачи в рабочих потоках занимают намного больше времени, потому что requestAnimationFrame отсутствует. Для этого теста: https://chromium-review.googlesource.com/c/chromium/src/+/2124971

Без этого рабочего изменения convertToBlob занимает ~ 400 мс
С этим рабочим изменением convertToBlob занимает ~ 10 мс

И если интересно , код, который я использовал, чтобы найти правильный диапазон пополам:

if( !window.OffscreenCanvasRenderingContext2D ) {
  throw new Error( "Your browser doesn't support the 2D context of the Offscreen canvas" );
}
// This code demonstrate how calling 100 times convertToBlob
// doesn't end up in 100 x single-time execution
// i.e the actual processing time is much less than the time we have to wait...
const script = `
onmessage= async (e) => {
  const offscreen = new OffscreenCanvas( 800, 600 ); 
  const ctx = offscreen.getContext( '2d' );
  const bitmap = await fetch( 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png' )
    .then( (resp) => resp.blob() )
    .then( createImageBitmap );

{
  const beginning = performance.now();
  const proms = [];
  
  for( let i = 0; i < 10; i++ ) {
    ctx.drawImage( bitmap, i, i );
    ctx.clearRect( 0, 0, 100, 100 );
    ctx.fillText( i , 50, 50 );
    const start = performance.now();
    proms.push( offscreen.convertToBlob()
      .then( blob => {
        const now = performance.now();
        postMessage( '('+ i + ') in sync loop' + ' - took:' + (now - start)
        );
      } )
     );
  }
  const blobs = await Promise.all( proms );
  const duration = performance.now() - beginning;
  postMessage( 'all done in ' + duration + ' -> ' + Math.round( duration / 10 ) + 'ms in average' );
}
{
  const beginning = performance.now();
  const proms = [];
  
  for( let i = 0; i < 10; i++) {
    ctx.drawImage( bitmap, i, i );
    ctx.clearRect( 0, 0, 100, 100 );
    ctx.fillText( i , 50, 50 );
    const start = performance.now();
    proms.push( offscreen.convertToBlob()
      .then( blob => {
        const now = performance.now();
        postMessage( '('+ i + ') in rAF loop took - ' + (now - start) );
      } )
    );
    await new Promise(res => requestAnimationFrame(res));
  }
  
  const blobs = await Promise.all( proms );
  const duration = performance.now() - beginning;
  postMessage( 'all done in ' + duration + ' -> ' + Math.round( duration / 10 ) + 'ms in average' );

}
{
  let duration = 0;
  const proms = [];

  for( let i = 0; i < 10; i++) {
    const beginning = performance.now();
    ctx.drawImage( bitmap, i, i );
    ctx.clearRect( 0, 0, 100, 100 );
    ctx.fillText( i , 50, 50 );
    const start = performance.now();
    proms.push( offscreen.convertToBlob()
      .then( blob => {
        const now = performance.now();
        duration += now - start;
        postMessage( '('+ i + ') in timeout loop took - ' + (now - start) );
      } )
    );
    await wait( 500 );
  }
  const blobs = await Promise.all( proms );
  postMessage( 'all done in ' + duration + ' -> ' + Math.round( duration / 10 ) + 'ms in average' );

}

};
function wait(ms) { return new Promise( (res) => setTimeout( res, ms ) ); }
`;
const blob = new Blob( [ script ] );
const worker = new Worker( URL.createObjectURL( blob ) );
worker.onmessage = (e) => console.log( e.data );
worker.postMessage("");

Из этого теста в Chrome <83 я получаю в среднем приблизительно 200 мс для двух первых тестов (syn c и requestAnimationFrame) и 900 мс (oO) с тестом setTimeout. <br>Эти тесты позволили мне понять, что это на самом деле не только «медленно», но что перед каждым шагом добавляется огромная задержка, где ничего не происходит.

В последней версии Canary все эти тесты возвращают в среднем 30 мс.

Так что, к сожалению, вы мало что можете сделать,

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


Теперь, так как OP хочет преобразовать ImageBitmap в Blob в Worker, это не поможет , но уродливый обходной путь для других читателей состоит в том, чтобы сделать это преобразование в основном потоке, либо сначала преобразовав холст в ImageBitmap (convertToImageBitmap() достаточно быстро), а затем отправив обратно этот ImageBitmap на основной поток, либо с помощью отдельного HTMLCanvasElement и вызова его toBlob метода.

if( !window.OffscreenCanvasRenderingContext2D ) {
  throw new Error( "Your browser doesn't support the 2D context of the Offscreen canvas" );
}

const script = `
onmessage= async (e) => {
  const offscreen = e.data; 
  const ctx = offscreen.getContext( '2d' );
  const bitmap = await fetch( 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png' )
    .then( (resp) => resp.blob() )
    .then( createImageBitmap );

  // probably do something else here...
  ctx.drawImage( bitmap, 0, 0 );
  
  const start = Date.now(); // start measuring from now
  postMessage( start );

};
`;
const blob = new Blob( [ script ] );
const worker = new Worker( URL.createObjectURL( blob ) );
const canvas = document.getElementById( 'canvas' );
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage( offscreen, [ offscreen ] );

worker.onmessage = (e) => {
  const start = e.data;
  canvas.toBlob( (blob) => {
    console.log( 'Transfer to Blob took', Date.now() - start + 'ms' );
  } );
};
<canvas id="canvas" width="800" height="600"></canvas>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...