Murmurhash3 comparison (v2)

Revision 2 of this benchmark created by Roland Pihlakas on


Description

My implementation is based on the observation that Javascript is not limited to exactly to 32-bit integers. Rather, it internally represents integers as double-precision floats. Therefore it is limited to 52-bit integers (the mantissa length). 52 bits is enough to contain 48 bits and therefore it is able to contain the result of multiplying 32-bit and 16-bit numbers without loss of lower bits. This observation enables the algorithm to be implemented so that it requires less operations to emulate 32-bit multiplications, as compared to an algorithm that uses 16-bit multiplications to emulate 32-bit multiplications. Also because of that, addition of 32-bit operands can be performed simpler.

The code of my version can be downloaded at https://github.com/levitation/murmurhash-js

Setup

/**
         * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011)
         * 
         * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
         * @see http://github.com/garycourt/murmurhash-js
         * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
         * @see http://sites.google.com/site/murmurhash/
         * 
         * @param {string} key ASCII only
         * @param {number} seed Positive integer only
         * @return {number} 32-bit positive integer hash 
         */
        
        function murmurhash3_32_gc(key, seed) {
            var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
            
            remainder = key.length & 3; // key.length % 4
            bytes = key.length - remainder;
            h1 = seed;
            c1 = 0xcc9e2d51;
            c2 = 0x1b873593;
            i = 0;
            
            while (i < bytes) {
                k1 = 
                  ((key.charCodeAt(i) & 0xff)) |
                  ((key.charCodeAt(++i) & 0xff) << 8) |
                  ((key.charCodeAt(++i) & 0xff) << 16) |
                  ((key.charCodeAt(++i) & 0xff) << 24);
                ++i;
                
                k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff;
                k1 = (k1 << 15) | (k1 >>> 17);
                k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff;
        
                h1 ^= k1;
                h1 = (h1 << 13) | (h1 >>> 19);
                h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff;
                h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16));
            }
            
            k1 = 0;
            
            switch (remainder) {
                case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
                case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
                case 1: k1 ^= (key.charCodeAt(i) & 0xff);
                
                k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
                k1 = (k1 << 15) | (k1 >>> 17);
                k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
                h1 ^= k1;
            }
            
            h1 ^= key.length;
        
            h1 ^= h1 >>> 16;
            h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
            h1 ^= h1 >>> 13;
            h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff;
            h1 ^= h1 >>> 16;
        
            return h1 >>> 0;
        }
    
    
        /**
         * JS Implementation of MurmurHash3 (r152) (as of May 10, 2013)
         * 
         * @author <a href="mailto:roland@simplify.ee">Roland Pihlakas</a>
         * @see http://github.com/levitation/murmurhash-js
         * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
         * @see https://code.google.com/p/smhasher/
         * 
         * @param {string} key ASCII only
         * @param {number} seed positive integer only
         * @return {number} 32-bit positive integer hash 
         */
    
        function murmurhash3_32_rp(key, seed) {
    
            var keyLength, tailLength, bodyLength, h1, k1, i, c1_low, c1_high, c2_low, c2_high;
    
            keyLength = key.length;
            tailLength = keyLength & 3;
            bodyLength = keyLength - tailLength;
            h1 = seed;
    
            //c1 = 0xcc9e2d51;
            c1_low = 0x2d51;
            c1_high = 0xcc9e0000;
    
            //c2 = 0x1b873593;
            c2_low = 0x3593;
            c2_high = 0x1b870000;
    
    
            //----------
            // body
    
            i = 0;
    
            while (i < bodyLength) {
    
                k1 =
                  ((key.charCodeAt(i) & 0xff)) |
                  ((key.charCodeAt(++i) & 0xff) << 8) |
                  ((key.charCodeAt(++i) & 0xff) << 16) |
                  ((key.charCodeAt(++i) & 0xff) << 24);
    
                ++i;
    
                //k1 *= c1;
                k1 = (c1_high * k1 | 0) + (c1_low * k1);
                //k1 = ROTL32(k1,15);
                k1 = (k1 << 15) | (k1 >> 17);
                //k1 *= c2;
                k1 = (c2_high * k1 | 0) + (c2_low * k1);
    
                //h1 ^= k1;
                h1 ^= k1;
                //h1 = ROTL32(h1,13); 
                h1 = (h1 << 13) | (h1 >>> 19);
                //h1 = h1*5+0xe6546b64;
                h1 = h1 * 5 + 0xe6546b64;
    
            }   //while (i < bodyLength) {
    
    
            //----------
            // tail
    
            k1 = 0;
    
            switch (tailLength) {
    
                case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
                case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
                case 1: k1 ^= (key.charCodeAt(i) & 0xff);
    
                    //k1 *= c1;
                    k1 = (c1_high * k1 | 0) + (c1_low * k1);
                    //k1 = ROTL32(k1,15);
                    k1 = (k1 << 15) | (k1 >> 17);
                    //k1 *= c2;
                    k1 = (c2_high * k1 | 0) + (c2_low * k1);
                    //h1 ^= k1;
                    h1 ^= k1;
    
            }   //switch (tailLength) {
    
    
            //----------
            // finalization
    
            h1 ^= keyLength;
    
            //h1 = fmix32(h1);
            {
                //h ^= h >> 16;
                h1 ^= h1 >>> 16;
                //h1 *= 0x85ebca6b;
                h1 = (0x85eb0000 * h1 | 0) + (0xca6b * h1);
                //h ^= h >> 13;
                h1 ^= h1 >>> 13;
                //h1 *= 0xc2b2ae35;
                h1 = (0xc2b20000 * h1 | 0) + (0xae35 * h1);
                //h ^= h >> 16;
                h1 ^= h1 >>> 16;
            }
    
    
            return h1 >>> 0;    //convert to unsigned int
    
        }   //function murmurhash3_32_rp(key, seed) {
        
        var i, s10, s1k, s1m = "",
        a10 = [], a1k = [];
        a10.length = 10;
        a1k.length = 1024;
        s10 = String.fromCharCode.apply(null, a10.map(function() {
            return ((Math.random() * 256) | 0);
        }));
        s1k = String.fromCharCode.apply(null, a1k.map(function() {
            return ((Math.random() * 256) | 0);
        }));
        for (i = 0; i < 1024; i++) s1m = s1m.concat(s1k);

Test runner

Ready to run.

Testing in
TestOps/sec
10-char string: MurmurHash3 by Gary Court
murmurhash3_32_gc(s10, 0x80808080)
ready
10-char string: MurmurHash3 by Roland Pihlakas
murmurhash3_32_rp(s10, 0x80808080)
ready
1k string: MurmurHash3 by Gary Court
murmurhash3_32_gc(s1k, 0x80808080)
ready
1k string: MurmurHash3 by Roland Pihlakas
murmurhash3_32_rp(s1k, 0x80808080)
ready
1M string: MurmurHash3 by Gary Court
murmurhash3_32_gc(s1m, 0x80808080)
ready
1M string: MurmurHash3 by Roland Pihlakas
murmurhash3_32_rp(s1m, 0x80808080)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 2: published by Roland Pihlakas on