New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 731586 link

Starred by 5 users

Issue metadata

Status: Assigned
Owner:
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Linux
Pri: 2
Type: Bug

Blocking:
issue 877083


Participants' hotlists:
dmurph-future-tasks


Sign in to add a comment

blob uris much slower than data uris

Reported by oss...@cendio.com, Jun 9 2017

Issue description

UserAgent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0

Steps to reproduce the problem:
1. Replace base64 encoded data uris with Blob()/createObjectURL()

What is the expected behavior?
Execution should be at least as fast, preferably faster.

What went wrong?
Code seems to execute at half the speed, or worse.

Did this work before? N/A 

Chrome version: 58.0.3029.110  Channel: stable
OS Version: Fedora 25
Flash Version: None

This was discovered whilst debugging a performance issue with noVNC in Firefox:

https://bugzilla.mozilla.org/show_bug.cgi?id=1369317

Blobs should be more efficient as it avoids all the base 64 encoding and decoding, as well as all the string management. So it would be nice if Chromium isn't preventing developers from using that technology for performance reasons.
 
Components: -Blink Blink>Loader
Components: Blink>Storage
Labels: Needs-Feedback
Blobs are managed in the browser process while data uris are processed in renderer, so it's possible that blobs have larger overhead due to IPC, etc.

How did you create blobs, how big they are?
It would be great if you could provide a test page.

Comment 3 by oss...@cendio.com, Jun 12 2017

The blobs are created from a Uint8Array (from data received over a WebSocket). They are generally small (kBs), but very numerous.

I'm afraid I don't have a simple test page right now. If you are willing to download noVNC then I could provide a patch that shows the issue.
Project Member

Comment 4 by sheriffbot@chromium.org, Jun 12 2017

Cc: ksakamoto@chromium.org
Labels: -Needs-Feedback
Thank you for providing more feedback. Adding requester "ksakamoto@chromium.org" to the cc list and removing "Needs-Feedback" label.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Cc: bsittler@chromium.org dmu...@chromium.org
Status: Untriaged (was: Unconfirmed)
Naïvely I would expect this behavior - blobs involve storage and inter-process communication in Chrome, whereas data URIs should not.

My own tests seem to confirm the behavior you report:

(this HTML+JS snippet is also hosted at 
https://fiddle.jshell.net/bcfbjguk/35/ so you can easily test it yourself)

Output on the ~two-year-old Chromebook I'm using:

blobs: 1265.664 kbps
dataUri: 3444.736 kbps
blobs: 1026.048 kbps
dataUri: 2224.128 kbps
blobs: 614.4 kbps
dataUri: 3465.216 kbps
blobs: 669.696 kbps
dataUri: 3272.704 kbps
blobs: 722.944 kbps
dataUri: 3409.92 kbps
done


HTML:

<h1>Pseudorandom Base64-encoded data: URI rate</h1>
<textarea id=output cols=80 rows=20 style="width:100%"></textarea>

JS:

(async function(){

let chunkSize = 2048;
let msPerRun = 1e3;
let repetitions = 5;

for (let runPair = 0; runPair < repetitions; ++runPair) {

await (async function(startTime, duration){
   let count = 0;
   let buffer = new Uint8Array(chunkSize);
   while (Date.now() < (startTime + duration)) {
      crypto.getRandomValues(buffer);
      let url = URL.createObjectURL(new Blob([buffer.buffer], {type: 'application/octet-binary'}));
      let response = await fetch(url);
      let out = await response.arrayBuffer();
      let oBuffer = new Uint8Array(out);
      if (oBuffer.length !== buffer.length) throw '1wrong response length ' + [oBuffer.length,buffer.length];
      for (let i = 0; i < oBuffer.length; ++i) {
         if (oBuffer[i] !== buffer[i]) throw '1wrong byte in response';
         ++count;
      }
   }
   document.getElementById('output').value +=
      'blobs: ' + (count / duration) + ' kbps\n';
})(Date.now(), msPerRun);

await (async function(startTime, duration){
   let count = 0;
   let buffer = new Uint8Array(chunkSize);
   while (Date.now() < (startTime + duration)) {
      crypto.getRandomValues(buffer);
      let url = 'data:application/octet-binary;base64,' + btoa(String.fromCharCode.apply(String, buffer));
      let response = await fetch(url);
      let out = await response.arrayBuffer();
      let oBuffer = new Uint8Array(out);
      if (oBuffer.length !== buffer.length) throw '2wrong response length ' + [oBuffer.length,buffer.length];
      for (let i = 0; i < oBuffer.length; ++i) {
         if (oBuffer[i] !== buffer[i]) throw '2wrong byte in response';
         ++count;
      }
   }
   document.getElementById('output').value +=
      'dataUri: ' + (count / duration) + ' kbps\n';
})(Date.now(), msPerRun);

}

})().then(() => document.getElementById('output').value += 'done');


Updated to call revokeObjectURL https://fiddle.jshell.net/bcfbjguk/37/

blobs: 1265.664 kbps
dataUri: 3444.736 kbps
blobs: 1026.048 kbps
dataUri: 2224.128 kbps
blobs: 614.4 kbps
dataUri: 3465.216 kbps
blobs: 669.696 kbps
dataUri: 3272.704 kbps
blobs: 722.944 kbps
dataUri: 3409.92 kbps
done
Blobs without the URI+Fetch step perform similarly BTW:

https://fiddle.jshell.net/bcfbjguk/39/

blobResponse: 1224.704 kbps
blobObjectUri: 1417.216 kbps
dataUri: 3233.792 kbps
blobResponse: 1400.832 kbps
blobObjectUri: 1394.688 kbps
dataUri: 3244.032 kbps
blobResponse: 1548.288 kbps
blobObjectUri: 1458.176 kbps
dataUri: 3457.024 kbps
blobResponse: 1554.432 kbps
blobObjectUri: 1472.512 kbps
dataUri: 3186.688 kbps
blobResponse: 1302.528 kbps
blobObjectUri: 1333.248 kbps
dataUri: 2990.08 kbps
done

Comment 9 by dmu...@chromium.org, Jun 12 2017

Owner: dmu...@chromium.org
Interesting. I think this is something we could profile and improve performance on. Let's bring this up in our meeting to see how we should prioritize.
https://fiddle.jshell.net/bcfbjguk/42/ adds escape()-based data: URIs as a comparison point also tries power-of-2 chunk sizes from 65536 (maximum allowed by getRandomValues) all the way down to 1
https://fiddle.jshell.net/bcfbjguk/45/ is updated to use correct units in the output
Status: Assigned (was: Untriaged)
Assigning to dmurph@chromium.org for prioritization; feel free to bounce back to me and/or discuss further if my repro code should turn into a performance benchmark of some sort
Labels: Needs-Feedback
This is a performance issue I'd like to keep track of. First priority here is getting a perf test about this use case. Once we get that needle in place, we can work towards getting it faster.

@ ossman@cendio.com - can you provide a little more details about the usage pattern for blobs here? I want to create a perf test that models what you're doing as closely as possible (something full-stack, not microbenchmarky) so we can create a metric here.

Comment 14 by oss...@cendio.com, Jun 15 2017

A rough outline of the flow:

 - Screen updates are sliced up in to many small JPEG images and sent from the server. Somewhat "bursty" as there will be a bunch of images, then a pause until the next screen update.
 - Data is read from the client WebSocket in to a Uint8Array
 - Once we have enough data for an entire JPEG image we give a slice to the Blob constructor
 - An Image object is created and .src is set via createObjectURL()
 - If .complete isn't set then we set up an event listener for "load" and start queueing Image objects as data comes in
 - When an Image is loaded we do drawImage() to an offscreen canvas
 - Once all images are processed we use drawImage() from the offscreen canvas to the visible canvas

Repeat for each screen update
Blocking: 877083

Sign in to add a comment