Hi,
Goal:
I’m creating a binary diff for two consecutive bytecode bundle releases to be used as a patch. Giving an example for clarity, say I have a bytecode bundle released for March - march_bundle.hbc, and one for April - april_bundle.hbc, I’m creating a diff between the two using the library bsdiff. This creates a patch, let’s call it march_april_diff, that can be applied to march_bundle.hbc to get april_bundle.hbc. I’m doing this so that instead of serving the entire april_bundle.hbc (~5 MBs), I can only serve march_april_diff (~800 KBs) and apply it to march_bundle.hbc that resides in the app to get april_bundle.hbc.
Problem:
The size of the diff obtained from the two bytecode bundles is large for my use case and I’m trying to reduce its size.
What I’m trying to do:
I’m trying to debug how the bytecode is generated, in BytecodeModuleGenerator::generate() in BytecodeGenerator.cpp and the preceding files in the call stack. The idea is to modify the bytecode generation in a way so that the diff size reduces. My guess is that some IDs are being attached to modules during bytecode generation that are causing the diff to increase, although I can’t be sure. I’ll be glad if someone can provide some insight or point me in a meaningful direction!
Thanks!
Please take a look at the --base-bytecode compiler command line option. It allows you to specify a previous bytecode image when generating the new one, which should result in the new one being more diffable.
As another approach, we also have a tool called hbc-deltaprep, under tools/hbc-deltaprep. It converts the bytecode from and to a form that is inherently more diffable. That tool is not actively supported by us though.
I tested both methods on two close versions of a 23MB bytecode file:
--base-bytecode: 1.0 MB--base-bytecode is both the most impactful and the easiest to set up (since it requires no post-processing). The final bundle was just 0.006 MB larger.
Thanks @tmikov @willholen, I tried -base-bytecode and the size reduced to 340 KBs which is pretty amazing. Will try hbc-deltaprep as well. I’ll be glad if either of you can share what -base-bytecode and hbc-deltaprep actually do to reduce the diff size.
Also as a suggestion for future users, it’ll be great if a doc can be provided for a more detailed explanation of the command line options and tools.
Thanks again!
Hermes bytecode contains a string table, and all instructions using string literals or identifiers will reference an index into it. Minor changes to the JavaScript tend to cause all the indices to shift, thereby causing cascading changes to every such instruction.
-base-bytecode initializes the string/identifier table with that of the base bytecode file, so that these cascading shifts don't happen. The VM doesn't care how the string table is ordered, so this is a transparent change.
In addition to string indices, there are various other headers and tables, such as for function offsets and array buffers. These suffer from similar cascading issues, where e.g. adding a 32 byte function will cause all subsequent function offsets to increase by 32, making diffs larger.
hbc-deltaprep will essentially rewrite these indices from absolute to relative values to improve diffability. The VM can't work with these relative indices, so a separate and opposite post-processing step is required to rewrite them back into absolute values.
These are indeed underdocumented features.
I also facing this problem and find -base-byteocde option is useful,but I also has a question and hope anyone to give me a answer :)
These is my test table:
base 1 version(method 1) means all the version base on a original version. e.g. There are the first version named 1.hbc, and 2.hbc、3.hbc compiled base on 1.hbchbc base last version(method 2) means the next version base on the last version. e.g. There are the first version named 1.hbc, and 2.hbc compiled base on 1.hbc, 3.hbc compiled base on 2.hbc.| Code Change | js | hbc | hbc(base 1 version) | hbc base last version |
|---------|------|--------|------|--------|
| only change string | 306b | 130kb(400 times)| 389B(almost same as js diff) | 2kb (7 times) |
| about 50+ files modify | 14kb | 400kb(28 times)| 88kb(6 times) | 86kb(6 times) |
| about 100+files modify| 25kb | 458kb (18times)| 152kb(6 times) | 151kb(6 times) |
| about 170+ file modify | 46kb | 561kb (12 times) | 201kb(4.5 times) | 202kb(4.5 times)|
version 1 diff with version 13| version diff | js | Hbc | Hbc(base 1 version) | Hbc(base last version) |
|----------|------|-------------|---------------------|------------------------|
| 1 -> 13 | 27kb | 489kb(18 times) | 160kb (6 times) | 160kb (6 times) |
| 13 -> 24 | 12kb | 432kb(36 times) | 126kb (10.5 times) | 77kb (6.4 times) |
| 24 -> 30 | 14kb | 400kb(28 times) | 88kb (6 times) | 86kb (6 times) |
| 30 -> 31 | 306b | 133kb(400 times) | 389B (almost same as js diff) | 2kb (6 times) |
According to the two tables, I can draw the conclusion:
base-bytecode option can reduce diff size (almost 6 times)base on a same version(method 1) and base on the last version(method 2).only change string case, method 1 s result(almost close to jsbundle diff result ) is much smaller thanmethod 2`. (Why?)method 2 is better than method 1, expect only change string.If you have any question of my research, please remind me fell free~
Forigive my poor english OTL, @willholen @tmikov hope you can help me out~
Try hbc-deltaprep tools with this commad line:
./hbc-deltaprep ./index.hbc -out delta.hbc -form delta
to produce delta object.
Then use:
./hbc-deltaprep ./delta.hbc -out index2.hbc -form execution
convert back to index2.hbc
When I run the index2.hbc on my phone, it crash, but index.hbc is fine . I also notice the md5 is different between index.hbc and index2.hbc.
Is there something wrong with my operation?
@SinLucifer This looks like a bug. As mentioned, we don't actively support or use this experimental tool.
I filed a separate issue #211 about it.
Most helpful comment
I tested both methods on two close versions of a 23MB bytecode file:
--base-bytecode: 1.0 MB--base-bytecodeis both the most impactful and the easiest to set up (since it requires no post-processing). The final bundle was just 0.006 MB larger.