asmcrypto / asmcrypto.js

JavaScript Cryptographic Library with performance in mind.
MIT License
659 stars 182 forks source link

Improving Firefox performance: heap+asm pooling #154

Open twiss opened 6 years ago

twiss commented 6 years ago

In Firefox, repeatedly constructing AES objects is really slow. Repeatedly allocating and discarding the heap and asm functions puts a lot of pressure on the Garbage Collector, and according to Firefox's profiler, it spends all of its time during encrypting/decrypting in the GC. That's not good.

In the old (JS) codebase, the static convenience functions used a shared heap and asm instance, which partially fixed this issue. However, if you needed the streaming API, and don't reuse the objects, you'd run into this issue. In the new (TS) codebase, this is presumably even worse, since the convenience functions allocate a new object every time, without reusing the heap+asm.

For @openpgpjs, instead of having a single shared heap, I've implemented a proof of concept of pooling the heap and asm functions: https://github.com/twiss/asmcrypto.js/commit/aa21f463fecd100247a58eb2612f572c49a30b17. This improves performance by about 4x in Firefox for us.

However, it has to make some assumptions: because JS doesn't have destructors, it assumes that after you call finish(), you won't want to use the same object again for another encryption/decryption. If you do, you incur a small performance hit due to the extra AES_Reset. Of course, there are various obvious optimizations to be made here (e.g. don't AES_Reset if the key&iv stayed the same) which could even help if you repeatedly call the static functions with the same key, for example.

However, there are always pathological cases where it's better to manually manage AES objects, rather than pool heap+asm, for example if you instantiate a first AES object, use it, then instantiate a second AES object with a different key, and then use the first one again. For that case, we could either add a config option not to pool, or add a parameter to finish() indicating that you want to use the object again.

The implementation linked above is still on the JS codebase, but would you be interested in a PR for pooling for the TS codebase?