stdlib: add a function to squeeze a number in a given range from a hash value

This function mixes the bits in the hash value, which improves Dictionary
performance for keys with bad hashes.

PrecommitBenchmark changes with greater than 7% difference:

``````````Dictionary2`,```1456.00`,```1508.00`,```1502.00`,````624.00`,````607.00`,````592.00`,`864.00`,``145.9%
``````````Dictionary3`,```1379.00`,```1439.00`,```1408.00`,````585.00`,````567.00`,````552.00`,`827.00`,``149.8%
````````````Histogram`,````850.00`,````849.00`,````851.00`,```1053.00`,```1049.00`,```1048.00`,`199.00`,``-19.0%
````````````````Prims`,```1999.00`,```2005.00`,```2018.00`,```1734.00`,```1689.00`,```1701.00`,`310.00`,```18.4%
``````````StrSplitter`,```2365.00`,```2334.00`,```2316.00`,```1979.00`,```1997.00`,```2000.00`,`337.00`,```17.0%
```````````````TwoSum`,```1551.00`,```1568.00`,```1556.00`,```1771.00`,```1741.00`,```1716.00`,`165.00`,```-9.6%

Regressions are in benchmarks that use `Int` as dictionary key: we are just
doing more work than previously (hashing an `Int` was an identity function).

rdar://17962402


Swift SVN r21142
This commit is contained in:
Dmitri Hrybenko
2014-08-12 12:02:26 +00:00
parent 0492048ead
commit 3a04e0809f
16 changed files with 520 additions and 51 deletions

View File

@@ -45,13 +45,6 @@ func strideofValue<T>(_:T) -> Int {
return strideof(T.self)
}
/// Returns if x is a power of 2, assuming that x is not 0
@transparent
func _isPowerOf2(v : Int) -> Bool {
_sanityCheck(v > 0)
return (v & (v - 1)) == 0
}
func _roundUpToAlignment(offset: Int, alignment: Int) -> Int {
_sanityCheck(offset >= 0)
_sanityCheck(alignment > 0)

View File

@@ -46,6 +46,7 @@ set(SWIFTLIB_ESSENTIAL
FixedPoint.swift.gyb
FloatingPoint.swift.gyb
FloatingPointOperations.swift.gyb
Hashing.swift
HeapBuffer.swift
ImplicitlyUnwrappedOptional.swift
Index.swift

View File

@@ -380,7 +380,7 @@ struct _NativeDictionaryStorage<Key : Hashable, Value> :
}
func _bucket(k: Key) -> Int {
return k.hashValue & _bucketMask
return _squeezeHashValue(k.hashValue, 0..<capacity)
}
func _next(bucket: Int) -> Int {

152
stdlib/core/Hashing.swift Normal file
View File

@@ -0,0 +1,152 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements helpers for constructing non-cryptographic hash
// functions.
//
// This code was ported from LLVM's ADT/Hashing.h.
//
// Currently the algorithm is based on CityHash, but this is an implementation
// detail. Even more, there are facilities to mix in a per-execution seed to
// ensure that hash values differ between executions.
//
struct _HashingDetail {
static var fixedSeedOverride: UInt64 = 0
@transparent
static func getExecutionSeed() -> UInt64 {
// FIXME: This needs to be a per-execution seed. This is just a placeholder
// implementation.
let seed: UInt64 = 0xff51afd7ed558ccd
return _HashingDetail.fixedSeedOverride == 0 ? seed : fixedSeedOverride
}
@transparent
static func hash16Bytes(low: UInt64, _ high: UInt64) -> UInt64 {
// Murmur-inspired hashing.
let mul: UInt64 = 0x9ddfea08eb382d69
var a: UInt64 = (low ^ high) &* mul
a ^= (a >> 47)
var b: UInt64 = (high ^ a) &* mul
b ^= (b >> 47)
b = b &* mul
return b
}
}
//
// API functions.
//
//
// _mix*() functions all have type (T) -> T. These functions don't compress
// their inputs and just exhibit avalance effect.
//
@transparent
func _mixUInt32(value: UInt32) -> UInt32 {
// Zero-extend to 64 bits, hash, select 32 bits from the hash.
//
// NOTE: this differs from LLVM's implementation, which selects the lower
// 32 bits. According to the statistical tests, the 3 lowest bits have
// weaker avalanche properties.
let extendedValue = UInt64(value)
let extendedResult = _mixUInt64(extendedValue)
return UInt32((extendedResult >> 3) & 0xffff_ffff)
}
@transparent
func _mixInt32(value: Int32) -> Int32 {
return Int32(bitPattern: _mixUInt32(UInt32(bitPattern: value)))
}
@transparent
func _mixUInt64(value: UInt64) -> UInt64 {
// Similar to hash_4to8_bytes but using a seed instead of length.
let seed: UInt64 = _HashingDetail.getExecutionSeed()
let low: UInt64 = value & 0xffff_ffff
let high: UInt64 = value >> 32
return _HashingDetail.hash16Bytes(seed + (low << 3), high);
}
@transparent
func _mixInt64(value: Int64) -> Int64 {
return Int64(bitPattern: _mixUInt64(UInt64(bitPattern: value)))
}
@transparent
func _mixUInt(value: UInt) -> UInt {
#if arch(i386) || arch(arm)
return UInt(_mixUInt32(UInt32(value)))
#elseif arch(x86_64) || arch(arm64)
return UInt(_mixUInt64(UInt64(value)))
#endif
}
@transparent
func _mixInt(value: Int) -> Int {
#if arch(i386) || arch(arm)
return Int(_mixInt32(Int32(value)))
#elseif arch(x86_64) || arch(arm64)
return Int(_mixInt64(Int64(value)))
#endif
}
/// Given a hash value, returns an integer value within the given range that
/// corresponds to a hash value.
///
/// This function is superior to computing the remainder of `hashValue` by
/// the range length. Some types have bad hash functions; sometimes simple
/// patterns in data sets create patterns in hash values and applying the
/// remainder operation just throws away even more information and invites
/// even more hash collisions. This effect is especially bad if the length
/// of the required range is a power of two -- applying the remainder
/// operation just throws away high bits of the hash (which would not be
/// a problem if the hash was known to be good). This function mixes the
/// bits in the hash value to compensate for such cases.
///
/// Of course, this function is a compressing function, and applying it to a
/// hash value does not change anything fundamentally: collisions are still
/// possible, and it does not prevent malicious users from constructing data
/// sets that will exhibit pathological collisions.
func _squeezeHashValue(hashValue: Int, resultRange: Range<Int>) -> Int {
// Length of a Range<Int> does not fit into an Int, but fits into an UInt.
// An efficient way to compute the length is to rely on two's complement
// arithmetic.
let resultCardinality =
UInt(bitPattern: resultRange.endIndex &- resultRange.startIndex)
// Calculate the result as `UInt` to handle the case when
// `resultCardinality >= Int.max`.
let unsignedResult =
_squeezeHashValue(hashValue, UInt(0)..<resultCardinality)
// We perform the unchecked arithmetic on `UInt` (instead of doing
// straightforward computations on `Int`) in order to handle the following
// tricky case: `startIndex` is negative, and `resultCardinality >= Int.max`.
// We can not convert the latter to `Int`.
return
Int(bitPattern:
UInt(bitPattern: resultRange.startIndex) &+ unsignedResult)
}
func _squeezeHashValue(hashValue: Int, resultRange: Range<UInt>) -> UInt {
let mixedHashValue = UInt(bitPattern: _mixInt(hashValue))
let resultCardinality: UInt = resultRange.endIndex - resultRange.startIndex
if _isPowerOf2(resultCardinality) {
return mixedHashValue & (resultCardinality - 1)
}
return resultRange.startIndex + (mixedHashValue % resultCardinality)
}

View File

@@ -67,6 +67,28 @@ func _stdlib_atomicCompareExchangeStrongPtr<T>(
return Int64(Builtin.int_ctlz_Int64(value.value, false.value))
}
/// Returns if `x` is a power of 2.
@transparent
public func _isPowerOf2(x: UInt) -> Bool {
if x == 0 {
return false
}
// Note: use unchecked subtraction because we have checked that `x` is not
// zero.
return x & (x &- 1) == 0
}
/// Returns if `x` is a power of 2.
@transparent
public func _isPowerOf2(x: Int) -> Bool {
if x <= 0 {
return false
}
// Note: use unchecked subtraction because we have checked that `x` is not
// `Int.min`.
return x & (x &- 1) == 0
}
@transparent public func _autorelease(x: AnyObject) {
Builtin.retain(x)
Builtin.autorelease(x)

View File

@@ -11,6 +11,7 @@ set(SWIFTUNITTEST_SOURCES
OpaqueIdentityFunctions.swift
PthreadBarriers.swift
PthreadWrappers.swift
Statistics.swift
StdlibCoreExtras.swift
SwiftBlockToCFunctionThunks.cpp
)

View File

@@ -0,0 +1,68 @@
import Darwin
public func rand32() -> UInt32 {
return arc4random()
}
public func rand64() -> UInt64 {
return (UInt64(arc4random()) << 32) | UInt64(arc4random())
}
public func randInt() -> Int {
#if arch(i386) || arch(arm)
return Int(Int32(bitPattern: rand32()))
#elseif arch(x86_64) || arch(arm64)
return Int(Int64(bitPattern: rand64()))
#else
fatalError("unimplemented")
#endif
}
public func randArray64(count: Int) -> ContiguousArray<UInt64> {
var result = ContiguousArray<UInt64>(count: count, repeatedValue: 0)
for i in indices(result) {
result[i] = rand64()
}
return result
}
/// For a given p-value, returns the critical chi-square value for
/// a distribution with 1 degree of freedom.
func _chiSquaredUniform1DFCritical(pValue: Double) -> Double {
if abs(pValue - 0.05) < 0.00001 { return 0.00393214 }
if abs(pValue - 0.02) < 0.00001 { return 0.000628450 }
if abs(pValue - 0.01) < 0.00001 { return 0.000157088 }
if abs(pValue - 0.007) < 0.00001 { return 0.000076971 }
if abs(pValue - 0.005) < 0.00001 { return 0.0000392704 }
if abs(pValue - 0.003) < 0.00001 { return 0.0000141372 }
if abs(pValue - 0.002) < 0.00001 { return 6.2832e-6 }
if abs(pValue - 0.001) < 0.00001 { return 1.5708e-6 }
fatalError("unknown value")
}
/// Perform chi-squared test for a discrete uniform distribution with
/// 2 outcomes.
public func chiSquaredUniform2(
trials: Int, observedACount: Int, pValue: Double
) -> Bool {
func square(x: Double) -> Double {
return x * x
}
let expectedA = 0.5
let expectedB = 0.5
let observedA = Double(observedACount) / Double(trials)
let observedB = 1.0 - observedA
let chiSq =
square(observedA - expectedA) / expectedA +
square(observedB - expectedB) / expectedB
if chiSq > _chiSquaredUniform1DFCritical(pValue) {
println("chi-squared test failed: \(trials) \(observedACount) \(chiSq)")
return false
}
return true
}

View File

@@ -235,6 +235,7 @@ public func assertionFailure() -> AssertionResult {
public func expectTrue(
actual: ${BoolType},
stackTrace: SourceLocStack? = nil,
_ collectMoreInfo: (()->String)? = nil,
file: String = __FILE__, line: UWord = __LINE__
) {
if !actual {
@@ -243,6 +244,7 @@ public func expectTrue(
_printStackTrace(stackTrace)
println("expected: true")
println("actual: \(actual)")
if collectMoreInfo != nil { println(collectMoreInfo!()) }
println()
}
}
@@ -250,6 +252,7 @@ public func expectTrue(
public func expectFalse(
actual: ${BoolType},
stackTrace: SourceLocStack? = nil,
_ collectMoreInfo: (()->String)? = nil,
file: String = __FILE__, line: UWord = __LINE__
) {
if actual {
@@ -258,6 +261,7 @@ public func expectFalse(
_printStackTrace(stackTrace)
println("expected: false")
println("actual: \(actual)")
if collectMoreInfo != nil { println(collectMoreInfo!()) }
println()
}
}

View File

@@ -109,8 +109,8 @@ if let obj: AnyObject = _bridgeToObjectiveC(a3) {
}
// CHECK: dictionary bridges to {
// CHECK-NEXT: 1 = Hello;
// CHECK-NEXT: 2 = World;
// CHECK-NEXT: 1 = Hello;
// CHECK-NEXT: }
var dict: Dictionary<NSNumber, NSString> = [1: "Hello", 2: "World"]
if let obj: AnyObject = _bridgeToObjectiveC(dict) {
@@ -120,8 +120,8 @@ if let obj: AnyObject = _bridgeToObjectiveC(dict) {
}
// CHECK: dictionary bridges to {
// CHECK-NEXT: 1 = Hello;
// CHECK-NEXT: 2 = World;
// CHECK-NEXT: 1 = Hello;
// CHECK-NEXT: }
var dict2 = [1: "Hello", 2: "World"]
if let obj: AnyObject = _bridgeToObjectiveC(dict2) {

View File

@@ -99,8 +99,8 @@ sub(x:f1, y:f2) // CHECK: Int = -1
var array = [1, 2, 3, 4, 5]
// CHECK: array : [Int] = [1, 2, 3, 4, 5]
var dict = [ "Hello" : 1.5, "World" : 3.0 ]
// CHECK: dict : [String : Double] = ["Hello": 1.5, "World": 3.0]
var dict = [ "Hello" : 1.5 ]
// CHECK: dict : [String : Double] = ["Hello": 1.5]
0..<10
// FIXME: Disabled CHECK for Range<Int> = 0...10 until we get general printing going

View File

@@ -370,25 +370,6 @@ println("\(intArrayMirror[0].0): \(intArrayMirror[0].1.summary)")
// CHECK-NEXT: [4]: 5
println("\(intArrayMirror[4].0): \(intArrayMirror[4].1.summary)")
let dict = ["One":1,"Two":2,"Three":3,"Four":4,"Five":5]
// CHECK-NEXT: 5 key/value pairs
// CHECK-NEXT: [0]: (2 elements)
// CHECK-NEXT: - .0: Four
// CHECK-NEXT: - .1: 4
// CHECK-NEXT: [1]: (2 elements)
// CHECK-NEXT: - .0: One
// CHECK-NEXT: - .1: 1
// CHECK-NEXT: [2]: (2 elements)
// CHECK-NEXT: - .0: Two
// CHECK-NEXT: - .1: 2
// CHECK-NEXT: [3]: (2 elements)
// CHECK-NEXT: - .0: Three
// CHECK-NEXT: - .1: 3
// CHECK-NEXT: [4]: (2 elements)
// CHECK-NEXT: - .0: Five
// CHECK-NEXT: - .1: 5
dump(dict)
enum JustSomeEnum {case A,B}
// CHECK-NEXT: (Enum Value)
println(reflect(JustSomeEnum.A).summary)

View File

@@ -0,0 +1,52 @@
// RUN: %target-build-swift -module-name a %s -o %t.out
// RUN: %target-run %t.out
//
// This file contains reflection tests that depend on hash values.
// Don't add other tests here.
//
import StdlibUnittest
var Reflection = TestCase("Reflection")
Reflection.test("Dictionary/Empty") {
let dict = [Int : Int]()
var output = ""
dump(dict, &output)
var expected = "- 0 key/value pairs\n"
expectEqual(expected, output)
}
Reflection.test("Dictionary") {
let dict = [ "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5 ]
var output = ""
dump(dict, &output)
var expected = ""
expected += "▿ 5 key/value pairs\n"
expected += " ▿ [0]: (2 elements)\n"
expected += " - .0: Five\n"
expected += " - .1: 5\n"
expected += " ▿ [1]: (2 elements)\n"
expected += " - .0: Two\n"
expected += " - .1: 2\n"
expected += " ▿ [2]: (2 elements)\n"
expected += " - .0: One\n"
expected += " - .1: 1\n"
expected += " ▿ [3]: (2 elements)\n"
expected += " - .0: Three\n"
expected += " - .1: 3\n"
expected += " ▿ [4]: (2 elements)\n"
expected += " - .0: Four\n"
expected += " - .1: 4\n"
expectEqual(expected, output)
}
runAllTests()

View File

@@ -858,5 +858,51 @@ Reflection.test("TupleMirror/NoLeak") {
}
}
var BitTwiddlingTestCase = TestCase("BitTwiddling")
func computeCountLeadingZeroes(var x: Int64) -> Int64 {
var r: Int64 = 64
while x != 0 {
x >>= 1
r--
}
return r
}
BitTwiddlingTestCase.test("_countLeadingZeros") {
for i in Int64(0)..<1000 {
expectEqual(computeCountLeadingZeroes(i), _countLeadingZeros(i))
}
expectEqual(0, _countLeadingZeros(Int64.min))
}
BitTwiddlingTestCase.test("_isPowerOf2/Int") {
expectFalse(_isPowerOf2(Int(-1025)))
expectFalse(_isPowerOf2(Int(-1024)))
expectFalse(_isPowerOf2(Int(-1023)))
expectFalse(_isPowerOf2(Int(-4)))
expectFalse(_isPowerOf2(Int(-3)))
expectFalse(_isPowerOf2(Int(-2)))
expectFalse(_isPowerOf2(Int(-1)))
expectFalse(_isPowerOf2(Int(0)))
expectTrue(_isPowerOf2(Int(1)))
expectTrue(_isPowerOf2(Int(2)))
expectFalse(_isPowerOf2(Int(3)))
expectTrue(_isPowerOf2(Int(1024)))
expectTrue(_isPowerOf2(Int(0x8000_0000)))
expectFalse(_isPowerOf2(Int.min))
expectFalse(_isPowerOf2(Int.max))
}
BitTwiddlingTestCase.test("_isPowerOf2/UInt") {
expectFalse(_isPowerOf2(UInt(0)))
expectTrue(_isPowerOf2(UInt(1)))
expectTrue(_isPowerOf2(UInt(2)))
expectFalse(_isPowerOf2(UInt(3)))
expectTrue(_isPowerOf2(UInt(1024)))
expectTrue(_isPowerOf2(UInt(0x8000_0000)))
expectFalse(_isPowerOf2(UInt.max))
}
runAllTests()

View File

@@ -1,20 +0,0 @@
// RUN: %target-run-simple-swift | FileCheck %s
func computeCountLeadingZeroes(xi: Int64) -> Int64 {
var r : Int64 = 64
var x = xi
while (x != 0) {
x >>= 1
r--
}
return r
}
func testeCountLeadingZeroes() {
for var i : Int64 = 1; i < 1000; i++ {
assert(_countLeadingZeros(i) == computeCountLeadingZeroes(i))
}
}
testeCountLeadingZeroes()
println("done!") // CHECK: {{^done!$}}

View File

@@ -0,0 +1,116 @@
// RUN: %target-build-swift -Xfrontend -disable-access-control -module-name a %s -o %t.out
// RUN: %target-run %t.out
import StdlibUnittest
var HashingTestCase = TestCase("Hashing")
HashingTestCase.test("_mixUInt32/GoldenValues") {
expectEqual(0x11b882c9, _mixUInt32(0x0))
expectEqual(0x60d0aafb, _mixUInt32(0x1))
expectEqual(0x636847b5, _mixUInt32(0xffff))
expectEqual(0x203f5350, _mixUInt32(0xffff_ffff))
expectEqual(0xb8747ef6, _mixUInt32(0xa62301f9))
expectEqual(0xef4eeeb2, _mixUInt32(0xfe1b46c6))
expectEqual(0xd44c9cf1, _mixUInt32(0xe4daf7ca))
expectEqual(0xfc1eb1de, _mixUInt32(0x33ff6f5c))
expectEqual(0x5605f0c0, _mixUInt32(0x13c2a2b8))
expectEqual(0xd9c48026, _mixUInt32(0xf3ad1745))
expectEqual(0x471ab8d0, _mixUInt32(0x656eff5a))
expectEqual(0xfe265934, _mixUInt32(0xfd2268c9))
}
HashingTestCase.test("_mixInt32/GoldenValues") {
expectEqual(Int32(bitPattern: 0x11b882c9), _mixInt32(0x0))
}
HashingTestCase.test("_mixUInt64/GoldenValues") {
expectEqual(0xb2b2_4f68_8dc4_164d, _mixUInt64(0x0))
expectEqual(0x792e_33eb_0685_57de, _mixUInt64(0x1))
expectEqual(0x9ec4_3423_1b42_3dab, _mixUInt64(0xffff))
expectEqual(0x4cec_e9c9_01fa_9a84, _mixUInt64(0xffff_ffff))
expectEqual(0xcba5_b650_bed5_b87c, _mixUInt64(0xffff_ffff_ffff))
expectEqual(0xe583_5646_3fb8_ac99, _mixUInt64(0xffff_ffff_ffff_ffff))
expectEqual(0xf5d0079f828d43a5, _mixUInt64(0x94ce7d9319f8d233))
expectEqual(0x61900a6be9db9c3f, _mixUInt64(0x2728821e8c5b1f7))
expectEqual(0xf2fd34b1b7d4b46e, _mixUInt64(0xe7f67ec98c64f482))
expectEqual(0x216199ed628c821, _mixUInt64(0xd7c277b5438873ac))
expectEqual(0xb1b486ff5f2e0e53, _mixUInt64(0x8399f1d563c42f82))
expectEqual(0x61acc92bd91c030, _mixUInt64(0x488cefd48a2c4bfd))
expectEqual(0xa7a52d6e4a8e3ddf, _mixUInt64(0x270a15116c351f95))
expectEqual(0x98ceedc363c4e56a, _mixUInt64(0xe5fb9b5f6c426a84))
}
HashingTestCase.test("_mixUInt64/GoldenValues") {
expectEqual(Int64(bitPattern: 0xb2b2_4f68_8dc4_164d), _mixInt64(0x0))
}
HashingTestCase.test("_mixUInt/GoldenValues") {
#if arch(i386) || arch(arm)
expectEqual(0x8dc4_164d, _mixUInt(0x0))
#elseif arch(x86_64) || arch(arm64)
expectEqual(0xb2b2_4f68_8dc4_164d, _mixUInt(0x0))
#else
fatalError("unimplemented")
#endif
}
HashingTestCase.test("_mixInt/GoldenValues") {
#if arch(i386) || arch(arm)
expectEqual(Int(bitPattern: 0x8dc4_164d), _mixInt(0x0))
#elseif arch(x86_64) || arch(arm64)
expectEqual(Int(bitPattern: 0xb2b2_4f68_8dc4_164d), _mixInt(0x0))
#else
fatalError("unimplemented")
#endif
}
HashingTestCase.test("_squeezeHashValue/Int") {
// Check that the function can return values that cover the whole range.
func checkRange(r: Range<Int>) {
var results = [Int : Void]()
for _ in 0..<(10 * (r.endIndex - r.startIndex)) {
let v = _squeezeHashValue(randInt(), r)
expectTrue(r ~= v)
if results[v] == nil {
results[v] = Void()
}
}
expectEqual(results.count, r.endIndex - r.startIndex)
}
checkRange(Int.min..<(Int.min+10))
checkRange(0..<4)
checkRange(0..<8)
checkRange(-5..<5)
checkRange((Int.max-10)..<(Int.max-1))
// Check that we can handle ranges that span more than `Int.max`.
expectEqual(0x32b24f688dc4164d, _squeezeHashValue(0, Int.min..<(Int.max - 1)))
expectEqual(-0x6d1cc14f97aa822, _squeezeHashValue(1, Int.min..<(Int.max - 1)))
}
HashingTestCase.test("_squeezeHashValue/UInt") {
// Check that the function can return values that cover the whole range.
func checkRange(r: Range<UInt>) {
var results = [UInt : Void]()
let cardinality = r.endIndex - r.startIndex
for _ in 0..<(10*cardinality) {
let v = _squeezeHashValue(randInt(), r)
expectTrue(r ~= v)
if results[v] == nil {
results[v] = Void()
}
}
expectEqual(results.count, Int(cardinality))
}
checkRange(0..<4)
checkRange(0..<8)
checkRange(0..<10)
checkRange(10..<20)
checkRange((UInt.max-10)..<(UInt.max-1))
}
runAllTests()

View File

@@ -0,0 +1,53 @@
// RUN: %target-build-swift -Xfrontend -disable-access-control -module-name a %s -o %t.out -O
// RUN: %target-run %t.out
import StdlibUnittest
var HashingTestCase = TestCase("Hashing")
func avalancheTest(bits: Int, hashUnderTest: (UInt64) -> UInt64, pValue: Double) {
let testsInBatch = 100000
let testData = randArray64(testsInBatch)
let testDataHashed = Array(lazy(testData).map { hashUnderTest($0) })
for inputBit in 0..<bits {
// Using an array here makes the test too slow.
var bitFlips = UnsafeMutablePointer<Int>.alloc(bits)
for i in 0..<bits {
bitFlips[i] = 0
}
for i in indices(testData) {
let inputA = testData[i]
let outputA = testDataHashed[i]
let inputB = inputA ^ (1 << UInt64(inputBit))
let outputB = hashUnderTest(inputB)
var delta = outputA ^ outputB
for outputBit in 0..<bits {
if delta & 1 == 1 {
++bitFlips[outputBit]
}
delta = delta >> 1
}
}
for outputBit in 0..<bits {
expectTrue(
chiSquaredUniform2(testsInBatch, bitFlips[outputBit], pValue)) {
"inputBit: \(inputBit), outputBit: \(outputBit)"
}
}
bitFlips.dealloc(bits)
}
}
// White-box testing: assume that the other N-bit to N-bit mixing functions
// just dispatch to these. (Avalanche test is relatively expensive.)
HashingTestCase.test("_mixUInt64/avalanche") {
avalancheTest(64, _mixUInt64, 0.02)
}
HashingTestCase.test("_mixUInt32/avalanche") {
avalancheTest(32, { UInt64(_mixUInt32(UInt32($0 & 0xffff_ffff))) }, 0.02)
}
runAllTests()