-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize mapObj #202
Optimize mapObj #202
Conversation
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
src/util.js
Outdated
const mappedObj = {}; | ||
for (let i = 0; i < keys.length; i += 1) { | ||
const key = keys[i]; | ||
const [newKey, newValue] = fn.call(undefined, [key, obj[key]]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd think that fn([key, obj[key]])
would be much faster than using .call
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so too, but this seemed to be a bit better, at least in Chrome. I was thinking it might have to do with thisObj being undefined, but maybe I'll take another look at the perf here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They seem to be about the same. I'll change it to fn() because it is less weird than fn.call.
I thought maybe it would be faster by removing the array here and passing args directly, but that didn't seem to really make a difference.
src/util.js
Outdated
const keys = Object.keys(obj); | ||
const mappedObj = {}; | ||
for (let i = 0; i < keys.length; i += 1) { | ||
const key = keys[i]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you're looking for performance, avoiding block-scoped vars in loops is a good way to do it - ie, repeating the keys[i]
in two places probably will give you a slight bump
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, maybe I'll give it a go. I originally avoided it for readability, and I think the performance improvement will be nominal. But maybe it will help with GC.
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
JSPerf: https://jsperf.com/aphrodite-mapobj-refactor/1 I'm seeing the new version about 10% faster in Chrome, 13% faster in Firefox. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Some small nit. Thanks for doing such thorough performance testing!
src/util.js
Outdated
const [newKey, newValue] = fn([keys[i], obj[keys[i]]]); | ||
mappedObj[newKey] = newValue; | ||
} | ||
return mappedObj; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we use 4 space indents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
I did some profiling of StyleSheet.create and noticed that mapObj was a good target for optimization. Methodology I created an HTML document with the following code in it: ```html <script type="text/javascript" src="./dist/aphrodite.umd.js"></script> <!-- setup --> <script type="text/javascript"> // build up an array of styles objects to run our test on var styles = []; for (var i = 0; i < 10000; i += 1) { styles.push({ [`a${Math.random()}`]: { [`a${Math.random()}`]: Math.random(), [`b${Math.random()}`]: String(Math.random()), [`c${Math.random()}`]: String(Math.random()), }, [`b${Math.random()}`]: { [`a${Math.random()}`]: Math.random(), [`b${Math.random()}`]: String(Math.random()), [`c${Math.random()}`]: String(Math.random()), }, [`c${Math.random()}`]: { [`a${Math.random()}`]: Math.random(), [`b${Math.random()}`]: String(Math.random()), [`c${Math.random()}`]: String(Math.random()), }, }); } </script> <!-- test --> <script type="text/javascript"> setTimeout(() => { performance.mark('start_run'); for (var i = 0; i < styles.length; i += 1) { // prevent caching optimizations eval(''); performance.mark('start_stylesheet_create'); aphrodite.StyleSheet.create(styles[i]); performance.mark('end_stylesheet_create'); performance.measure( 'aphrodite.StyleSheet.create', 'start_stylesheet_create', 'end_stylesheet_create' ); performance.clearMarks('start_stylesheet_create', 'end_stylesheet_create'); } performance.mark('end_run'); performance.measure(`Benchmark ${styles.length}`, 'start_run', 'end_run'); performance.clearMarks(); }); </script> ``` Then, looking at the timeline tool in Chrome, I loaded the page a few times before and after this change. Similarly, I ran a CPU profile before and after this change through 5 page reloads each. In this test, the timeline was not very helpful, I think because of the testing overhead. However, the CPU profile was very clear. Before this change, normalizing for the callback showing up in a different part of the profile, `mapObj` took ~317ms and after this change, it drops to ~211ms. `StyleSheet.create` drops from ~755ms to ~670ms or roughly 11% faster. The rest of the time in `StyleSheet.create` is spent in `murmurhash2_32_gc` and `hashObject`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM now!
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to Khan#202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms).
* Replace murmur hash with djb2 hash In profiling StyleSheet.create, I noticed that much of the time was spent hashing. So, I found a faster hashing algorithm. The implementation was taken from: https://github.com/darkskyapp/string-hash According to this StackExchange post, this algorithm doesn't have as good of randomness, but it has about the same percentage of collisions. I don't think randomness matters for this application, so I think this is okay. http://softwareengineering.stackexchange.com/a/145633 Using similar methodology to #202, this appears to make StylSheet.create ~15% faster (~220ms to ~185ms). * Depend on string-hash for djb2 hashing algorithm This is where I copied the code for this algorithm from, seems like we might as well just bring in the dependency for it.
I did some profiling of StyleSheet.create and noticed that mapObj was a
good target for optimization.
Methodology
I created an HTML document with the following code in it:
Then, looking at the timeline tool in Chrome, I loaded the page a few
times before and after this change. Similarly, I ran a CPU profile
before and after this change through 5 page reloads each.
In this test, the timeline was not very helpful, I think because of the
testing overhead. However, the CPU profile was very clear. Before this
change, normalizing for the callback showing up in a different part of
the profile,
mapObj
took ~317ms and after this change, it drops to~211ms.
StyleSheet.create
drops from ~755ms to ~670ms or roughly 11%faster. The rest of the time in
StyleSheet.create
is spent inmurmurhash2_32_gc
andhashObject
.cc @ljharb