package org.rascalmpl.value.util; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Random; import java.util.Set; import javax.imageio.ImageIO; public class MixDistribution { private static final int[] BIT_OFFSET = new int[32]; static { for (int i=0; i < 32; i++) { BIT_OFFSET[i] = 1 << i; } } private static void reportCollisions(String name, int[] data, Mixer mixer) { Set<Integer> used = new HashSet<Integer>(); Set<Integer> seen = new HashSet<Integer>(); Set<Integer> seen16 = new HashSet<Integer>(); int collisions = 0; int collisions16 = 0; for (int d : data) { if (used.contains(d)) { continue; } used.add(d); int m = mixer.mix(d); if (seen.contains(m)) { collisions++; continue; } seen.add(m); int m16l = m & 0x0000FFFF; int m16h = m & 0xFFFF0000; if (seen16.contains(m16l) || seen16.contains(m16h)) { collisions16++; } seen16.add(m16l); seen16.add(m16h); } System.out.println(name + " full collisions: " + collisions); System.out.println(name + " 16-bit collisions: " + collisions16); } private static void reportHashDistribution(String name, int[] hashes) { int[] counts = new int[32]; for (int h : hashes) { for (int bit =0; bit < 32; bit++) { if ((h & BIT_OFFSET[bit]) != 0) { counts[bit]++; } } } System.out.print(name); for (int c: counts) { System.out.print(','); System.out.print(c); } System.out.println(); Double total = 0.0; int maxError = 0; System.out.print(name+"-offset"); for (int c: counts) { System.out.print(','); int error = c - (hashes.length / 2); System.out.print(error); total += (error * error); maxError = Math.max(maxError, Math.abs(error)); } System.out.println(); System.out.println(name + " max absolute offset: " + maxError); System.out.println(name + " root mean error:" + Math.sqrt(total / hashes.length)); } private static void createBitStatsPlot(String name, String category, int[] numbers, Mixer mixer) throws IOException { int[][] bits = new int[32][32]; for (int input : numbers) { int A = mixer.mix(input); for (int sourceBit = 0; sourceBit < 32; sourceBit++) { int flipped = input ^ BIT_OFFSET[sourceBit]; int B = mixer.mix(flipped); for (int bit =0; bit < 32; bit++) { int Abit = A & BIT_OFFSET[bit]; int Bbit = B & BIT_OFFSET[bit]; if (Abit != Bbit) { bits[sourceBit][bit]++; } } } } BufferedImage img = new BufferedImage(32, 32, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < 32; x++) { for (int y = 0; y < 32; y++) { img.setRGB(x, y, getColor(Math.abs((numbers.length/2)-bits[x][y])/((double)numbers.length/2))); } } ImageIO.write(img, "PNG", new File("avalanche-" + category +"-"+ name + ".png")); } private static int getColor(double errorRate) { double green = 0.0; double red = 0.0; if (0 <= errorRate && errorRate < 0.5 ) { green = 1.0; red = 2 * errorRate; } else { red = 1.0; green = 1.0 - 2 * (errorRate-0.5); } return (((int)Math.floor(red * 255)) << 16) | (((int)Math.floor(green * 255)) << 8) ; } public static void main(String[] args) throws IOException { Map<String, Mixer> mixers = new LinkedHashMap<>(); mixers.put("raw", new RawMix()); mixers.put("xxhash", new XXHashMix()); mixers.put("xxhash2", new XXHashMix2()); mixers.put("lookup3", new Lookup3Mix()); mixers.put("murmur2", new MurmurHash2Mix()); mixers.put("murmur2-2", new MurmurHash2Mix2()); mixers.put("murmur2-3", new MurmurHash2Mix3()); mixers.put("murmur2-4", new MurmurHash2Mix4()); mixers.put("murmur3", new MurmurHash3Mix()); mixers.put("murmur3-2", new MurmurHash3Mix2()); mixers.put("crapwow", new CrapWowMix()); mixers.put("crapwow-2", new CrapWowMix2()); mixers.put("crapwow-3", new CrapWowMix3()); mixers.put("encode.ru-m2-2", new EncodeRUm2Mix()); mixers.put("superfasthash", new SuperFastHashMix()); mixers.put("superfasthash2", new SuperFastHashMix2()); mixers.put("hashmap", new HashMapMix()); mixers.put("scala-hashmap", new ScalaHashMapMix()); int[] data = new int[10000]; for (int i=0; i < data.length; i++) { data[i] = i; } System.out.println("Numbers from 1-10000"); for (String m : mixers.keySet()) { reportHashDistribution(m, mix(data, mixers.get(m))); reportCollisions(m, data, mixers.get(m)); } System.out.println(""); System.out.println(""); System.out.println("Numbers from 1-10000 << 16"); for (int i=0; i < data.length; i++) { data[i] = i << 16; } for (String m : mixers.keySet()) { reportHashDistribution(m, mix(data, mixers.get(m))); reportCollisions(m, data, mixers.get(m)); } Random rand = new Random(); for (int i=0; i < data.length; i++) { data[i] = rand.nextInt(); } System.out.println(""); System.out.println(""); System.out.println(""); System.out.println("Random numbers"); for (String m : mixers.keySet()) { reportHashDistribution(m, mix(data, mixers.get(m))); reportCollisions(m, data, mixers.get(m)); createBitStatsPlot(m, "random", data, mixers.get(m)); } data = new int[512]; for (int i=0; i < data.length; i++) { data[i] = i; } System.out.println(""); System.out.println(""); System.out.println(""); System.out.println("Random small numbers"); for (String m : mixers.keySet()) { reportHashDistribution(m, mix(data, mixers.get(m))); reportCollisions(m, data, mixers.get(m)); } } private static int[] mix(int[] data, Mixer mixer) { int[] result = new int[data.length]; for (int i = 0; i < data.length; i++) { result[i] = mixer.mix(data[i]); } return result; } private static interface Mixer { int mix(int n); } private static class RawMix implements Mixer { @Override public int mix(int n) { return n; } } private static class XXHashMix implements Mixer { @Override public int mix(int n) { int h32 = 0x165667b1; h32 += n * (int)3266489917L; h32 = Integer.rotateLeft(h32, 17) * 668265263; h32 ^= h32 >>> 15; h32 *= (int) 2246822519L; h32 ^= h32 >>> 13; h32 *= (int) 3266489917L; h32 ^= h32 >>> 16; return h32; } } private static class XXHashMix2 implements Mixer { @Override public int mix(int n) { int h32 = n + 0x165667b1; h32 ^= h32 >>> 15; h32 *= (int) 2246822519L; h32 ^= h32 >>> 13; h32 *= (int) 3266489917L; h32 ^= h32 >>> 16; return h32; } } private static class HashMapMix implements Mixer { @Override public int mix(int n) { int h = n; h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } } private static class ScalaHashMapMix implements Mixer { @Override public int mix(int n) { int h = n + ~(n << 9); h = h ^ (h >>> 14); h = h + (h << 4); return h ^ (h >>> 10); } } private static class MurmurHash2Mix implements Mixer { @Override public int mix(int n) { int h = 1 ^ 4; int k = n; k = mixK(k); h = mixH(h, k); h *= m; h ^= h >>> 13; h *= m; h ^= h >>> 15; return h; } private final int m = 0x5bd1e995; private final int r = 24; private int mixK(int k) { k *= m; k ^= k >>> r; k *= m; return k; } private int mixH(int h, int k) { h *= m; h ^= k; return h; } } private static class MurmurHash2Mix2 implements Mixer { @Override public int mix(int n) { int h = n; h *= 0x5bd1e995; h ^= h >>> 13; h *= 0x5bd1e995; h ^= h >>> 15; return h; } } private static class MurmurHash2Mix3 implements Mixer { @Override public int mix(int n) { int h = n ^ 0x85ebca6b; h ^= h >>> 13; h *= 0x5bd1e995; h ^= h >>> 15; return h; } } private static class MurmurHash2Mix4 implements Mixer { @Override public int mix(int n) { int h = n ^ 0x85ebca6b; h ^= h >>> 13; h *= 0x5bd1e995; h ^= h >>> 15; h += ( h >> 22 ) ^ ( h << 4 ); return h; } } private static class Lookup3Mix implements Mixer { @Override public int mix(int n) { int a,b,c; a = b = c = 0xdeadbeef + (1<<2); a += n; { c ^= b; c -= (b<<14)|(b>>>-14); a ^= c; a -= (c<<11)|(c>>>-11); b ^= a; b -= (a<<25)|(a>>>-25); c ^= b; c -= (b<<16)|(b>>>-16); a ^= c; a -= (c<<4)|(c>>>-4); b ^= a; b -= (a<<14)|(a>>>-14); c ^= b; c -= (b<<24)|(b>>>-24); } return c; } } private static class MurmurHash3Mix implements Mixer{ @Override public int mix(int n) { int h = 1; int k = n; k = mixK(k); h = mixH(h, k); // finalizing h ^= 1; h ^= h >>> 16; h *= 0x85ebca6b; h ^= h >>> 13; h *= 0xc2b2ae35; h ^= h >>> 16; return h; } private final static int C1 = 0xcc9e2d51; private final static int C2 = 0x1b873593; private final static int M = 5; private final static int N = 0xe6546b64; private final static int mixK(int k) { k *= C1; k = Integer.rotateLeft(k, 15); k = k * C2; return k; } private final static int mixH(int h, int k) { h ^= k; h = Integer.rotateLeft(h, 13); h = h * M + N; return h; } } private static class MurmurHash3Mix2 implements Mixer{ @Override public int mix(int n) { int h = n ^ 1; h ^= h >>> 16; h *= 0x85ebca6b; h ^= h >>> 13; h *= 0xc2b2ae35; h ^= h >>> 16; return h; } } private static class SuperFastHashMix implements Mixer { @Override public int mix(int n) { int hash = 0, tmp; hash += n & 0xFFFF; tmp = ((n & 0xFFFF0000) >> 5) ^ hash; hash = (hash << 16) ^ tmp; hash += hash >> 11; hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } } private static class SuperFastHashMix2 implements Mixer { @Override public int mix(int n) { int hash = n; hash += hash >> 11; hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } } private static class CrapWowMix implements Mixer { private final static int M = 0x57559429, N = 0x5052acdb; @Override public int mix(int n) { int h = 1; int k = 1 + n + N; // "loop" long p = n * (long)M; h ^= (int)p; k ^= (int)(p >> 32); // end mix p = (h ^ (k + N)) * (long)N; k ^= (int)p; h ^= (int)(p >> 32); return k ^ h; } } private static class CrapWowMix2 implements Mixer { private final static int M = 0x57559429, N = 0x5052acdb; @Override public int mix(int n) { long p = n * (long)M; p ^= (long)n + (long)N; p *= N; return ((int)p) ^ (int)(p >> 32); } } private static class CrapWowMix3 implements Mixer { private final static int M = 0x57559429, N = 0x5052acdb; @Override public int mix(int n) { long p; p = n * (long)M; p ^= ((long)n) << 32; p *= (long)N; return ((int)p) ^ (int)(p >> 32); } } private static class EncodeRUm2Mix implements Mixer { @Override public int mix(int n) { long k = n; k ^= k >> 33; k *= 0xff51afd7ed558ccdL; k ^= k >> 33; k *= 0xc4ceb9fe1a85ec53L; k ^= k >> 33; return (int)k ^ (int)(k >> 32); } } }