Description of the problem
UUID generation in Math.js produces strings stored as concatenated strings in Firefox and Chrome.
On Chrome 40bytes are used for each component of the final string, creating heap overhead. In my application with thousands of objects this adds up.
For example, Heap size using Chrome
r89 as is: 95MB
r89 with uuid flattened with String().toUpper(): 66MB.
I see similar results using Firefox.
Three.js version
- Dev
- [x ] r89
- …
Browser
- All of them
- [x ] Chrome
- Firefox
- Internet Explorer
OS
- All of them
- Windows
- macOS
- Linux
- Android
- iOS
.toUpperCase()
technique seems very interesting.I evaluated memory consumption with the following code.
600 Bytes
per uuid withTHREE.Math.generateUUID()
while64 Bytes
per uuid withTHREE.Math.generateUUID().toUpperCase()
on my Windows10 + Chrome.This tech reduces the uuid memory consumption 8x.
64 Bytes
per 36 chars sounds very reasonable.detail:
5.1MB for Three.js itself + Array on heap. (heap where I comment out the loop)
42.6MB with
THREE.Math.generateUUID()
on heap, 600B (= (42.6MB – 5.1MB) / 0x10000) per uuid9.1MB with
THREE.Math.generateUUID().toUpperCase()
on heap, 64B (= (9.1MB – 5.1MB) / 0x10000) per uuidAs @aardgoose this’s JavaScript strings memory layout topic.
I’m not so familiar with that, but the following is my rough understanding.
For example, uuid “D30D4BC2-4BAE-49FF-8A3F-3F9A4467A0FA” layout in low-layer is represented as linked list, like
because of
generateUUID()
logic, concatenating strings of LUT elements.https://github.com/mrdoob/three.js/blob/dev/src/math/Math.js#L11
And seems like
.toUpper/LowerCase()
allocates new consecutive memory space “D30D4BC2-4BAE-49FF-8A3F-3F9A4467A0FA”. Therefore, memory consumption will be reduced by removing overheads.Even if the two strings looks the same in the JS code layer, the memory layout can be different in low-layer.
.toUpper/LowerCase()
tech optimizes it.I hope JS engine optimizes well and we won’t need this hacky tech in the future.
@Usnul I tried that here, it was 15 times slower.
@makc I have a bit of a counter to what you have written:
https://jsfiddle.net/Travnik/3guqr5va/21/
numbers:
generateUUID: 347.219970703125ms <- base
generateUUID2: 1697.90185546875ms <- your version
generateUUID3: 158.04296875ms <- my version, using 16 byte array instead of your 36
The main problem is in materializing the string. Actual generation is pretty quick and compact.
[update]
for purists, I added example without UUID object wrapper, just using Uint8Array:
generateUUID4: 141.053955078125ms
@mrdoob Probably no perfect solution but there may be better situation. I’m gonna follow up the discussion and try some ideas soon. In my mind, there’re two options now. Using “Number tech” mentioned in #13094 or reverting generateUUID. (#13077 would also work?)
IMO huge memory consumption can lead slow performance, we should fix.