diff --git a/gcs/bench_test.go b/gcs/bench_test.go new file mode 100644 index 00000000..f71ade09 --- /dev/null +++ b/gcs/bench_test.go @@ -0,0 +1,127 @@ +// Copyright (c) 2018-2019 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package gcs + +import ( + "math/rand" + "testing" +) + +var ( + // globalMatch is used to ensure the benchmarks do not elide code. + globalMatch bool + + // Collision probability for the benchmarks (1/2**20). + P = uint8(20) +) + +// genFilterElements generates the given number of elements using the provided +// prng. This allows a prng with a fixed seed to be provided so the same values +// are produced for each benchmark iteration. +func genFilterElements(numElements uint, prng *rand.Rand) ([][]byte, error) { + result := make([][]byte, numElements) + for i := uint(0); i < numElements; i++ { + randElem := make([]byte, 32) + if _, err := prng.Read(randElem); err != nil { + return nil, err + } + result[i] = randElem + } + + return result, nil +} + +// BenchmarkFilterBuild benchmarks building a filter with 50000 elements. +func BenchmarkFilterBuild50000(b *testing.B) { + // Use a fixed prng seed for stable benchmarks. + prng := rand.New(rand.NewSource(0)) + contents, err := genFilterElements(50000, prng) + if err != nil { + b.Fatalf("unable to generate random item: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + var key [KeySize]byte + for i := 0; i < b.N; i++ { + _, err := NewFilter(P, key, contents) + if err != nil { + b.Fatalf("unable to generate filter: %v", err) + } + } +} + +// BenchmarkFilterBuild benchmarks building a filter with 100000 elements. +func BenchmarkFilterBuild100000(b *testing.B) { + // Use a fixed prng seed for stable benchmarks. + prng := rand.New(rand.NewSource(0)) + contents, err := genFilterElements(100000, prng) + if err != nil { + b.Fatalf("unable to generate random item: %v", err) + } + + b.ReportAllocs() + b.ResetTimer() + var key [KeySize]byte + for i := 0; i < b.N; i++ { + _, err := NewFilter(P, key, contents) + if err != nil { + b.Fatalf("unable to generate filter: %v", err) + } + } +} + +// BenchmarkFilterMatch benchmarks querying a filter for a single value. +func BenchmarkFilterMatch(b *testing.B) { + // Use a fixed prng seed for stable benchmarks. + prng := rand.New(rand.NewSource(0)) + contents, err := genFilterElements(20, prng) + if err != nil { + b.Fatalf("unable to generate random item: %v", err) + } + + var key [KeySize]byte + filter, err := NewFilter(P, key, contents) + if err != nil { + b.Fatalf("Failed to build filter") + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + globalMatch = filter.Match(key, []byte("Nate")) + globalMatch = filter.Match(key, []byte("Nates")) + } +} + +// BenchmarkFilterMatchAny benchmarks querying a filter for a list of values. +func BenchmarkFilterMatchAny(b *testing.B) { + // Generate elements for filter. + prng1 := rand.New(rand.NewSource(0)) + contents, err := genFilterElements(20, prng1) + if err != nil { + b.Fatalf("unable to generate random item: %v", err) + } + + // Generate matches using a separate prng seed so they're very likely all + // misses. + prng2 := rand.New(rand.NewSource(1)) + matchList, err := genFilterElements(20, prng2) + if err != nil { + b.Fatalf("unable to generate random item: %v", err) + } + + var key [KeySize]byte + filter, err := NewFilter(P, key, contents) + if err != nil { + b.Fatalf("Failed to build filter") + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + globalMatch = filter.MatchAny(key, matchList) + } +} diff --git a/gcs/bits.go b/gcs/bits.go index b0c84695..39e59ba8 100644 --- a/gcs/bits.go +++ b/gcs/bits.go @@ -127,6 +127,9 @@ func (b *bitReader) readNBits(n uint) (uint64, error) { if n > 64 { panic("gcs: cannot read more than 64 bits as a uint64") } + if n == 0 { + return 0, nil + } if len(b.bytes) == 0 { return 0, io.EOF @@ -175,14 +178,6 @@ func (b *bitReader) readNBits(n uint) (uint64, error) { // Read the remaining bits. for n > 0 { - if b.next == 0 { - b.bytes = b.bytes[1:] - if len(b.bytes) == 0 { - return 0, io.EOF - } - b.next = 1 << 7 - } - n-- if b.bytes[0]&b.next != 0 { value |= 1 << n diff --git a/gcs/gcs.go b/gcs/gcs.go index 6515a5dd..c52f293f 100644 --- a/gcs/gcs.go +++ b/gcs/gcs.go @@ -161,11 +161,13 @@ func FromBytes(N uint32, P uint8, d []byte) (*Filter, error) { // FromNBytes deserializes a GCS filter from a known P, and serialized N and // filter as returned by NBytes(). func FromNBytes(P uint8, d []byte) (*Filter, error) { - if len(d) < 4 { + var n uint32 + if len(d) >= 4 { + n = binary.BigEndian.Uint32(d[:4]) + } else if len(d) < 4 && len(d) != 0 { return nil, ErrMisserialized } - n := binary.BigEndian.Uint32(d[:4]) f := &Filter{ n: n, p: P, diff --git a/gcs/gcs_test.go b/gcs/gcs_test.go index bdd65f92..45896225 100644 --- a/gcs/gcs_test.go +++ b/gcs/gcs_test.go @@ -1,6 +1,4 @@ -// Copyright (c) 2016-2017 The btcsuite developers -// Copyright (c) 2016-2017 The Lightning Network Developers -// Copyright (c) 2018 The Decred developers +// Copyright (c) 2018-2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -9,218 +7,340 @@ package gcs import ( "bytes" "encoding/binary" + "encoding/hex" "math/rand" "testing" + "time" "github.com/decred/dcrd/chaincfg/chainhash" ) -var ( - // No need to allocate an err variable in every test - err error - - // Collision probability for the tests (1/2**20) - P = uint8(20) - - // Filters are conserved between tests but we must define with an - // interface which functions we're testing because the gcsFilter type - // isn't exported - filter, filter2, filter3 *Filter - - // We need to use the same key for building and querying the filters - key [KeySize]byte - - // List of values for building a filter - contents = [][]byte{ - []byte("Alex"), - []byte("Bob"), - []byte("Charlie"), - []byte("Dick"), - []byte("Ed"), - []byte("Frank"), - []byte("George"), - []byte("Harry"), - []byte("Ilya"), - []byte("John"), - []byte("Kevin"), - []byte("Larry"), - []byte("Michael"), - []byte("Nate"), - []byte("Owen"), - []byte("Paul"), - []byte("Quentin"), - } - - // List of values for querying a filter using MatchAny() - contents2 = [][]byte{ - []byte("Alice"), - []byte("Betty"), - []byte("Charmaine"), - []byte("Donna"), - []byte("Edith"), - []byte("Faina"), - []byte("Georgia"), - []byte("Hannah"), - []byte("Ilsbeth"), - []byte("Jennifer"), - []byte("Kayla"), - []byte("Lena"), - []byte("Michelle"), - []byte("Natalie"), - []byte("Ophelia"), - []byte("Peggy"), - []byte("Queenie"), - } -) - -// TestGCSFilterBuild builds a test filter with a randomized key. For Bitcoin -// use, deterministic filter generation is desired. Therefore, a key that's -// derived deterministically would be required. -func TestGCSFilterBuild(t *testing.T) { +// TestFilter ensures the filters and all associated methods work as expected by +// using various known parameters and contents along with random keys for +// matching purposes. +func TestFilter(t *testing.T) { + // Use a random key for each test instance and log it if the tests fail. + rng := rand.New(rand.NewSource(time.Now().Unix())) + var randKey [KeySize]byte for i := 0; i < KeySize; i += 4 { - binary.BigEndian.PutUint32(key[i:], rand.Uint32()) + binary.BigEndian.PutUint32(randKey[i:], rng.Uint32()) } - filter, err = NewFilter(P, key, contents) - if err != nil { - t.Fatalf("Filter build failed: %s", err.Error()) + defer func(t *testing.T, randKey [KeySize]byte) { + if t.Failed() { + t.Logf("random key: %x", randKey) + } + }(t, randKey) + + // contents1 defines a set of known elements for use in the tests below. + contents1 := [][]byte{[]byte("Alex"), []byte("Bob"), []byte("Charlie"), + []byte("Dick"), []byte("Ed"), []byte("Frank"), []byte("George"), + []byte("Harry"), []byte("Ilya"), []byte("John"), []byte("Kevin"), + []byte("Larry"), []byte("Michael"), []byte("Nate"), []byte("Owen"), + []byte("Paul"), []byte("Quentin"), + } + + // contents2 defines a separate set of known elements for use in the tests + // below. + contents2 := [][]byte{[]byte("Alice"), []byte("Betty"), + []byte("Charmaine"), []byte("Donna"), []byte("Edith"), []byte("Faina"), + []byte("Georgia"), []byte("Hannah"), []byte("Ilsbeth"), + []byte("Jennifer"), []byte("Kayla"), []byte("Lena"), []byte("Michelle"), + []byte("Natalie"), []byte("Ophelia"), []byte("Peggy"), []byte("Queenie"), + } + + tests := []struct { + name string // test description + p uint8 // collision probability + matchKey [KeySize]byte // random filter key for matches + contents [][]byte // data to include in the filter + wantMatches [][]byte // expected matches + fixedKey [KeySize]byte // fixed filter key for testing serialization + wantBytes string // expected serialized bytes + wantNBytes string // expected serialized bytes with N param + wantHash string // expected filter hash + }{{ + name: "empty filter", + p: 20, + matchKey: randKey, + contents: nil, + wantMatches: nil, + fixedKey: [KeySize]byte{}, + wantBytes: "", + wantNBytes: "", + wantHash: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "contents1 with P=20", + p: 20, + matchKey: randKey, + contents: contents1, + wantMatches: contents1, + fixedKey: [KeySize]byte{}, + wantBytes: "ce76b76760b54096a233d504ce55b80600fb072c74893cf306eb0c050f0b3c32e8c23436f8f5e67a986a46470790", + wantNBytes: "00000011ce76b76760b54096a233d504ce55b80600fb072c74893cf306eb0c050f0b3c32e8c23436f8f5e67a986a46470790", + wantHash: "a802fbe6f06991877cde8f3d770d8da8cf195816f04874cab045ffccaddd880d", + }, { + name: "contents1 with P=19", + p: 19, + matchKey: randKey, + contents: contents1, + wantMatches: contents1, + fixedKey: [KeySize]byte{}, + wantBytes: "2375937586050f0e9e19689983a3ab9b6f8f0cbc2f204b5233d5099ca0c9fbe9ec6a1f60e76fba3ad6835a28", + wantNBytes: "000000112375937586050f0e9e19689983a3ab9b6f8f0cbc2f204b5233d5099ca0c9fbe9ec6a1f60e76fba3ad6835a28", + wantHash: "be9ba34f03ced957e6f5c4d583ddfd34c136b486fbec2a42b4c7588a2d7813c1", + }, { + name: "contents2 with P=19", + p: 19, + matchKey: randKey, + contents: contents2, + wantMatches: contents2, + fixedKey: [KeySize]byte{}, + wantBytes: "4306259e36131a6c9bbd968a6c61dc110804d5ac91d20d6e9314a50332bffed877657c004e2366fcd34cda60", + wantNBytes: "000000114306259e36131a6c9bbd968a6c61dc110804d5ac91d20d6e9314a50332bffed877657c004e2366fcd34cda60", + wantHash: "dcbaf452f6de4c82ea506fa551d75876c4979ef388f785509b130de62eeaec23", + }, { + name: "contents2 with P=10", + p: 10, + matchKey: randKey, + contents: contents2, + wantMatches: contents2, + fixedKey: [KeySize]byte{}, + wantBytes: "1ca3aafb023074dc5bf2498df791b7d6e846e9f5016006d600", + wantNBytes: "000000111ca3aafb023074dc5bf2498df791b7d6e846e9f5016006d600", + wantHash: "afa181cd5c4b08eb9c16d1c97c95df1ca7b82e5e444a396cec5e02f2804fbd1a", + }} + + for _, test := range tests { + // Create a filter with the match key for all tests not related to + // testing serialization. + f, err := NewFilter(test.p, test.matchKey, test.contents) + if err != nil { + t.Errorf("%q: unexpected err: %v", test.name, err) + continue + } + + // Ensure the parameter values are returned properly. + resultP := f.P() + if resultP != test.p { + t.Errorf("%q: unexpected P -- got %d, want %d", test.name, + resultP, test.p) + continue + } + resultN := f.N() + if resultN != uint32(len(test.contents)) { + t.Errorf("%q: unexpected N -- got %d, want %d", test.name, + resultN, uint32(len(test.contents))) + continue + } + + // Ensure empty data never matches. + if f.Match(test.matchKey, nil) { + t.Errorf("%q: unexpected match of nil data", test.name) + continue + } + if f.MatchAny(test.matchKey, nil) { + t.Errorf("%q: unexpected match any of nil data", test.name) + continue + } + + // Ensure empty filter never matches data. + if len(test.contents) == 0 { + wantMiss := []byte("test") + if f.Match(test.matchKey, wantMiss) { + t.Errorf("%q: unexpected match of %q on empty filter", + test.name, wantMiss) + continue + } + if f.MatchAny(test.matchKey, [][]byte{wantMiss}) { + t.Errorf("%q: unexpected match any of %q on empty filter", + test.name, wantMiss) + continue + } + } + + // Ensure all of the expected matches occur individually. + for _, wantMatch := range test.wantMatches { + if !f.Match(test.matchKey, wantMatch) { + t.Errorf("%q: failed match for %q", test.name, wantMatch) + continue + } + } + + // Ensure a subset of the expected matches works in various orders when + // matching any. + if len(test.contents) > 0 { + // Create set of data to attempt to match such that only the final + // item is an element in the filter. + matches := make([][]byte, 0, len(test.contents)) + for _, data := range test.contents { + mutated := make([]byte, len(data)) + copy(mutated, data) + mutated[0] ^= 0x55 + matches = append(matches, mutated) + } + matches[len(matches)-1] = test.contents[len(test.contents)-1] + + if !f.MatchAny(test.matchKey, matches) { + t.Errorf("%q: failed match for %q", test.name, matches) + continue + } + + // Fisher-Yates shuffle the match set and test for matches again. + for i := 0; i < len(matches); i++ { + // Pick a number between current index and the end. + j := rand.Intn(len(matches)-i) + i + matches[i], matches[j] = matches[j], matches[i] + } + if !f.MatchAny(test.matchKey, matches) { + t.Errorf("%q: failed match for %q", test.name, matches) + continue + } + } + + // Recreate the filter with a fixed key for serialization testing. + fixedFilter, err := NewFilter(test.p, test.fixedKey, test.contents) + if err != nil { + t.Errorf("%q: unexpected err: %v", test.name, err) + continue + } + + // Parse the expected serialized bytes and ensure they match. + wantBytes, err := hex.DecodeString(test.wantBytes) + if err != nil { + t.Errorf("%q: unexpected err parsing want bytes hex: %v", test.name, + err) + continue + } + resultBytes := fixedFilter.Bytes() + if !bytes.Equal(resultBytes, wantBytes) { + t.Errorf("%q: mismatched bytes -- got %x, want %x", test.name, + resultBytes, wantBytes) + continue + } + + // Parse the expected serialized bytes that include the N parameter and + // ensure they match. + wantNBytes, err := hex.DecodeString(test.wantNBytes) + if err != nil { + t.Errorf("%q: unexpected err parsing want nbytes hex: %v", test.name, + err) + continue + } + resultNBytes := fixedFilter.NBytes() + if !bytes.Equal(resultNBytes, wantNBytes) { + t.Errorf("%q: mismatched bytes -- got %x, want %x", test.name, + resultNBytes, wantNBytes) + continue + } + + // Parse the expected hash and ensure it matches. + wantHash, err := chainhash.NewHashFromStr(test.wantHash) + if err != nil { + t.Errorf("%q: unexpected err parsing want hash hex: %v", test.name, + err) + continue + } + resultHash := fixedFilter.Hash() + if resultHash != *wantHash { + t.Errorf("%q: mismatched hash -- got %v, want %v", test.name, + resultHash, *wantHash) + continue + } + + // Deserialize the filter from bytes. + f2, err := FromBytes(uint32(len(test.contents)), test.p, wantBytes) + if err != nil { + t.Errorf("%q: unexpected err: %v", test.name, err) + continue + } + + // Ensure all of the expected matches occur on the deserialized filter. + for _, wantMatch := range test.wantMatches { + if !f2.Match(test.fixedKey, wantMatch) { + t.Errorf("%q: failed match for %q", test.name, wantMatch) + continue + } + } + + // Deserialize the filter from bytes with N parameter. + f3, err := FromNBytes(test.p, wantNBytes) + if err != nil { + t.Errorf("%q: unexpected err: %v", test.name, err) + continue + } + + // Ensure all of the expected matches occur on the deserialized filter. + for _, wantMatch := range test.wantMatches { + if !f3.Match(test.fixedKey, wantMatch) { + t.Errorf("%q: failed match for %q", test.name, wantMatch) + continue + } + } } } -// TestGCSFilterCopy deserializes and serializes a filter to create a copy. -func TestGCSFilterCopy(t *testing.T) { - filter2, err = FromBytes(filter.N(), P, filter.Bytes()) - if err != nil { - t.Fatalf("Filter copy failed: %s", err.Error()) - } - filter3, err = FromNBytes(filter.P(), filter.NBytes()) - if err != nil { - t.Fatalf("Filter copy failed: %s", err.Error()) - } -} - -// TestGCSFilterMetadata checks that the filter metadata is built and copied -// correctly. -func TestGCSFilterMetadata(t *testing.T) { - if filter.P() != P { - t.Fatal("P not correctly stored in filter metadata") - } - if filter.N() != uint32(len(contents)) { - t.Fatal("N not correctly stored in filter metadata") - } - if filter.P() != filter2.P() { - t.Fatal("P doesn't match between copied filters") - } - if filter.P() != filter3.P() { - t.Fatal("P doesn't match between copied filters") - } - if filter.N() != filter2.N() { - t.Fatal("N doesn't match between copied filters") - } - if filter.N() != filter3.N() { - t.Fatal("N doesn't match between copied filters") - } - if !bytes.Equal(filter.Bytes(), filter2.Bytes()) { - t.Fatal("Bytes don't match between copied filters") - } - if !bytes.Equal(filter.Bytes(), filter3.Bytes()) { - t.Fatal("Bytes don't match between copied filters") - } -} - -// TestGCSFilterMatch checks that both the built and copied filters match -// correctly, logging any false positives without failing on them. -func TestGCSFilterMatch(t *testing.T) { - if !filter.Match(key, []byte("Nate")) { - t.Fatal("Filter didn't match when it should have!") - } - if !filter2.Match(key, []byte("Nate")) { - t.Fatal("Filter didn't match when it should have!") - } - if !filter.Match(key, []byte("Quentin")) { - t.Fatal("Filter didn't match when it should have!") - } - if !filter2.Match(key, []byte("Quentin")) { - t.Fatal("Filter didn't match when it should have!") - } - if filter.Match(key, []byte("Nates")) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } - if filter2.Match(key, []byte("Nates")) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } - if filter.Match(key, []byte("Quentins")) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } - if filter2.Match(key, []byte("Quentins")) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } -} - -// TestGCSFilterMatchAny checks that both the built and copied filters match a -// list correctly, logging any false positives without failing on them. -func TestGCSFilterMatchAny(t *testing.T) { - if filter.MatchAny(key, contents2) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } - if filter2.MatchAny(key, contents2) { - t.Logf("False positive match, should be 1 in 2**%d!", P) - } - contents2 = append(contents2, []byte("Nate")) - if !filter.MatchAny(key, contents2) { - t.Fatal("Filter didn't match any when it should have!") - } - if !filter2.MatchAny(key, contents2) { - t.Fatal("Filter didn't match any when it should have!") - } -} - -// TestEmptyFilter ensures that empty filters are handled properly. -func TestEmptyFilter(t *testing.T) { - // Ensure an empty filter can be constructed without error. - f, err := NewFilter(P, key, nil) +// TestFilterMisses ensures the filter does not match entries with a rate that +// far exceeds the false positive rate. +func TestFilterMisses(t *testing.T) { + // Create a filter with the lowest supported false positive rate to reduce + // the chances of a false positive as much as possible. + var key [KeySize]byte + f, err := NewFilter(32, key, [][]byte{[]byte("entry")}) if err != nil { t.Fatalf("unexpected err: %v", err) } - // Ensure empty filters do not have any serialization. - gotBytes := f.Bytes() - if gotBytes != nil { - t.Fatalf("filter bytes not empty -- got %x", gotBytes) + // Since the filter may have false positives, try several queries and track + // how many matches there are. Something is very wrong if the filter + // matched multiple queries for data that are not in the filter with such a + // low false positive rate. + const numTries = 5 + var numMatches int + for i := uint8(0); i < numTries; i++ { + data := [1]byte{i} + if f.Match(key, data[:]) { + numMatches++ + } } - gotBytes = f.NBytes() - if gotBytes != nil { - t.Fatalf("filter nbytes not empty -- got %x", gotBytes) + if numMatches == numTries { + t.Fatalf("filter matched non-existing entries %d times", numMatches) } - // Ensure the hash of empty filters is all zeroes. - gotHash := f.Hash() - expectedHash := chainhash.Hash{} - if gotHash != expectedHash { - t.Fatalf("unexpected filter hash -- got %s, want %s", gotHash, - expectedHash) + // Try again with multi match. + numMatches = 0 + for i := uint8(0); i < numTries; i++ { + searchEntry := [1]byte{i} + data := [][]byte{searchEntry[:]} + if f.MatchAny(key, data[:]) { + numMatches++ + } } - - // Ensure an empty filter does not match empty data or arbitrary data. - if f.Match(key, nil) { - t.Fatal("unexpected match of nil data") - } - if f.Match(key, []byte("test")) { - t.Fatal("unexpected match of data") - } - if f.MatchAny(key, nil) { - t.Fatal("unexpected match of nil data") - } - if f.MatchAny(key, [][]byte{[]byte("test")}) { - t.Fatal("unexpected match of data") - } - - // Ensure empty filter returns correct parameters. - gotN := f.N() - if gotN != 0 { - t.Fatalf("unexpected N -- got %d, want %d", gotN, 0) - } - gotP := f.P() - if gotP != P { - t.Fatalf("unexpected P -- got %d, want %d", gotP, P) + if numMatches == numTries { + t.Fatalf("filter matched non-existing entries %d times", numMatches) + } +} + +// TestFilterCorners ensures a few negative corner cases such as specifying +// parameters that are too large behave as expected. +func TestFilterCorners(t *testing.T) { + // Attempt to construct filter with parameters too large. + const largeP = 33 + var key [KeySize]byte + _, err := NewFilter(largeP, key, nil) + if err != ErrPTooBig { + t.Fatalf("did not receive expected err for P too big -- got %v, want %v", + err, ErrPTooBig) + } + _, err = FromBytes(0, largeP, nil) + if err != ErrPTooBig { + t.Fatalf("did not receive expected err for P too big -- got %v, want %v", + err, ErrPTooBig) + } + + // Attempt to decode a filter without the N value serialized properly. + _, err = FromNBytes(20, []byte{0x00}) + if err != ErrMisserialized { + t.Fatalf("did not receive expected err -- got %v, want %v", + err, ErrMisserialized) } } diff --git a/gcs/gcsbench_test.go b/gcs/gcsbench_test.go deleted file mode 100644 index 74362e57..00000000 --- a/gcs/gcsbench_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2016-2017 The btcsuite developers -// Copyright (c) 2016-2017 The Lightning Network Developers -// Copyright (c) 2018 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package gcs - -import ( - "encoding/binary" - "math/rand" - "testing" -) - -var globalMatch bool - -func genRandFilterElements(numElements uint) ([][]byte, error) { - testContents := make([][]byte, numElements) - for i := range contents { - randElem := make([]byte, 32) - if _, err := rand.Read(randElem); err != nil { - return nil, err - } - testContents[i] = randElem - } - - return testContents, nil -} - -// BenchmarkGCSFilterBuild benchmarks building a filter. -func BenchmarkGCSFilterBuild50000(b *testing.B) { - b.StopTimer() - var testKey [KeySize]byte - for i := 0; i < KeySize; i += 4 { - binary.BigEndian.PutUint32(testKey[i:], rand.Uint32()) - } - randFilterElems, genErr := genRandFilterElements(50000) - if err != nil { - b.Fatalf("unable to generate random item: %v", genErr) - } - b.StartTimer() - - for i := 0; i < b.N; i++ { - _, err := NewFilter(P, key, randFilterElems) - if err != nil { - b.Fatalf("unable to generate filter: %v", err) - } - } -} - -// BenchmarkGCSFilterBuild benchmarks building a filter. -func BenchmarkGCSFilterBuild100000(b *testing.B) { - var testKey [KeySize]byte - for i := 0; i < KeySize; i += 4 { - binary.BigEndian.PutUint32(testKey[i:], rand.Uint32()) - } - randFilterElems, genErr := genRandFilterElements(100000) - if err != nil { - b.Fatalf("unable to generate random item: %v", genErr) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, err := NewFilter(P, key, randFilterElems) - if err != nil { - b.Fatalf("unable to generate filter: %v", err) - } - } -} - -// BenchmarkGCSFilterMatch benchmarks querying a filter for a single value. -func BenchmarkGCSFilterMatch(b *testing.B) { - filter, err := NewFilter(P, key, contents) - if err != nil { - b.Fatalf("Failed to build filter") - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - globalMatch = filter.Match(key, []byte("Nate")) - globalMatch = filter.Match(key, []byte("Nates")) - } -} - -// BenchmarkGCSFilterMatchAny benchmarks querying a filter for a list of -// values. -func BenchmarkGCSFilterMatchAny(b *testing.B) { - filter, err := NewFilter(P, key, contents) - if err != nil { - b.Fatalf("Failed to build filter") - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - globalMatch = filter.MatchAny(key, contents2) - } -}