Firefox OS/MappedArrayBuffer

From MozillaWiki
Jump to: navigation, search

Memory-mapped array buffer for XHR response

The feature allows to read data in packaged app by XHR with array buffer type as memory-mapped. It helps to save RAM memory usage, especially for big data files in a packaged app.
For example, the English word suggestion/auto correction or the JSZhuyin IME database in the keyboard APP are read as array buffer, which are good candidates to use this feature on a small RAM device.
Main bug for this feature at https://bugzilla.mozilla.org/show_bug.cgi?id=945152

Support status

  • Supported platform
    • Linux
    • Firefox OS(except Windows simulator)
Windows support follow up at https://bugzilla.mozilla.org/show_bug.cgi?id=988813
  • Parent process and child process
  • Main thread and worker thread
  • How to enable?
    • Use preference "dom.mapped_arraybuffer.enabled" to enable/disable this feature
    • Enabled for Firefox OS by default

Requirements

  • Target file data offset in a packaged app must be
    • Uncompressed
    • Aligned to
      • 8 bytes minimum, and page size[1] recommended
      • After [2] landed, page size aligment is mandatory
  • If requirements not meet
    • Fall back to be normal array buffer
Note: The memory-mapped array buffer is mapped as copy-on-write. If starting/ending position of the file are not page aligned, the contents within the page fragmentation will be initilized to zero for security(this would introduce heap memory overhead for maximum two page size). Although page alignment of target file in the zip may increase the zip package size a little, it saves you one page heap memory overhead.

Arraybuffer mapping.png

[1] Page size on 32-bits Linux is 4K bytes.
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=855669

Use it or not?

To determine whether to use it, you may want to consider these factors:

  • RAM size
  • Flash size
  • File size
    • There might be no memory saving when the file size belows 8K bytes.
  • File compression ratio
    • If both RAM and Flash size are limited, you may or may not want to compress the file, depending on the compression ratio.

How to use it?

You need to make the target files uncompressed and aligned when building a packaged app. To do this, put a configuration file "metadata.json" in the gaia app directory to specify memory-mapped files.
Below is an example of "metadata.json" for keyboard app:
 {
   "external": false,
   "zip": {
     "mmap_files": [
       "js/imes/latin/dictionaries/en_us.dict",
       "js/imes/jszhuyin/data/database.data"
     ]  
   }
 }
After that, the files specified in "mmap_files" will be uncompressed and aligned to 4096 bytes in the zip package.
 $ unzip -v gaia/profile/webapps/keyboard.gaiamobile.org/application.zip | grep Stored
 1451390  Stored  1451390   0% 1970-01-01 08:00 3d22f787  js/imes/latin/dictionaries/en_us.dict
 4541832  Stored  4541832   0% 1970-01-01 08:00 426a7f8e  js/imes/jszhuyin/data/database.data

Note: The current approach first makes specified files uncompressed, then re-organizes the zip package to make uncompressed files to aligned by padding in extended field of the header. This could introduce some overhead on zip package size, because not all uncompressed files are expected to be read as memory-mapped, which need not and should not be made aligned.

There is a follow up to align for individual files in the zip package https://bugzilla.mozilla.org/show_bug.cgi?id=961622.

How it is done in child process?

Xhr jar remoteopenfile.png

See https://bugzilla.mozilla.org/show_bug.cgi?id=988816 for detail.

Q & A

  • How to know if the array buffer is memory-mapped in an XHR response?
The Content-Type header is "application/mem-mapped" in such response.
 var xhr = new XMLHttpRequest();
 xhr.onreadystatechange = function() {
   if (xhr.readyState == xhr.DONE && xhr.status == 200) {
     var ct = xhr.getResponseHeader("Content-Type");
     if (ct.indexOf("mem-mapped") != -1) {
       dump("Data is memory-mapped!\n");
     }
   }
 }
 xhr.open('GET', 'database.data');
 xhr.responseType = 'arraybuffer';
 xhr.send()
  • How to check memory usage?
You can check the memory usage by the following command:
 $ tools/get_about_memory.py --no-gc-cc-log
Below is an example for English and JSZhuyin IME, which means they are not taking any RAM memory.
 │   ├──5.13 MB (27.03%) -- worker(./js/imes/jszhuyin/lib/worker.js, 0xb22dd400)
 │   │  ├──4.57 MB (24.10%) -- zone(0xb104c800)
 │   │  │  ├──4.45 MB (23.46%) -- compartment(web-worker)
 │   │  │  │  ├──4.36 MB (22.99%) -- objects
 │   │  │  │  │  ├──4.33 MB (22.83%) -- non-heap
 │   │  │  │  │  │  ├──4.33 MB (22.83%) ── elements/mapped
 │   │  │  │  │  │  └──0.00 MB (00.00%) ── code/asm.js
 │   │  │  │  │  └──0.03 MB (00.16%) ++ (2 tiny)
 │   │  │  │  └──0.09 MB (00.48%) ++ (2 tiny)
 │   │  │  └──0.12 MB (00.63%) ++ (4 tiny)
 │   │  ├──0.29 MB (01.54%) ++ runtime
 │   │  ├──0.23 MB (01.24%) -- gc-heap
 │   │  │  ├──0.21 MB (01.11%) ── unused-arenas
 │   │  │  └──0.02 MB (00.12%) ++ (2 tiny)
 │   │  └──0.03 MB (00.16%) ++ zone(0xb104bc00)
 │   └──2.02 MB (10.64%) -- worker(js/imes/latin/worker.js, 0xb0d02800)
 │      ├──1.68 MB (08.86%) -- zone(0xb0d0d000)
 │      │  ├──1.56 MB (08.21%) -- compartment(web-worker)
 │      │  │  ├──1.48 MB (07.82%) -- objects
 │      │  │  │  ├──1.38 MB (07.30%) -- non-heap
 │      │  │  │  │  ├──1.38 MB (07.30%) ── elements/mapped
 │      │  │  │  │  └──0.00 MB (00.00%) ── code/asm.js
 │      │  │  │  └──0.10 MB (00.53%) ++ (2 tiny)
 │      │  │  └──0.07 MB (00.39%) ++ (2 tiny)
 │      │  └──0.12 MB (00.65%) ++ (4 tiny)
 │      ├──0.29 MB (01.54%) ++ runtime
 │      └──0.05 MB (00.25%) ++ (2 tiny)
Memory-mapped array buffer is reported at non-heap/elements/mapped
Normal array buffer is reported at malloc-heap/elements/non-asm.js
  • Can we write to a memory-mapped array buffer?
The array buffer is mapped as copy-on-write by private mapping. If you write to a memory-mapped array buffer, it's not visible to other processes mapping the same file, and the updates are not carried through to the underlying file. However, you should avoid that because it introduces heap memory allocation when writing to the address of each page in the virtual address space.
  • Can we pass a memory-mapped array buffer between main/worker thread?
Yes, there are two ways to pass array buffer by postMessage():
1. Structured clone
  • The destination array buffer becomes a normal array buffer
 worker.postMessage(ab);
2. Transferable objects
  • Ownership transfered to destination array buffer, still memory-mapped
  • Source array buffer is neutered after transfer
 worker.postMessage(ab, [ab]);
  • Is it possible to apply memory-mapped array buffer on blobs coming from IndexedDB?
Yes, there is a follow up at https://bugzilla.mozilla.org/show_bug.cgi?id=988815