diff --git a/HISTORY b/HISTORY index 09fe1a89..c256792c 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,11 @@ SnapRAID HISTORY ================ +3.2 2013/6 +========== + * Better selection of the RAID6 implementation for different CPUs. + * Adds support for the new Spooky hash. It's faster in 64 bit architectures. + 3.1 2013/5 ========== * Direct use of Windows API for disk access to improve error reporting. diff --git a/Makefile.am b/Makefile.am index 031b7b18..23fcf97c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,7 @@ noinst_HEADERS = \ parity.h \ handle.h \ murmurhash3.c \ + spookyhash2.c \ tommyarray.h \ tommychain.h \ tommyhash.h \ diff --git a/acinclude.m4 b/acinclude.m4 index ceed823a..1b92cc9e 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -17,4 +17,3 @@ fi rm -f conftest* ]) - diff --git a/cpu.h b/cpu.h index 0609418a..9656d40a 100644 --- a/cpu.h +++ b/cpu.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Andrea Mazzoleni + * Copyright (C) 2011,2013 Andrea Mazzoleni * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,26 +26,66 @@ #define X86_FEATURE_XMM2 (0*32+26) /* Streaming SIMD Extensions-2 */ #define X86_FEATURE_MMXEXT (1*32+22) /* AMD MMX extensions */ -static inline int cpu_has(uint32_t flag) +static inline void cpuid(uint32_t func, uint32_t* reg) { - uint32_t eax = (flag >> 5) ? 0x80000001 : 1; - uint32_t edx; - asm volatile( -#if defined(__i386__) +#if defined(__i386__) && defined(__PIC__) /* allow compilation in PIC mode saving ebx */ - "push %%ebx\n" + "xchgl %%ebx, %1\n" "cpuid\n" - "pop %%ebx\n" - : "+a" (eax), "=d" (edx) - : : "ecx"); + "xchgl %%ebx, %1\n" + : "=a" (reg[0]), "=r" (reg[1]), "=c" (reg[2]), "=d" (reg[3]) + : "0" (func) #else "cpuid\n" - : "+a" (eax), "=d" (edx) - : : "ecx", "ebx"); + : "=a" (reg[0]), "=b" (reg[1]), "=c" (reg[2]), "=d" (reg[3]) + : "0" (func) #endif + ); +} - return (edx >> (flag & 31)) & 1; +static inline int cpu_has(uint32_t flag) +{ + uint32_t reg[4]; + + cpuid((flag >> 5) ? 0x80000001 : 1, reg); + + return (reg[3] >> (flag & 31)) & 1; +} + +#define CPU_VENDOR_MAX 13 + +static inline void cpu_info(char* vendor, unsigned* family, unsigned* model) +{ + uint32_t reg[4]; + unsigned f, ef, m, em; + + cpuid(0, reg); + + ((uint32_t*)vendor)[0] = reg[1]; + ((uint32_t*)vendor)[1] = reg[3]; + ((uint32_t*)vendor)[2] = reg[2]; + vendor[12] = 0; + + cpuid(1, reg); + + f = (reg[0] >> 8) & 0xF; + ef = (reg[0] >> 20) & 0xFF; + m = (reg[0] >> 4) & 0xF; + em = (reg[0] >> 16) & 0xF; + + if (strcmp(vendor, "AuthenticAMD") == 0) { + if (f < 15) { + *family = f; + *model = m; + } else { + *family = f + ef; + *model = m + (em << 4); + } + } else { + *family = f + ef; + *model = m + (em << 4); + } } static inline int cpu_has_mmx(void) @@ -61,6 +101,232 @@ static inline int cpu_has_sse2(void) cpu_has(X86_FEATURE_XMM2); } +/** + * Check if the processor has a slow SSE2 support. + * If yes, it's better to use MMX. + */ +static inline int cpu_has_slowsse2(void) +{ + char vendor[CPU_VENDOR_MAX]; + unsigned family; + unsigned model; + + cpu_info(vendor, &family, &model); + + if (strcmp(vendor, "GenuineIntel") == 0) { + /* Atom (x264) + * Intel(R) Atom(TM) CPU D525 @ 1.80GHz (info from user) + * Compiler gcc 4.7.2 + * CPU GenuineIntel, family 6, model 28, flags mmx sse2 slowsse2 slowmult + * Memory is little-endian 64-bit + * memset 2259 [MB/s] + * Murmur3 378 [MB/s] + * Spooky2 3423 [MB/s] + * RAID5 int32x2 1601 [MB/s] + * RAID5 mmxx2 2226 [MB/s] + * RAID5 sse2x2 3122 [MB/s] + * RAID6 int32x2 677 [MB/s] + * RAID6 mmxx2 1633 [MB/s] + * RAID6 sse2x2 500 [MB/s] + */ + if (family == 6 && model == 28) + return 1; + + /* Pentium-M "banias" (info from x264) */ + if (family == 6 && model == 9) + return 1; + + /* Pentium-M "dothan" (info from x264) */ + if (family == 6 && model == 13) + return 1; + + /* Core1 "yonah" (info from x264) */ + if (family == 6 && model == 14) + return 1; + } + + if (strcmp(vendor, "AuthenticAMD") == 0) { + /* Bobcat/Zacade (info from x264 -> full family 20) + * AMD E-350 Processor (info from user) + * Compiler gcc 4.7.3 + * CPU AuthenticAMD, family 20, model 1, flags mmx sse2 slowsse2 + * Memory is little-endian 64-bit + * memset 2137 [MB/s] + * Murmur3 1140 [MB/s] + * Spooky2 2326 [MB/s] + * RAID5 int32x2 1853 [MB/s] + * RAID5 mmxx2 2019 [MB/s] + * RAID5 sse2x2 2908 [MB/s] + * RAID6 int32x2 884 [MB/s] + * RAID6 mmxx2 1502 [MB/s] + * RAID6 sse2x2 1168 [MB/s] + */ + if (family == 20) + return 1; + + /* AMD Turion(tm) II Neo N40L Dual-Core Processor (info from user) + * Compiler gcc 4.7.1 + * CPU AuthenticAMD, family 16, model 6, flags mmx sse2 + * Memory is little-endian 64-bit + * memset 4910 [MB/s] + * Murmur3 1160 [MB/s] + * Spooky2 3994 [MB/s] + * RAID5 int32x2 530 [MB/s] + * RAID5 mmxx2 1114 [MB/s] + * RAID5 sse2x1 2127 [MB/s] + * RAID5 sse2x2 2804 [MB/s] + * RAID6 int32x2 316 [MB/s] + * RAID6 mmxx2 655 [MB/s] + * RAID6 sse2x2 639 [MB/s] + */ + if (family == 16 && model == 6) + return 1; + + /* AMD Athlon(tm) II X4 620 (info from user) + * Compiler gcc 4.7.3 + * CPU AuthenticAMD, family 16, model 5, flags mmx sse2 + * Memory is little-endian 64-bit + * memset 2845 [MB/s] + * Murmur3 2262 [MB/s] + * Murmur3x64 3500 [MB/s] + * Spooky2 4362 [MB/s] + * Spooky2x86 4000 [MB/s] + * RAID5 int32x2 1064 [MB/s] + * RAID5 mmxx2 2064 [MB/s] + * RAID5 sse2x2 3289 [MB/s] + * RAID6 int32x2 639 [MB/s] + * RAID6 mmxx2 1325 [MB/s] + * RAID6 sse2x2 915 [MB/s] + */ + if (family == 16 && model == 5) + return 1; + } + + return 0; +} + +/** + * Check if the processor has a slow MULT implementation. + * If yes, it's better to use a hash not based on multiplication. + */ +static inline int cpu_has_slowmult(void) +{ + char vendor[CPU_VENDOR_MAX]; + unsigned family; + unsigned model; + + cpu_info(vendor, &family, &model); + + if (strcmp(vendor, "GenuineIntel") == 0) { + /* Intel(R) Atom(TM) CPU D525 @ 1.80GHz (info from user) + * Compiler gcc 4.7.2 + * CPU GenuineIntel, family 6, model 28, flags mmx sse2 slowsse2 slowmult + * Memory is little-endian 64-bit + * memset 2259 [MB/s] + * Murmur3 378 [MB/s] + * Spooky2 3423 [MB/s] + * RAID5 int32x2 1601 [MB/s] + * RAID5 mmxx2 2226 [MB/s] + * RAID5 sse2x2 3122 [MB/s] + * RAID6 int32x2 677 [MB/s] + * RAID6 mmxx2 1633 [MB/s] + * RAID6 sse2x2 500 [MB/s] + */ + if (family == 6 && model == 28) + return 1; + } + + return 0; +} + +/* other cases */ + +/* Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz + * Compiler gcc 4.7.3 + * CPU GenuineIntel, family 6, model 58, flags mmx sse2 + * Memory is little-endian 32-bit + * memset0 34685 [MB/s] + * Murmur3 4170 [MB/s] + * Spooky2 2599 [MB/s] + * RAID5 int32x2 8919 [MB/s] + * RAID5 mmxx2 17170 [MB/s] + * RAID5 sse2x2 27478 [MB/s] + * RAID6 int32x2 1953 [MB/s] + * RAID6 mmxx2 7560 [MB/s] + * RAID6 sse2x2 13930 [MB/s] + */ + +/* Intel(R) Core(TM) i7-3740QM CPU @ 2.70GHz + * Compiler gcc 4.7.3 + * CPU GenuineIntel, family 6, model 58, flags mmx sse2 + * Memory is little-endian 64-bit + * memset0 26164 [MB/s] + * Murmur3 4469 [MB/s] + * Spooky2 12834 [MB/s] + * RAID5 int32x2 10775 [MB/s] + * RAID5 mmxx2 19644 [MB/s] + * RAID5 sse2x2 29168 [MB/s] + * RAID6 int32x2 3288 [MB/s] + * RAID6 mmxx2 8374 [MB/s] + * RAID6 sse2x2 15547 [MB/s] + */ + +/* Intel(R) Core(TM) i5 CPU 650 @ 3.20GHz + * Compiler gcc 4.7.1 + * CPU GenuineIntel, family 6, model 37, flags mmx sse2 + * Memory is little-endian 32-bit + * memset0 25468 [MB/s] + * Murmur3 3357 [MB/s] + * Spooky2 1861 [MB/s] + * RAID5 int32x2 5043 [MB/s] + * RAID5 mmxx2 9864 [MB/s] + * RAID5 sse2x2 16896 [MB/s] + * RAID6 int32x2 1100 [MB/s] + * RAID6 mmxx2 5056 [MB/s] + * RAID6 sse2x2 8726 [MB/s] + */ + +/* AMD Athlon(tm) 64 X2 Dual Core Processor 3600+ 1913.377 MHz + * Compiler gcc 4.7.3 + * CPU AuthenticAMD, family 15, model 107, flags mmx sse2 + * Memory is little-endian 32-bit + * memset 2414 [MB/s] + * Murmur3 1213 [MB/s] + * Spooky2 947 [MB/s] + * RAID5 int32x2 532 [MB/s] + * RAID5 mmxx2 1121 [MB/s] + * RAID5 sse2x2 2061 [MB/s] + * RAID6 int32x2 280 [MB/s] + * RAID6 mmxx2 632 [MB/s] + * RAID6 sse2x2 909 [MB/s] + */ + +/* ARM Feroceon 88FR131 rev 1 (v5l) + * Compiler gcc 4.7.2 + * CPU is not and x86/x64 + * Memory is little-endian 32-bit + * memset 763 [MB/s] + * Murmur3 237 [MB/s] + * Spooky2 216 [MB/s] + * RAID5 int32x2 233 [MB/s] + * RAID6 int32x2 103 [MB/s] + */ + +/* Intel(R) Xeon(R) CPU E3-1270 V2 @ 3.50GHz $ + * Compiler gcc 4.8.1 + * CPU GenuineIntel, family 6, model 58, flags mmx sse2 + * Memory is little-endian 64-bit + * memset 27842 [MB/s] + * Murmur3 4884 [MB/s] + * Spooky2 14039 [MB/s] + * RAID5 int32x2 11038 [MB/s] + * RAID5 mmxx2 20055 [MB/s] + * RAID5 sse2x2 30703 [MB/s] + * RAID6 int32x2 3267 [MB/s] + * RAID6 mmxx2 8883 [MB/s] + * RAID6 sse2x2 16433 [MB/s] + */ + #endif #endif diff --git a/murmurhash3.c b/murmurhash3.c index df3439ff..2de9fc67 100644 --- a/murmurhash3.c +++ b/murmurhash3.c @@ -28,25 +28,6 @@ // MurmurHash3 was written by Austin Appleby, and is placed in the public // domain. The author hereby disclaims copyright to this source code. -/* Rotate left */ -inline uint32_t rotl32(uint32_t x, int8_t r) -{ - return (x << r) | (x >> (32 - r)); -} - -/* Swap endianess */ -#if WORDS_BIGENDIAN -static inline uint32_t swap32(uint32_t v) -{ - return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | ((v >> 24) & 0xff); -} -#else -static inline uint32_t swap32(uint32_t v) -{ - return v; -} -#endif - /* Finalization mix - force all bits of a hash block to avalanche */ static inline uint32_t fmix32(uint32_t h) { @@ -94,26 +75,33 @@ uint32_t c2 = 0xab0e9789; uint32_t c3 = 0x38b34ae5; uint32_t c4 = 0xa1e38b93; -void MurmurHash3_x86_128(const void * key, unsigned len, uint32_t seed, void* out) +void MurmurHash3_x86_128(const void* key, unsigned len, void* out) { - const uint8_t * data = (const uint8_t*)key; + const uint8_t* data = key; unsigned nblocks = len / 16; unsigned i; - uint32_t h1 = seed; - uint32_t h2 = seed; - uint32_t h3 = seed; - uint32_t h4 = seed; + uint32_t h1 = 0; + uint32_t h2 = 0; + uint32_t h3 = 0; + uint32_t h4 = 0; const uint32_t* blocks = (const uint32_t*)data; /* body */ for(i=0;i. + */ + +/* + * Derivative work from SpookyV2.cpp/h + * + * WARNING!!!! Note that this implementation doesn't use the short hash optimization + * resulting in different hashes for any length shorter than 192 bytes + * + * SpookyHash + * http://burtleburtle.net/bob/hash/spooky.html + * + * Exact source used as reference: + * http://burtleburtle.net/bob/c/SpookyV2.h + * http://burtleburtle.net/bob/c/SpookyV2.cpp + */ + +// Spooky Hash +// A 128-bit noncryptographic hash, for checksums and table lookup +// By Bob Jenkins. Public domain. +// Oct 31 2010: published framework, disclaimer ShortHash isn't right +// Nov 7 2010: disabled ShortHash +// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again +// April 10 2012: buffer overflow on platforms without unaligned reads +// July 12 2012: was passing out variables in final to in/out in short +// July 30 2012: I reintroduced the buffer overflow +// August 5 2012: SpookyV2: d = should be d += in short hash, and remove extra mix from long hash +// +// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages. +// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit. +// +// This was developed for and tested on 64-bit x86-compatible processors. +// It assumes the processor is little-endian. There is a macro +// controlling whether unaligned reads are allowed (by default they are). +// This should be an equally good hash on big-endian machines, but it will +// compute different results on them than on little-endian machines. +// +// Google's CityHash has similar specs to SpookyHash, and CityHash is faster +// on new Intel boxes. MD4 and MD5 also have similar specs, but they are orders +// of magnitude slower. CRCs are two or more times slower, but unlike +// SpookyHash, they have nice math for combining the CRCs of pieces to form +// the CRCs of wholes. There are also cryptographic hashes, but those are even +// slower than MD5. +// + +#define Mix(data, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11) \ + s0 += data[0]; s2 ^= s10; s11^= s0; s0 = rotl64(s0,11); s11 += s1; \ + s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = rotl64(s1,32); s0 += s2; \ + s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = rotl64(s2,43); s1 += s3; \ + s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = rotl64(s3,31); s2 += s4; \ + s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = rotl64(s4,17); s3 += s5; \ + s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = rotl64(s5,28); s4 += s6; \ + s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = rotl64(s6,39); s5 += s7; \ + s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = rotl64(s7,57); s6 += s8; \ + s8 += data[8]; s10^= s6; s7 ^= s8; s8 = rotl64(s8,55); s7 += s9; \ + s9 += data[9]; s11^= s7; s8 ^= s9; s9 = rotl64(s9,54); s8 += s10; \ + s10+= data[10]; s0 ^= s8; s9 ^= s10; s10= rotl64(s10,22); s9 += s11; \ + s11+= data[11]; s1 ^= s9; s10^= s11; s11= rotl64(s11,46); s10 += s0; + +#define EndPartial(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9, h10, h11) \ + h11+= h1; h2 ^= h11; h1 = rotl64(h1,44); \ + h0 += h2; h3 ^= h0; h2 = rotl64(h2,15); \ + h1 += h3; h4 ^= h1; h3 = rotl64(h3,34); \ + h2 += h4; h5 ^= h2; h4 = rotl64(h4,21); \ + h3 += h5; h6 ^= h3; h5 = rotl64(h5,38); \ + h4 += h6; h7 ^= h4; h6 = rotl64(h6,33); \ + h5 += h7; h8 ^= h5; h7 = rotl64(h7,10); \ + h6 += h8; h9 ^= h6; h8 = rotl64(h8,13); \ + h7 += h9; h10^= h7; h9 = rotl64(h9,38); \ + h8 += h10; h11^= h8; h10= rotl64(h10,53); \ + h9 += h11; h0 ^= h9; h11= rotl64(h11,42); \ + h10+= h0; h1 ^= h10; h0 = rotl64(h0,54); \ + +#define End(data, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9, h10,h11) \ + h0 += data[0]; h1 += data[1]; h2 += data[2]; h3 += data[3]; \ + h4 += data[4]; h5 += data[5]; h6 += data[6]; h7 += data[7]; \ + h8 += data[8]; h9 += data[9]; h10+= data[10]; h11+= data[11]; \ + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); \ + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); \ + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + +// number of uint64_t's in internal state +#define sc_numVars 12 + +// size of the internal state +#define sc_blockSize (sc_numVars*8) + +// +// sc_const: a constant which: +// * is not zero +// * is odd +// * is a not-very-regular mix of 1's and 0's +// * does not need any other special mathematical properties +// +#define sc_const 0xdeadbeefdeadbeefLL + +void SpookyHash128(const void* message, size_t length, uint8_t* digest) +{ + uint64_t h0, h1, h2, h3, h4, h5, h6, h7, h8, h9, h10, h11; + uint64_t buf[sc_numVars]; + uint64_t* end; + union { + const uint8_t* p8; + uint64_t* p64; + size_t i; + } u; + size_t remainder; + + h0 = h3 = h6 = h9 = 0; + h1 = h4 = h7 = h10 = 0; + h2 = h5 = h8 = h11 = sc_const; + + u.p8 = message; + end = u.p64 + (length/sc_blockSize)*sc_numVars; + + /* body */ + + while (u.p64 < end) { +#if WORDS_BIGENDIAN + unsigned i; + for(i=0;ipool[0] = 0; state->pool_device = 0; state->level = 1; /* default is the lowest protection */ - state->hash = HASH_MURMUR3; /* default is the fastest */ + + /* select the fastest hash */ + +#if defined(__i386__) || defined(__x86_64__) + if (sizeof(void*) == 4 && !cpu_has_slowmult()) + state->hash = HASH_MURMUR3; + else + state->hash = HASH_SPOOKY2; +#else + if (sizeof(void*) == 4) + state->hash = HASH_MURMUR3; + else + state->hash = HASH_SPOOKY2; +#endif + state->hash = HASH_MURMUR3; + tommy_list_init(&state->disklist); tommy_list_init(&state->maplist); tommy_list_init(&state->contentlist); @@ -1416,6 +1432,9 @@ void state_read(struct snapraid_state* state) } else if (strcmp(buffer, "murmur3") == 0) { state->hash = HASH_MURMUR3; hash = oathash8(hash, 'u'); + } else if (strcmp(buffer, "spooky2") == 0) { + state->hash = HASH_SPOOKY2; + hash = oathash8(hash, 'k'); } else { fprintf(stderr, "Invalid 'checksum' specification '%s' in '%s' at line %u\n", buffer, path, line); exit(EXIT_FAILURE); @@ -1630,10 +1649,17 @@ void state_write(struct snapraid_state* state) sputsl("checksum md5", f); hash = oathash8(hash, 'c'); hash = oathash8(hash, 'm'); - } else { + } else if (state->hash == HASH_MURMUR3) { sputsl("checksum murmur3", f); hash = oathash8(hash, 'c'); hash = oathash8(hash, 'u'); + } else if (state->hash == HASH_SPOOKY2) { + sputsl("checksum spooky2", f); + hash = oathash8(hash, 'c'); + hash = oathash8(hash, 'k'); + } else { + fprintf(stderr, "Unexpected hash when writing the content file '%s'.\n", serrorfile(f)); + exit(EXIT_FAILURE); } sputeol(f); if (serror(f)) { diff --git a/util.c b/util.c index 45199493..7e50e9d3 100644 --- a/util.c +++ b/util.c @@ -795,13 +795,55 @@ char* strdup_nofail(const char* str) return ptr; } +/****************************************************************************/ +/* hash */ + +/* Rotate left 32 */ +inline uint32_t rotl32(uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + +inline uint64_t rotl64(uint64_t x, int8_t r) +{ + return (x << r) | (x >> (64 - r)); +} + +/* Swap endianess */ +#if HAVE_BYTESWAP_H + +#include + +#define swap32(x) bswap_32(x) +#define swap64(x) bswap_64(x) + +#else +static inline uint32_t swap32(uint32_t v) +{ + return (rotl32(v, 8) & 0x00ff00ff) + | (rotl32(v, 24) & 0xff00ff00); +} + +static inline uint64_t swap64(uint64_t v) +{ + return (rotl64(v, 8) & 0x000000ff000000ffLLU) + | (rotl64(v, 24) & 0x0000ff000000ff00LLU) + | (rotl64(v, 40) & 0x00ff000000ff0000LLU) + | (rotl64(v, 56) & 0xff000000ff000000LLU); +} +#endif + #include "murmurhash3.c" +#include "spookyhash2.c" void memhash(unsigned kind, void* digest, const void* src, unsigned size) { switch (kind) { case HASH_MURMUR3 : - MurmurHash3_x86_128(src, size, 0, digest); + MurmurHash3_x86_128(src, size, digest); + break; + case HASH_SPOOKY2 : + SpookyHash128(src, size, digest); break; default: fprintf(stderr, "Internal inconsistency in hash function %u\n", kind); diff --git a/util.h b/util.h index 3354e7b2..a91f8bac 100644 --- a/util.h +++ b/util.h @@ -390,6 +390,7 @@ char* strdup_nofail(const char* str); #define HASH_UNDEFINED 0 #define HASH_MD5 1 #define HASH_MURMUR3 2 +#define HASH_SPOOKY2 3 /** * Computes the HASH of a memory block.