From d37a81ac6265d12c42d78e05bf3a5e291c8f0746 Mon Sep 17 00:00:00 2001 From: Joe Lencioni Date: Sat, 4 Mar 2017 10:28:24 -0800 Subject: [PATCH] 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). --- src/util.js | 55 +++++++++++--------------------------------- tests/index_test.js | 2 +- tests/inject_test.js | 16 ++++++------- 3 files changed, 22 insertions(+), 51 deletions(-) diff --git a/src/util.js b/src/util.js index c6e90c2..be1289c 100644 --- a/src/util.js +++ b/src/util.js @@ -152,52 +152,23 @@ export const stringifyValue = ( }; /** - * JS Implementation of MurmurHash2 + * JS Implementation of djb2 * - * @author Gary Court - * @see http://github.com/garycourt/murmurhash-js - * @author Austin Appleby - * @see http://sites.google.com/site/murmurhash/ - * - * @param {string} str ASCII only - * @return {string} Base 36 encoded hash result + * @see /~https://github.com/darkskyapp/string-hash */ -function murmurhash2_32_gc(str) { - let l = str.length; - let h = l; - let i = 0; - let k; - - while (l >= 4) { - k = ((str.charCodeAt(i) & 0xff)) | - ((str.charCodeAt(++i) & 0xff) << 8) | - ((str.charCodeAt(++i) & 0xff) << 16) | - ((str.charCodeAt(++i) & 0xff) << 24); - - k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); - k ^= k >>> 24; - k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); - - h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; - - l -= 4; - ++i; - } - /* eslint-disable no-fallthrough */ // forgive existing code - switch (l) { - case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16; - case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8; - case 1: h ^= (str.charCodeAt(i) & 0xff); - h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); - } - /* eslint-enable no-fallthrough */ +function djb2Hash(str) { + let hash = 5381; + let i = str.length; - h ^= h >>> 13; - h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); - h ^= h >>> 15; + while(i) { + hash = (hash * 33) ^ str.charCodeAt(--i); + } - return (h >>> 0).toString(36); + /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed + * integers. Since we want the results to be always positive, convert the + * signed int to an unsigned by doing an unsigned bitshift. */ + return "" + (hash >>> 0); } // Hash a javascript object using JSON.stringify. This is very fast, about 3 @@ -208,7 +179,7 @@ function murmurhash2_32_gc(str) { // this to produce consistent hashes browsers need to have a consistent // ordering of objects. Ben Alpert says that Facebook depends on this, so we // can probably depend on this too. -export const hashObject = (object /* : ObjectMap */) /* : string */ => murmurhash2_32_gc(JSON.stringify(object)); +export const hashObject = (object /* : ObjectMap */) /* : string */ => djb2Hash(JSON.stringify(object)); const IMPORTANT_RE = /^([^:]+:.*?)( !important)?;$/; diff --git a/tests/index_test.js b/tests/index_test.js index 10cfbae..a9360c3 100644 --- a/tests/index_test.js +++ b/tests/index_test.js @@ -215,7 +215,7 @@ describe('StyleSheet.create', () => { }, }); - assert.equal(sheet.test._name, 'test_y60qhp'); + assert.equal(sheet.test._name, 'test_1158556445'); }); it('works for empty stylesheets and styles', () => { diff --git a/tests/inject_test.js b/tests/inject_test.js index 0a91149..8605843 100644 --- a/tests/inject_test.js +++ b/tests/inject_test.js @@ -369,11 +369,11 @@ describe('String handlers', () => { css(sheet.animate); flushToStyleTag(); - assertStylesInclude('@keyframes keyframe_1ptfkz1'); + assertStylesInclude('@keyframes keyframe_3424156980'); assertStylesInclude('from{left:10px;}'); assertStylesInclude('50%{left:20px;}'); assertStylesInclude('to{left:40px;}'); - assertStylesInclude('animation-name:keyframe_1ptfkz1'); + assertStylesInclude('animation-name:keyframe_3424156980'); }); it('doesn\'t add the same keyframes twice', () => { @@ -406,7 +406,7 @@ describe('String handlers', () => { const styleTags = global.document.getElementsByTagName("style"); const styles = styleTags[0].textContent; - assert.include(styles, '@keyframes keyframe_1ptfkz1'); + assert.include(styles, '@keyframes keyframe_3424156980'); assert.equal(styles.match(/@keyframes/g).length, 1); }); @@ -439,9 +439,9 @@ describe('String handlers', () => { css(sheet.animate); flushToStyleTag(); - assertStylesInclude('@keyframes keyframe_1q5qq7q'); - assertStylesInclude('@keyframes keyframe_1sbxkmr'); - assertStylesInclude('animation-name:keyframe_1q5qq7q,keyframe_1sbxkmr') + assertStylesInclude('@keyframes keyframe_2796205350'); + assertStylesInclude('@keyframes keyframe_4151659962'); + assertStylesInclude('animation-name:keyframe_2796205350,keyframe_4151659962') }); it('concatenates a custom keyframe animation with a plain string', () => { @@ -464,8 +464,8 @@ describe('String handlers', () => { css(sheet.animate); flushToStyleTag(); - assertStylesInclude('@keyframes keyframe_1q5qq7q'); - assertStylesInclude('animation-name:keyframe_1q5qq7q,hoo') + assertStylesInclude('@keyframes keyframe_2796205350'); + assertStylesInclude('animation-name:keyframe_2796205350,hoo') }); }); });