├── java ├── .gitignore ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── peterrk │ │ │ └── pbf │ │ │ ├── PageBloomFilterTest.java │ │ │ ├── HashTest.java │ │ │ └── BloomFilterBenchmark.java │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── peterrk │ │ └── pbf │ │ ├── Hash.java │ │ └── PageBloomFilter.java └── pom.xml ├── go ├── .gitignore ├── go-inject.sh ├── wash-asm.py ├── stub_amd64.s ├── pbf_test.go ├── stub.go ├── go-inject.py ├── pbf.go └── impl.go ├── python ├── .gitignore ├── setup.py ├── bind.c └── pbf.py ├── go.mod ├── images ├── byte.png ├── ratio.png ├── U7-155H.png ├── EPYC-7K83.png ├── EPYC-9T24.png ├── Xeon-8374C.png ├── Xeon-8475B.png └── i7-10710U.png ├── rust ├── Cargo.toml └── src │ ├── lib.rs │ ├── hash.rs │ └── pbf.rs ├── csharp ├── PageBloomFilter │ ├── PageBloomFilter.csproj │ ├── Hash.cs │ └── PageBloomFilter.cs ├── PageBloomFilter.Benchmark │ ├── PageBloomFilter.Benchmark.csproj │ └── Program.cs ├── PageBloomFilter.Tests │ ├── PageBloomFilter.Tests.csproj │ ├── PageBloomFilterTest.cs │ └── HashTest.cs └── PageBloomFilter.sln ├── test ├── set-wrapper.cc ├── set-wrapper.h ├── set-bench.cc ├── bench.cc ├── bench.sh ├── test.cc └── peer-bench.cc ├── src ├── hash.h ├── pbf-c.cc ├── pbf-internal.h ├── hash.cc ├── pbf.cc └── aesni-hash.h ├── CMakeLists.txt ├── include ├── pbf-c.h └── pbf.h ├── LICENSE ├── README-CN.md └── README.md /java/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /go/.gitignore: -------------------------------------------------------------------------------- 1 | *.asm 2 | *.txt 3 | *.o 4 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | __pycache__/* 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PeterRK/PageBloomFilter 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /images/byte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/byte.png -------------------------------------------------------------------------------- /images/ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/ratio.png -------------------------------------------------------------------------------- /images/U7-155H.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/U7-155H.png -------------------------------------------------------------------------------- /images/EPYC-7K83.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/EPYC-7K83.png -------------------------------------------------------------------------------- /images/EPYC-9T24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/EPYC-9T24.png -------------------------------------------------------------------------------- /images/Xeon-8374C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/Xeon-8374C.png -------------------------------------------------------------------------------- /images/Xeon-8475B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/Xeon-8475B.png -------------------------------------------------------------------------------- /images/i7-10710U.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeterRK/PageBloomFilter/HEAD/images/i7-10710U.png -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pbf" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | doctest = false 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter/PageBloomFilter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | AnyCPU;x64 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/set-wrapper.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "set-wrapper.h" 6 | 7 | bool Set::test(const std::string& key) const noexcept { 8 | return m_core.find(key) != m_core.end(); 9 | } 10 | 11 | void Set::set(const std::string& key) noexcept { 12 | m_core.insert(key); 13 | } -------------------------------------------------------------------------------- /test/set-wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "robin_hood.h" 9 | 10 | class Set final { 11 | public: 12 | explicit Set(size_t size) { 13 | m_core.reserve(size); 14 | } 15 | 16 | bool test(const std::string& key) const noexcept; 17 | void set(const std::string& key) noexcept; 18 | 19 | private: 20 | robin_hood::unordered_flat_set m_core; 21 | }; -------------------------------------------------------------------------------- /src/hash.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #pragma once 6 | #ifndef PAGE_BLOOM_FILTER_HASH_H 7 | #define PAGE_BLOOM_FILTER_HASH_H 8 | 9 | #include 10 | 11 | namespace pbf { 12 | 13 | #define FORCE_INLINE inline __attribute__((always_inline)) 14 | 15 | struct V128 { 16 | uint64_t l; 17 | uint64_t h; 18 | }; 19 | 20 | 21 | extern V128 Hash(const uint8_t* msg, unsigned len) noexcept; 22 | 23 | } //pbf 24 | #endif // PAGE_BLOOM_FILTER_HASH_H -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # Copyright (c) 2023, Ruan Kunliang. 3 | # Use of this source code is governed by a BSD-style 4 | # license that can be found in the LICENSE file. 5 | 6 | import os 7 | from distutils.core import setup, Extension 8 | 9 | base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 10 | 11 | pbf = Extension('_pbf', 12 | include_dirs=[base_dir+'/include'], 13 | sources=['bind.c', base_dir+'/src/pbf-c.cc', base_dir+'/src/hash.cc']) 14 | 15 | setup(name='pbf', version='0.1', description='Page Bloom Filter', 16 | py_modules=['pbf'], ext_modules=[pbf]) 17 | -------------------------------------------------------------------------------- /go/go-inject.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clang -O3 -std=c++14 -march=native \ 4 | -fno-asynchronous-unwind-tables -fno-exceptions \ 5 | -DC_ALL_IN_ONE -S -fPIE -I../include ../src/pbf-c.cc -o origin.asm && \ 6 | ./wash-asm.py origin.asm washed.asm && \ 7 | as washed.asm -o inject.o && \ 8 | objdump -s --section=.rodata.func_size inject.o >func-size.txt && \ 9 | objdump -s --section=.text inject.o >func-data.txt && \ 10 | objdump -d --section=.text inject.o >func-code.txt && \ 11 | ./go-inject.py func-size.txt func-data.txt func-code.txt inject_amd64.s && \ 12 | rm origin.asm washed.asm inject.o func-size.txt func-data.txt func-code.txt 13 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter.Benchmark/PageBloomFilter.Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | AnyCPU;x64 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Ruan Kunliang. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | cmake_minimum_required(VERSION 3.10) 6 | project(PageBloomFilter) 7 | 8 | set(CMAKE_CXX_STANDARD 14) 9 | 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") 11 | 12 | include_directories(include) 13 | 14 | add_library(pbf SHARED src/pbf.cc src/hash.cc) 15 | 16 | add_executable(test test/test.cc src/pbf.cc src/hash.cc) 17 | target_link_libraries(test gtest pthread) 18 | 19 | add_executable(bench test/bench.cc src/pbf.cc src/hash.cc) 20 | 21 | #include_directories(3rd-party/include) 22 | #link_directories(3rd-party/lib) 23 | 24 | #add_executable(set-bench test/set-bench.cc test/set-wrapper.cc) 25 | 26 | #add_executable(peer-bench test/peer-bench.cc) 27 | #target_link_libraries(peer-bench bloom pbf bf) -------------------------------------------------------------------------------- /include/pbf-c.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #pragma once 6 | #ifndef PAGE_BLOOM_FILTER_C_H 7 | #define PAGE_BLOOM_FILTER_C_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #define PAGE_BLOOM_FILTER_FUNC(way) \ 18 | extern bool PBF##way##_Set(void* space, unsigned page_level, unsigned page_num, const void* key, unsigned len); \ 19 | extern bool PBF##way##_Test(const void* space, unsigned page_level, unsigned page_num, const void* key, unsigned len); 20 | 21 | PAGE_BLOOM_FILTER_FUNC(4) 22 | PAGE_BLOOM_FILTER_FUNC(5) 23 | PAGE_BLOOM_FILTER_FUNC(6) 24 | PAGE_BLOOM_FILTER_FUNC(7) 25 | PAGE_BLOOM_FILTER_FUNC(8) 26 | 27 | #undef PAGE_BLOOM_FILTER_FUNC 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | #endif //PAGE_BLOOM_FILTER_C_H -------------------------------------------------------------------------------- /csharp/PageBloomFilter.Tests/PageBloomFilter.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | AnyCPU;x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/set-bench.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include "set-wrapper.h" 8 | 9 | int main(int argc, char* argv[]) { 10 | const uint64_t n = 1000000; 11 | Set set(n/2); 12 | std::string key; 13 | key.resize(8); 14 | uint64_t& num = *reinterpret_cast(&key[0]); 15 | 16 | auto start = std::chrono::steady_clock::now(); 17 | for (uint64_t i = 0; i < n; i+=2) { 18 | num = i; 19 | set.set(key); 20 | } 21 | auto end = std::chrono::steady_clock::now(); 22 | auto delta = std::chrono::duration_cast(end - start).count(); 23 | std::cout << "set: " << static_cast(delta)/(n/2) << "ns/op" << std::endl; 24 | 25 | start = std::chrono::steady_clock::now(); 26 | for (uint64_t i = 0; i < n; i++) { 27 | num = i; 28 | set.test(key); 29 | } 30 | end = std::chrono::steady_clock::now(); 31 | 32 | delta = std::chrono::duration_cast(end - start).count(); 33 | std::cout << "test: " << static_cast(delta)/n << "ns/op" << std::endl; 34 | return 0; 35 | } -------------------------------------------------------------------------------- /test/bench.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include "pbf.h" 8 | 9 | int main(int argc, char* argv[]) { 10 | #ifndef BENCHMARK_WAY 11 | #define BENCHMARK_WAY 8 12 | #endif 13 | pbf::PageBloomFilter< BENCHMARK_WAY > bf(12, 250); 14 | 15 | const uint64_t n = 1000000; 16 | 17 | auto start = std::chrono::steady_clock::now(); 18 | for (uint64_t i = 0; i < n; i += 2) { 19 | bf.set(reinterpret_cast(&i), 8); 20 | } 21 | auto end = std::chrono::steady_clock::now(); 22 | auto delta = std::chrono::duration_cast(end - start).count(); 23 | std::cout << "set: " << static_cast(delta)/(n/2) << "ns/op" << std::endl; 24 | 25 | start = std::chrono::steady_clock::now(); 26 | for (uint64_t i = 0; i < n; i++) { 27 | bf.test(reinterpret_cast(&i), 8); 28 | } 29 | end = std::chrono::steady_clock::now(); 30 | 31 | delta = std::chrono::duration_cast(end - start).count(); 32 | std::cout << "test: " << static_cast(delta)/n << "ns/op" << std::endl; 33 | return 0; 34 | } -------------------------------------------------------------------------------- /test/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | COMPILE="clang++ -O3 -std=c++14 -march=native -I../include" 4 | 5 | echo "robin-hood-set" 6 | ${COMPILE} set-wrapper.cc set-bench.cc 7 | for ((i=0;i<10;i++)); do 8 | ./a.out 9 | done 10 | echo "" 11 | 12 | SOURCE="../src/hash.cc ../src/pbf.cc bench.cc" 13 | 14 | for w in 4 5 6 7 8; do 15 | echo "way-${w}" 16 | ${COMPILE} -DBENCHMARK_WAY=${w} -DDISABLE_SIMD_OPTIMIZE ${SOURCE} 17 | for ((i=0;i<10;i++)); do 18 | ./a.out 19 | done 20 | echo "" 21 | done 22 | 23 | for w in 4 5 6 7 8; do 24 | echo "way-${w}-xx" 25 | ${COMPILE} -DBENCHMARK_WAY=${w} -DDISABLE_SIMD_OPTIMIZE -DUSE_XXHASH ${SOURCE} 26 | for ((i=0;i<10;i++)); do 27 | ./a.out 28 | done 29 | echo "" 30 | done 31 | 32 | for w in 5 6 7 8; do 33 | ${COMPILE} -DBENCHMARK_WAY=${w} ${SOURCE} 34 | echo "way-simd-${w}" 35 | for ((i=0;i<10;i++)); do 36 | ./a.out 37 | done 38 | echo "" 39 | done 40 | 41 | for w in 5 6 7 8; do 42 | ${COMPILE} -DBENCHMARK_WAY=${w} -DUSE_XXHASH ${SOURCE} 43 | echo "way-simd-${w}-xx" 44 | for ((i=0;i<10;i++)); do 45 | ./a.out 46 | done 47 | echo "" 48 | done 49 | 50 | for w in 5 6 7 8; do 51 | ${COMPILE} -DBENCHMARK_WAY=${w} -DUSE_AESNI_HASH ${SOURCE} 52 | echo "way-simd-${w}-aesni" 53 | for ((i=0;i<10;i++)); do 54 | ./a.out 55 | done 56 | echo "" 57 | done 58 | 59 | rm a.out -------------------------------------------------------------------------------- /csharp/PageBloomFilter.Tests/PageBloomFilterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | using NUnit.Framework; 6 | 7 | namespace PageBloomFilter.Tests { 8 | public class PageBloomFilterTest { 9 | 10 | [Test] 11 | public void CreateTest() { 12 | var bf = PageBloomFilter.New(500, 0.01); 13 | Assert.That(bf.Way, Is.EqualTo(7)); 14 | Assert.That(bf.PageLevel, Is.EqualTo(7)); 15 | Assert.That(bf.Data.Length, Is.EqualTo(640)); 16 | } 17 | 18 | private static void DoTest(int way) { 19 | PageBloomFilter bf = PageBloomFilter.New(way, 7, 3); 20 | var key = new Span(new byte[8]); 21 | for (long i = 0; i < 200; i++) { 22 | BitConverter.TryWriteBytes(key, i); 23 | Assert.That(bf.Set(key), Is.True); 24 | } 25 | for (long i = 0; i < 200; i++) { 26 | BitConverter.TryWriteBytes(key, i); 27 | Assert.That(bf.Test(key), Is.True); 28 | } 29 | for (long i = 200; i < 400; i++) { 30 | BitConverter.TryWriteBytes(key, i); 31 | Assert.That(bf.Test(key), Is.False); 32 | } 33 | } 34 | 35 | [Test] 36 | public void OperateTest() { 37 | for (int i = 4; i <= 8; i++) { 38 | DoTest(i); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Ruan Kunliang 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/pbf-c.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "pbf-c.h" 6 | #include "pbf-internal.h" 7 | #ifdef C_ALL_IN_ONE 8 | #include "hash.cc" 9 | #endif 10 | 11 | extern "C" { 12 | 13 | #define PAGE_BLOOM_FILTER_FUNC(way) \ 14 | bool PBF##way##_Set(void* space, unsigned page_level, unsigned page_num, \ 15 | const void* key, unsigned len) { \ 16 | pbf::V128X t; \ 17 | t.v = pbf::Hash((const uint8_t*)key, len); \ 18 | size_t idx = PageHash(t) % page_num; \ 19 | auto page = ((uint8_t*)space) + (idx << page_level); \ 20 | return pbf::Set< way >(page, page_level, t); \ 21 | } \ 22 | bool PBF##way##_Test(const void* space, unsigned page_level, unsigned page_num, \ 23 | const void* key, unsigned len) { \ 24 | pbf::V128X t; \ 25 | t.v = pbf::Hash((const uint8_t*)key, len); \ 26 | size_t idx = PageHash(t) % page_num; \ 27 | auto page = ((uint8_t*)space) + (idx << page_level); \ 28 | return pbf::Test< way >(page, page_level, t); \ 29 | } 30 | 31 | PAGE_BLOOM_FILTER_FUNC(4) 32 | PAGE_BLOOM_FILTER_FUNC(5) 33 | PAGE_BLOOM_FILTER_FUNC(6) 34 | PAGE_BLOOM_FILTER_FUNC(7) 35 | PAGE_BLOOM_FILTER_FUNC(8) 36 | 37 | #undef PAGE_BLOOM_FILTER_FUNC 38 | } -------------------------------------------------------------------------------- /java/src/test/java/com/github/peterrk/pbf/PageBloomFilterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.github.peterrk.pbf; 6 | 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.Assertions; 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | 12 | 13 | public class PageBloomFilterTest { 14 | 15 | @Test 16 | public void createTest() { 17 | PageBloomFilter bf = PageBloomFilter.New(500, 0.01); 18 | Assertions.assertEquals(7, bf.getWay()); 19 | Assertions.assertEquals(7, bf.getPageLevel()); 20 | Assertions.assertEquals(640, bf.getData().length); 21 | } 22 | 23 | 24 | @Test 25 | public void operateTest() { 26 | for (int i = 4; i <= 8; i++) { 27 | test(i); 28 | } 29 | } 30 | 31 | private static void intToKey(long num, byte[] buf) { 32 | ByteBuffer bb = ByteBuffer.wrap(buf); 33 | bb.order(ByteOrder.LITTLE_ENDIAN); 34 | bb.putLong(num); 35 | } 36 | 37 | private void test(int way) { 38 | PageBloomFilter bf = PageBloomFilter.New(way, 7, 3); 39 | byte[] key = new byte[8]; 40 | for (long i = 0; i < 200; i++) { 41 | intToKey(i, key); 42 | Assertions.assertTrue(bf.set(key)); 43 | } 44 | for (long i = 0; i < 200; i++) { 45 | intToKey(i, key); 46 | Assertions.assertTrue(bf.test(key)); 47 | } 48 | for (long i = 200; i < 400; i++) { 49 | intToKey(i, key); 50 | Assertions.assertFalse(bf.test(key)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include 6 | #include "pbf.h" 7 | 8 | int main(int argc,char **argv){ 9 | testing::InitGoogleTest(&argc,argv); 10 | return RUN_ALL_TESTS(); 11 | } 12 | 13 | TEST(PBF, New) { 14 | auto bf = NEW_BLOOM_FILTER(500, 0.01); 15 | ASSERT_FALSE(!bf); 16 | ASSERT_EQ(7, bf.way()); 17 | ASSERT_EQ(7, bf.page_level()); 18 | ASSERT_EQ(640, bf.data_size()); 19 | auto p = pbf::New(500, 0.01); 20 | ASSERT_NE(nullptr, p); 21 | ASSERT_FALSE(!*p); 22 | ASSERT_EQ(7, p->way()); 23 | ASSERT_EQ(7, p->page_level()); 24 | ASSERT_EQ(640, p->data_size()); 25 | } 26 | 27 | template 28 | void DoTest() { 29 | pbf::PageBloomFilter bf(7, 3); 30 | ASSERT_FALSE(!bf); 31 | ASSERT_GE(bf.capacity(), 384); 32 | 33 | for (uint64_t i = 0; i < 200; i++) { 34 | ASSERT_TRUE(bf.set(reinterpret_cast(&i), 8)); 35 | } 36 | ASSERT_EQ(bf.unique_cnt(), 200); 37 | 38 | for (uint64_t i = 0; i < 200; i++) { 39 | ASSERT_TRUE(bf.test(reinterpret_cast(&i), 8)); 40 | } 41 | for (uint64_t i = 200; i < 400; i++) { 42 | ASSERT_FALSE(bf.test(reinterpret_cast(&i), 8)); 43 | } 44 | } 45 | 46 | TEST(PBF, X8) { DoTest<8>(); } 47 | TEST(PBF, X7) { DoTest<7>(); } 48 | TEST(PBF, X6) { DoTest<6>(); } 49 | TEST(PBF, X5) { DoTest<5>(); } 50 | TEST(PBF, X4) { DoTest<4>(); } 51 | 52 | TEST(PBF, Copy) { 53 | auto bf = pbf::New(500, 0.01); 54 | for (uint64_t i = 0; i < 200; i++) { 55 | ASSERT_TRUE(bf->set(reinterpret_cast(&i), 8)); 56 | } 57 | auto copy = pbf::New(bf->way(), bf->page_level(), bf->data(), bf->data_size(), bf->unique_cnt()); 58 | for (uint64_t i = 0; i < 200; i++) { 59 | ASSERT_TRUE(copy->test(reinterpret_cast(&i), 8)); 60 | } 61 | for (uint64_t i = 200; i < 400; i++) { 62 | ASSERT_TRUE(copy->set(reinterpret_cast(&i), 8)); 63 | } 64 | for (uint64_t i = 200; i < 400; i++) { 65 | ASSERT_FALSE(bf->test(reinterpret_cast(&i), 8)); 66 | } 67 | } -------------------------------------------------------------------------------- /python/bind.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #define PY_SSIZE_T_CLEAN 6 | #include 7 | #include 8 | #include "pbf-c.h" 9 | 10 | #define DEFINE_PY_FUNC(way) \ 11 | static PyObject* set##way(PyObject *self, PyObject *args) { \ 12 | FETCH_ARGS \ 13 | return PyBool_FromLong(PBF##way##_Set(space.buf, page_level, space.len>>page_level, key, key_len)); \ 14 | } \ 15 | static PyObject* test##way(PyObject *self, PyObject *args) { \ 16 | FETCH_ARGS \ 17 | return PyBool_FromLong(PBF##way##_Test(space.buf, page_level, space.len>>page_level, key, key_len)); \ 18 | } 19 | 20 | #define FETCH_ARGS \ 21 | Py_buffer space; \ 22 | unsigned page_level; \ 23 | const uint8_t* key; \ 24 | Py_ssize_t key_len; \ 25 | if (!PyArg_ParseTuple(args, "y*Is#", \ 26 | &space, &page_level, &key, &key_len)) return NULL; 27 | 28 | DEFINE_PY_FUNC(4) 29 | DEFINE_PY_FUNC(5) 30 | DEFINE_PY_FUNC(6) 31 | DEFINE_PY_FUNC(7) 32 | DEFINE_PY_FUNC(8) 33 | 34 | 35 | static PyObject* clear(PyObject *self, PyObject *args) { 36 | Py_buffer space; 37 | if (!PyArg_ParseTuple(args, "y*", &space)) return NULL; 38 | memset(space.buf, 0, space.itemsize * space.len); 39 | Py_RETURN_NONE; 40 | } 41 | 42 | static PyMethodDef funcs[] = { 43 | {"clear", clear, METH_VARARGS, "clear buffer"}, 44 | {"set4", set4, METH_VARARGS, "4-way pbf set"}, 45 | {"test4", test4, METH_VARARGS, "4-way pbf test"}, 46 | {"set5", set5, METH_VARARGS, "5-way pbf set"}, 47 | {"test5", test5, METH_VARARGS, "5-way pbf test"}, 48 | {"set6", set6, METH_VARARGS, "6-way pbf set"}, 49 | {"test6", test6, METH_VARARGS, "6-way pbf test"}, 50 | {"set7", set7, METH_VARARGS, "7-way pbf set"}, 51 | {"test7", test7, METH_VARARGS, "7-way pbf test"}, 52 | {"set8", set8, METH_VARARGS, "8-way pbf set"}, 53 | {"test8", test8, METH_VARARGS, "8-way pbf test"}, 54 | {NULL, NULL, 0, NULL} // Sentinel 55 | }; 56 | 57 | static struct PyModuleDef module = { 58 | PyModuleDef_HEAD_INIT, 59 | "_pbf", // name of module 60 | NULL, // module documentation, may be NULL 61 | -1, /* size of per-interpreter state of the module, 62 | or -1 if the module keeps state in global variables. */ 63 | funcs 64 | }; 65 | 66 | PyMODINIT_FUNC PyInit__pbf(void) { return PyModule_Create(&module); } 67 | -------------------------------------------------------------------------------- /src/pbf-internal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #ifndef DISABLE_SIMD_OPTIMIZE 6 | #include 7 | #endif 8 | #include "hash.h" 9 | 10 | namespace pbf { 11 | 12 | union V128X { 13 | V128 v; 14 | uint32_t w[4]; 15 | uint16_t s[8]; 16 | #ifndef DISABLE_SIMD_OPTIMIZE 17 | __m128i m; 18 | #endif 19 | }; 20 | 21 | static FORCE_INLINE uint32_t Rot32(uint32_t x, unsigned k) noexcept { 22 | return (x << k) | (x >> (32U - k)); 23 | } 24 | 25 | static FORCE_INLINE uint32_t PageHash(V128X t) noexcept { 26 | return Rot32(t.w[0], 8) ^ Rot32(t.w[1], 6) ^ Rot32(t.w[2], 4) ^ Rot32(t.w[3], 2); 27 | } 28 | 29 | template 30 | static FORCE_INLINE bool Test(const uint8_t* page, unsigned page_level, V128X t) noexcept { 31 | #if defined(__AVX2__) && !defined(DISABLE_SIMD_OPTIMIZE) 32 | if (N > 4) { 33 | __m256i hole = _mm256_set1_epi32(-1); 34 | if (N == 7) { 35 | hole = _mm256_set_epi32(0, -1, -1, -1, -1, -1, -1, -1); 36 | } else if (N == 6) { 37 | hole = _mm256_set_epi32(0, -1, -1, -1, 0, -1, -1, -1); 38 | } else if (N == 5) { 39 | hole = _mm256_set_epi32(0, 0, -1, -1, 0, -1, -1, -1); 40 | } 41 | __m256i mask = _mm256_set1_epi32((1U << (page_level+3U)) - 1); 42 | __m256i idx = _mm256_and_si256(_mm256_setr_m128i(t.m, _mm_srli_epi32(t.m, 16)), mask); 43 | __m256i rec = _mm256_mask_i32gather_epi32(_mm256_set1_epi32(-1), reinterpret_cast(page), 44 | _mm256_srli_epi32(idx, 5U), hole, 4); 45 | __m256i bit = _mm256_sllv_epi32(_mm256_set1_epi32(1), _mm256_and_si256(idx, _mm256_set1_epi32(31))); 46 | return _mm256_testz_si256(_mm256_andnot_si256(rec, bit), bit); 47 | } 48 | #endif 49 | uint16_t mask = (1U << (page_level+3U)) - 1U; 50 | for (unsigned i = 0; i < N; i++) { 51 | uint16_t idx = t.s[i] & mask; 52 | uint8_t bit = 1U << (idx&7); 53 | if ((page[idx>>3U] & bit) == 0) { 54 | return false; 55 | } 56 | } 57 | return true; 58 | } 59 | 60 | template 61 | static FORCE_INLINE bool Set(uint8_t* page, unsigned page_level, V128X t) noexcept { 62 | uint8_t hit = 1U; 63 | uint16_t mask = (1U << (page_level+3U)) - 1U; 64 | for (unsigned i = 0; i < N; i++) { 65 | uint16_t idx = t.s[i] & mask; 66 | uint8_t bit = 1U << (idx&7); 67 | hit &= page[idx>>3U] >> (idx&7); 68 | page[idx>>3U] |= bit; 69 | } 70 | return !hit; 71 | } 72 | 73 | } //pbf -------------------------------------------------------------------------------- /go/wash-asm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import re 4 | import sys 5 | 6 | 7 | _PTN_FUNC_BEGIN = re.compile('.*# -- Begin function') 8 | _PTN_FUNC_END = re.compile('.*# -- End function') 9 | _PTN_TEXT = re.compile('\s*\.text') 10 | _PTN_SECTION = re.compile('\s*\.section\s+') 11 | _PTN_GLOBAL_SYMBOL = re.compile('\s*\.globl\s+(\w+)') 12 | _PTN_FUNC_SIZE = re.compile('\s*\.size\s+(\w+),\s*([\w\.]+-\w+)') 13 | 14 | 15 | def main(src, dest): 16 | parts = [] 17 | func = None 18 | with open(src, 'r') as f: 19 | for line in f: 20 | line = line.strip('\n') 21 | if _PTN_FUNC_BEGIN.match(line) is not None: 22 | assert func is None 23 | func = [line] 24 | continue 25 | if func is None: 26 | continue 27 | func.append(line) 28 | if _PTN_FUNC_END.match(line) is not None: 29 | parts.append(func) 30 | func = None 31 | 32 | for i in range(len(parts)): 33 | text = True 34 | code = None 35 | other = [] 36 | for line in parts[i]: 37 | if _PTN_TEXT.match(line) is not None: 38 | text = True 39 | continue 40 | if _PTN_SECTION.match(line) is not None: 41 | text = False 42 | continue 43 | 44 | if code is None: 45 | m = _PTN_GLOBAL_SYMBOL.match(line) 46 | if m is not None: 47 | name = m.groups()[0] 48 | code = [] 49 | continue 50 | other.append(line) 51 | continue 52 | 53 | if not text: 54 | other.append(line) 55 | continue 56 | 57 | m = _PTN_FUNC_SIZE.match(line) 58 | if m is not None: 59 | target, size = m.groups() 60 | continue 61 | code.append(line) 62 | 63 | assert target == name 64 | parts[i] = (code, other, size) 65 | 66 | with open(dest, 'w') as f: 67 | print(' .section .rodata.func_size,"aM",@progbits,4', file=f) 68 | print(' .p2align 2', file=f) 69 | for i in range(len(parts)): 70 | _1, _2, size = parts[i] 71 | print(' .long ' + size, file=f) 72 | off = size.find('-') 73 | print(' .long .L__END__{}{}'.format(i, size[off:]), file=f) 74 | print('#===================================', file=f) 75 | print(' .text', file=f) 76 | print(' .p2align 5, 0xcc', file=f) 77 | for i in range(len(parts)): 78 | code, other, _ = parts[i] 79 | for line in code: 80 | print(line, file=f) 81 | for line in other: 82 | print(line, file=f) 83 | print('.L__END__{}:'.format(i), file=f) 84 | print('#===================================', file=f) 85 | print(' .p2align 5, 0xcc', file=f) 86 | 87 | 88 | if __name__ == '__main__': 89 | if len(sys.argv) != 3: 90 | print('usage: {} input output'.format(sys.argv[0])) 91 | quit(1) 92 | main(sys.argv[1], sys.argv[2]) -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.peterrk 8 | pbf 9 | 0.1 10 | 11 | 12 | 8 13 | 8 14 | UTF-8 15 | 16 | 17 | 18 | 19 | org.junit.jupiter 20 | junit-jupiter 21 | 5.9.2 22 | test 23 | 24 | 25 | org.openjdk.jmh 26 | jmh-core 27 | 1.37 28 | test 29 | 30 | 31 | org.openjdk.jmh 32 | jmh-generator-annprocess 33 | 1.37 34 | test 35 | 36 | 37 | com.google.guava 38 | guava 39 | 32.0.0-jre 40 | test 41 | 42 | 43 | com.github.alexandrnikitin 44 | bloom-filter_2.13 45 | 0.13.1 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 3.8.0 56 | 57 | 58 | -Xlint 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-surefire-plugin 66 | 3.0.0 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /go/stub_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·pbf4Set(SB), NOSPLIT, $120 8 | MOVQ space+0(FP), DI 9 | MOVQ pageLevel+8(FP), SI 10 | MOVQ pageNum+16(FP), DX 11 | MOVQ key+24(FP), CX 12 | MOVQ key_len+32(FP), R8 13 | CALL PBF4_Set(SB) 14 | MOVB AX, ret+40(FP) 15 | RET 16 | 17 | TEXT ·pbf4Test(SB), NOSPLIT, $120 18 | MOVQ space+0(FP), DI 19 | MOVQ pageLevel+8(FP), SI 20 | MOVQ pageNum+16(FP), DX 21 | MOVQ key+24(FP), CX 22 | MOVQ key_len+32(FP), R8 23 | CALL PBF4_Test(SB) 24 | MOVB AX, ret+40(FP) 25 | RET 26 | 27 | TEXT ·pbf5Set(SB), NOSPLIT, $120 28 | MOVQ space+0(FP), DI 29 | MOVQ pageLevel+8(FP), SI 30 | MOVQ pageNum+16(FP), DX 31 | MOVQ key+24(FP), CX 32 | MOVQ key_len+32(FP), R8 33 | CALL PBF5_Set(SB) 34 | MOVB AX, ret+40(FP) 35 | RET 36 | 37 | TEXT ·pbf5Test(SB), NOSPLIT, $120 38 | MOVQ space+0(FP), DI 39 | MOVQ pageLevel+8(FP), SI 40 | MOVQ pageNum+16(FP), DX 41 | MOVQ key+24(FP), CX 42 | MOVQ key_len+32(FP), R8 43 | CALL PBF5_Test(SB) 44 | MOVB AX, ret+40(FP) 45 | RET 46 | 47 | TEXT ·pbf6Set(SB), NOSPLIT, $120 48 | MOVQ space+0(FP), DI 49 | MOVQ pageLevel+8(FP), SI 50 | MOVQ pageNum+16(FP), DX 51 | MOVQ key+24(FP), CX 52 | MOVQ key_len+32(FP), R8 53 | CALL PBF6_Set(SB) 54 | MOVB AX, ret+40(FP) 55 | RET 56 | 57 | TEXT ·pbf6Test(SB), NOSPLIT, $120 58 | MOVQ space+0(FP), DI 59 | MOVQ pageLevel+8(FP), SI 60 | MOVQ pageNum+16(FP), DX 61 | MOVQ key+24(FP), CX 62 | MOVQ key_len+32(FP), R8 63 | CALL PBF6_Test(SB) 64 | MOVB AX, ret+40(FP) 65 | RET 66 | 67 | TEXT ·pbf7Set(SB), NOSPLIT, $120 68 | MOVQ space+0(FP), DI 69 | MOVQ pageLevel+8(FP), SI 70 | MOVQ pageNum+16(FP), DX 71 | MOVQ key+24(FP), CX 72 | MOVQ key_len+32(FP), R8 73 | CALL PBF7_Set(SB) 74 | MOVB AX, ret+40(FP) 75 | RET 76 | 77 | TEXT ·pbf7Test(SB), NOSPLIT, $120 78 | MOVQ space+0(FP), DI 79 | MOVQ pageLevel+8(FP), SI 80 | MOVQ pageNum+16(FP), DX 81 | MOVQ key+24(FP), CX 82 | MOVQ key_len+32(FP), R8 83 | CALL PBF7_Test(SB) 84 | MOVB AX, ret+40(FP) 85 | RET 86 | 87 | TEXT ·pbf8Set(SB), NOSPLIT, $120 88 | MOVQ space+0(FP), DI 89 | MOVQ pageLevel+8(FP), SI 90 | MOVQ pageNum+16(FP), DX 91 | MOVQ key+24(FP), CX 92 | MOVQ key_len+32(FP), R8 93 | CALL PBF8_Set(SB) 94 | MOVB AX, ret+40(FP) 95 | RET 96 | 97 | TEXT ·pbf8Test(SB), NOSPLIT, $120 98 | MOVQ space+0(FP), DI 99 | MOVQ pageLevel+8(FP), SI 100 | MOVQ pageNum+16(FP), DX 101 | MOVQ key+24(FP), CX 102 | MOVQ key_len+32(FP), R8 103 | CALL PBF8_Test(SB) 104 | MOVB AX, ret+40(FP) 105 | RET 106 | -------------------------------------------------------------------------------- /go/pbf_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pbf 6 | 7 | import ( 8 | "encoding/binary" 9 | "testing" 10 | "unsafe" 11 | ) 12 | 13 | func assert(t *testing.T, state bool) { 14 | if !state { 15 | t.FailNow() 16 | } 17 | } 18 | 19 | func TestNew(t *testing.T) { 20 | bf := NewBloomFilter(500, 0.01) 21 | assert(t, bf != nil) 22 | assert(t, bf.Way() == 7) 23 | assert(t, bf.PageLevel() == 7) 24 | assert(t, len(bf.Data()) == 640) 25 | } 26 | 27 | func doTest(t *testing.T, way uint32) { 28 | bf := NewPageBloomFilter(way, 7, 3) 29 | assert(t, bf != nil) 30 | 31 | var tmp [8]byte 32 | slc := tmp[:] 33 | key := *(*string)(unsafe.Pointer(&slc)) 34 | for i := uint64(0); i < 200; i++ { 35 | binary.LittleEndian.PutUint64(slc, i) 36 | assert(t, bf.Set(key)) 37 | } 38 | for i := uint64(0); i < 200; i++ { 39 | binary.LittleEndian.PutUint64(slc, i) 40 | assert(t, bf.Test(key)) 41 | } 42 | for i := uint64(200); i < 400; i++ { 43 | binary.LittleEndian.PutUint64(slc, i) 44 | assert(t, !bf.Test(key)) 45 | } 46 | } 47 | 48 | func TestW4(t *testing.T) { doTest(t, 4) } 49 | func TestW5(t *testing.T) { doTest(t, 5) } 50 | func TestW6(t *testing.T) { doTest(t, 6) } 51 | func TestW7(t *testing.T) { doTest(t, 7) } 52 | func TestW8(t *testing.T) { doTest(t, 8) } 53 | 54 | func benchSet(b *testing.B, way uint32) { 55 | b.StopTimer() 56 | tmp := make([]byte, b.N*8) 57 | keys := make([]string, b.N) 58 | for i := 0; i < b.N; i++ { 59 | slc := tmp[i*8 : (i+1)*8] 60 | keys[i] = *(*string)(unsafe.Pointer(&slc)) 61 | binary.LittleEndian.PutUint64(slc, uint64(i)) 62 | } 63 | bf := NewPageBloomFilter(way, 12, uint32(b.N/2048)+1) 64 | b.StartTimer() 65 | for i := 0; i < b.N; i++ { 66 | bf.Set(keys[i]) 67 | } 68 | } 69 | 70 | func benchTest(b *testing.B, way uint32) { 71 | b.StopTimer() 72 | tmp := make([]byte, b.N*8) 73 | keys := make([]string, b.N) 74 | for i := 0; i < b.N; i++ { 75 | slc := tmp[i*8 : (i+1)*8] 76 | keys[i] = *(*string)(unsafe.Pointer(&slc)) 77 | } 78 | bf := NewPageBloomFilter(way, 12, uint32(b.N/4096)+1) 79 | for i := 0; i < b.N/2; i++ { 80 | binary.LittleEndian.PutUint64(tmp[:8], uint64(i)) 81 | bf.Set(keys[0]) 82 | } 83 | for i := 0; i < b.N; i++ { 84 | binary.LittleEndian.PutUint64(tmp[i*8:(i+1)*8], uint64(i)) 85 | } 86 | b.StartTimer() 87 | for i := 0; i < b.N; i++ { 88 | bf.Test(keys[i]) 89 | } 90 | } 91 | 92 | func BenchmarkSet4(b *testing.B) { benchSet(b, 4) } 93 | func BenchmarkTest4(b *testing.B) { benchTest(b, 4) } 94 | func BenchmarkSet5(b *testing.B) { benchSet(b, 5) } 95 | func BenchmarkTest5(b *testing.B) { benchTest(b, 5) } 96 | func BenchmarkSet6(b *testing.B) { benchSet(b, 6) } 97 | func BenchmarkTest6(b *testing.B) { benchTest(b, 6) } 98 | func BenchmarkSet7(b *testing.B) { benchSet(b, 7) } 99 | func BenchmarkTest7(b *testing.B) { benchTest(b, 7) } 100 | func BenchmarkSet8(b *testing.B) { benchSet(b, 8) } 101 | func BenchmarkTest8(b *testing.B) { benchTest(b, 8) } 102 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PageBloomFilter", "PageBloomFilter\PageBloomFilter.csproj", "{CF6F5A78-C763-40FF-9177-0B7927AA4761}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PageBloomFilter.Tests", "PageBloomFilter.Tests\PageBloomFilter.Tests.csproj", "{43BC62E1-E842-421A-A0E2-2116DAD59751}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PageBloomFilter.Benchmark", "PageBloomFilter.Benchmark\PageBloomFilter.Benchmark.csproj", "{A4394495-E476-4846-B5D3-F7281DDFCBDC}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Release|Any CPU = Release|Any CPU 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Debug|x64.Build.0 = Debug|Any CPU 24 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Release|x64.ActiveCfg = Release|x64 27 | {CF6F5A78-C763-40FF-9177-0B7927AA4761}.Release|x64.Build.0 = Release|x64 28 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Debug|x64.ActiveCfg = Debug|Any CPU 31 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Debug|x64.Build.0 = Debug|Any CPU 32 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Release|x64.ActiveCfg = Release|x64 35 | {43BC62E1-E842-421A-A0E2-2116DAD59751}.Release|x64.Build.0 = Release|x64 36 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Debug|x64.ActiveCfg = Debug|x64 39 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Debug|x64.Build.0 = Debug|x64 40 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Release|x64.ActiveCfg = Release|x64 43 | {A4394495-E476-4846-B5D3-F7281DDFCBDC}.Release|x64.Build.0 = Release|x64 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {8935FBDB-8E2C-4D0A-ACC0-3F02AC9DA6B5} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /java/src/test/java/com/github/peterrk/pbf/HashTest.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.github.peterrk.pbf; 6 | 7 | import com.github.peterrk.pbf.Hash.V128; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.Assertions; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.Arrays; 12 | 13 | 14 | public class HashTest { 15 | 16 | @Test 17 | public void stableTest() { 18 | V128[] expected = { 19 | new V128(0x232706fc6bf50919L,0x8b72ee65b4e851c7L), 20 | new V128(0x50209687d54ec67eL,0x62fe85108df1cf6dL), 21 | new V128(0xfbe67d8368f3fb4fL,0xb54a5a89706d5a5aL), 22 | new V128(0x2882d11a5846ccfaL,0x6b21b0e870109222L), 23 | new V128(0xf5e0d56325d6d000L,0xaf8703c9f9ac75e5L), 24 | new V128(0x59a0f67b7ae7a5adL,0x84d7aeabc053b848L), 25 | new V128(0xf01562a268e42c21L,0xdfe994ab22873e7eL), 26 | new V128(0x16133104620725ddL,0xa5ca36afa7182e6aL), 27 | new V128(0x7a9378dcdf599479L,0x30f5a569a74ecdd7L), 28 | new V128(0xd9f07bdc76c20a78L,0x34f0621847f7888aL), 29 | new V128(0x332a4fff07df83daL,0xfa40557cc0ea6b72L), 30 | new V128(0x976beeefd11659dcL,0x8a3187b6a72d0039L), 31 | new V128(0xc3fcc139e4c6832aL,0xdadfeff6e01e2f2eL), 32 | new V128(0x86130593c7746a6fL,0x8ac9fb14904fe39dL), 33 | new V128(0x70550dbe5cdde280L,0xddb95757282706c0L), 34 | new V128(0x67211fbaf6b9122dL,0x68f4e8f3bbc700dbL), 35 | new V128(0xe2d06846964b80adL,0x6005068ac75c4c20L), 36 | new V128(0xd55b3c010258ce93L,0x981c8b03659d9950L), 37 | new V128(0x5a2507daa032fa13L,0x0d1c989bfc0c6cf7L), 38 | new V128(0xaf8618678ae5cd55L,0xe0b75cfad427eefcL), 39 | new V128(0xad5a7047e8a139d8L,0x183621cf988a753eL), 40 | new V128(0x8fc110192723cd5eL,0x203129f80764b844L), 41 | new V128(0x50170b4485d7af19L,0x7f2c79d145db7d35L), 42 | new V128(0x7c32444652212bf3L,0x27fd51b9156e2ad2L), 43 | new V128(0x90e571225cce7360L,0xf743b8f6f7433428L), 44 | new V128(0x9919537c1add41e1L,0x7ff0158f05b261f2L), 45 | new V128(0x3a70a8070883029fL,0xc5dcba911815d20aL), 46 | new V128(0xcc32b418290e2879L,0xbb7945d6d79b5dfbL), 47 | new V128(0xde493e4646077aebL,0x465c2ea52660973aL), 48 | new V128(0x4d3ad9b55316f970L,0x9137e3040a7d87bbL), 49 | new V128(0x1547de75efe848f4L,0x21ae3f08b5330aacL), 50 | new V128(0xe2ead0cc6aab6affL,0x29a20bccf77e70a7L), 51 | new V128(0x3dc2f4a9e9b451b4L,0x27de306dde7b60d2L), 52 | new V128(0xce247654a4de9f51L,0x040097e45e948d66L), 53 | new V128(0xbc118f2ba2305503L,0x810f05d0ea32853fL), 54 | new V128(0xb55cd8bdcac2a118L,0x4e93b65164705d2aL), 55 | new V128(0xb7c97db807c32f38L,0x510723230adef63dL), 56 | }; 57 | byte[] buf = "0123456789abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.UTF_8); 58 | for (int i = 0; i < expected.length; i++) { 59 | V128 code = Hash.hash128(Arrays.copyOfRange(buf, 0, i)); 60 | Assertions.assertEquals(expected[i], code); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter.Tests/HashTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | using NUnit.Framework; 6 | using System.Text; 7 | using static PageBloomFilter.Hash; 8 | 9 | namespace PageBloomFilter.Tests { 10 | public class HashTest { 11 | 12 | [Test] 13 | public void StableTest() { 14 | 15 | V128[] expected = { 16 | new V128(0x232706fc6bf50919UL,0x8b72ee65b4e851c7UL), 17 | new V128(0x50209687d54ec67eUL,0x62fe85108df1cf6dUL), 18 | new V128(0xfbe67d8368f3fb4fUL,0xb54a5a89706d5a5aUL), 19 | new V128(0x2882d11a5846ccfaUL,0x6b21b0e870109222UL), 20 | new V128(0xf5e0d56325d6d000UL,0xaf8703c9f9ac75e5UL), 21 | new V128(0x59a0f67b7ae7a5adUL,0x84d7aeabc053b848UL), 22 | new V128(0xf01562a268e42c21UL,0xdfe994ab22873e7eUL), 23 | new V128(0x16133104620725ddUL,0xa5ca36afa7182e6aUL), 24 | new V128(0x7a9378dcdf599479UL,0x30f5a569a74ecdd7UL), 25 | new V128(0xd9f07bdc76c20a78UL,0x34f0621847f7888aUL), 26 | new V128(0x332a4fff07df83daUL,0xfa40557cc0ea6b72UL), 27 | new V128(0x976beeefd11659dcUL,0x8a3187b6a72d0039UL), 28 | new V128(0xc3fcc139e4c6832aUL,0xdadfeff6e01e2f2eUL), 29 | new V128(0x86130593c7746a6fUL,0x8ac9fb14904fe39dUL), 30 | new V128(0x70550dbe5cdde280UL,0xddb95757282706c0UL), 31 | new V128(0x67211fbaf6b9122dUL,0x68f4e8f3bbc700dbUL), 32 | new V128(0xe2d06846964b80adUL,0x6005068ac75c4c20UL), 33 | new V128(0xd55b3c010258ce93UL,0x981c8b03659d9950UL), 34 | new V128(0x5a2507daa032fa13UL,0x0d1c989bfc0c6cf7UL), 35 | new V128(0xaf8618678ae5cd55UL,0xe0b75cfad427eefcUL), 36 | new V128(0xad5a7047e8a139d8UL,0x183621cf988a753eUL), 37 | new V128(0x8fc110192723cd5eUL,0x203129f80764b844UL), 38 | new V128(0x50170b4485d7af19UL,0x7f2c79d145db7d35UL), 39 | new V128(0x7c32444652212bf3UL,0x27fd51b9156e2ad2UL), 40 | new V128(0x90e571225cce7360UL,0xf743b8f6f7433428UL), 41 | new V128(0x9919537c1add41e1UL,0x7ff0158f05b261f2UL), 42 | new V128(0x3a70a8070883029fUL,0xc5dcba911815d20aUL), 43 | new V128(0xcc32b418290e2879UL,0xbb7945d6d79b5dfbUL), 44 | new V128(0xde493e4646077aebUL,0x465c2ea52660973aUL), 45 | new V128(0x4d3ad9b55316f970UL,0x9137e3040a7d87bbUL), 46 | new V128(0x1547de75efe848f4UL,0x21ae3f08b5330aacUL), 47 | new V128(0xe2ead0cc6aab6affUL,0x29a20bccf77e70a7UL), 48 | new V128(0x3dc2f4a9e9b451b4UL,0x27de306dde7b60d2UL), 49 | new V128(0xce247654a4de9f51UL,0x040097e45e948d66UL), 50 | new V128(0xbc118f2ba2305503UL,0x810f05d0ea32853fUL), 51 | new V128(0xb55cd8bdcac2a118UL,0x4e93b65164705d2aUL), 52 | new V128(0xb7c97db807c32f38UL,0x510723230adef63dUL), 53 | }; 54 | var view = Encoding.ASCII.GetBytes("0123456789abcdefghijklmnopqrstuvwxyz").AsSpan(); 55 | for (int i = 0; i < expected.Length; i++) { 56 | V128 code = Hash128(view[..i]); 57 | Assert.That(code, Is.EqualTo(expected[i])); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /test/peer-bench.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | #include 8 | #include "pbf.h" 9 | #include "bloom.h" 10 | #include "bf/all.hpp" 11 | 12 | constexpr size_t N = 1000000; 13 | 14 | struct DeltaTime { 15 | long set = 0; 16 | long test = 0; 17 | 18 | const DeltaTime& operator+=(const DeltaTime& x) noexcept { 19 | set += x.set; 20 | test += x.test; 21 | return *this; 22 | } 23 | }; 24 | 25 | template 26 | DeltaTime BenchMark(pbf::PageBloomFilter& bf) { 27 | auto a = std::chrono::steady_clock::now(); 28 | for (uint64_t i = 0; i < N; i += 2) { 29 | bf.set(reinterpret_cast(&i), 8); 30 | } 31 | auto b = std::chrono::steady_clock::now(); 32 | for (uint64_t i = 0; i < N; i++) { 33 | bf.test(reinterpret_cast(&i), 8); 34 | } 35 | auto c = std::chrono::steady_clock::now(); 36 | return { 37 | std::chrono::duration_cast(b - a).count(), 38 | std::chrono::duration_cast(c - b).count() 39 | }; 40 | } 41 | 42 | DeltaTime BenchMark(bf::basic_bloom_filter& bf) { 43 | auto a = std::chrono::steady_clock::now(); 44 | for (uint64_t i = 0; i < N; i += 2) { 45 | bf.add(i); 46 | } 47 | auto b = std::chrono::steady_clock::now(); 48 | for (uint64_t i = 0; i < N; i++) { 49 | bf.lookup(i); 50 | } 51 | auto c = std::chrono::steady_clock::now(); 52 | return { 53 | std::chrono::duration_cast(b - a).count(), 54 | std::chrono::duration_cast(c - b).count() 55 | }; 56 | } 57 | 58 | DeltaTime BenchMark(bloom& bf) { 59 | auto a = std::chrono::steady_clock::now(); 60 | for (uint64_t i = 0; i < N; i += 2) { 61 | bloom_add(&bf, &i, 8); 62 | } 63 | auto b = std::chrono::steady_clock::now(); 64 | for (uint64_t i = 0; i < N; i++) { 65 | bloom_check(&bf, &i, 8); 66 | } 67 | auto c = std::chrono::steady_clock::now(); 68 | return { 69 | std::chrono::duration_cast(b - a).count(), 70 | std::chrono::duration_cast(c - b).count() 71 | }; 72 | } 73 | 74 | int main(int argc, char* argv[]) { 75 | const unsigned loop = 100; 76 | DeltaTime t; 77 | 78 | auto bf1 = NEW_BLOOM_FILTER(N, 0.01); 79 | t = DeltaTime(); 80 | for (int i = 0; i < loop; i++) { 81 | t += BenchMark(bf1); 82 | bf1.clear(); 83 | } 84 | std::cout << "pbf-set: " << (t.set / (double)(loop*N/2)) << " ns/op\n"; 85 | std::cout << "pbf-test: " << (t.test / (double)(loop*N)) << " ns/op\n"; 86 | 87 | bf::basic_bloom_filter bf2(0.01, N); 88 | t = DeltaTime(); 89 | for (int i = 0; i < loop; i++) { 90 | t += BenchMark(bf2); 91 | bf2.clear(); 92 | } 93 | std::cout << "libbf-set: " << (t.set / (double)(loop*N/2)) << " ns/op\n"; 94 | std::cout << "libbf-test: " << (t.test / (double)(loop*N)) << " ns/op\n"; 95 | 96 | bloom bf3; 97 | bloom_init2(&bf3, N, 0.01); 98 | t = DeltaTime(); 99 | for (int i = 0; i < loop; i++) { 100 | t += BenchMark(bf3); 101 | bloom_reset(&bf3); 102 | } 103 | bloom_free(&bf3); 104 | std::cout << "libbloom-set: " << (t.set / (double)(loop*N/2)) << " ns/op\n"; 105 | std::cout << "libbloom-test: " << (t.test / (double)(loop*N)) << " ns/op\n"; 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /go/stub.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build amd64 && !go1.21 6 | 7 | package pbf 8 | 9 | import ( 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | //go:noescape 15 | func pbf4Set(space uintptr, pageLevel, pageNum uint, key string) bool 16 | 17 | //go:noescape 18 | func pbf4Test(space uintptr, pageLevel, pageNum uint, key string) bool 19 | 20 | func (bf *pbfW4) Set(key string) bool { 21 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 22 | if pbf4Set(space, uint(bf.pageLevel), uint(bf.pageNum), key) { 23 | bf.uniqueCnt++ 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | func (bf *pbfW4) Test(key string) bool { 30 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 31 | return pbf4Test(space, uint(bf.pageLevel), uint(bf.pageNum), key) 32 | } 33 | 34 | //go:noescape 35 | func pbf5Set(space uintptr, pageLevel, pageNum uint, key string) bool 36 | 37 | //go:noescape 38 | func pbf5Test(space uintptr, pageLevel, pageNum uint, key string) bool 39 | 40 | func (bf *pbfW5) Set(key string) bool { 41 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 42 | if pbf5Set(space, uint(bf.pageLevel), uint(bf.pageNum), key) { 43 | bf.uniqueCnt++ 44 | return true 45 | } 46 | return false 47 | } 48 | 49 | func (bf *pbfW5) Test(key string) bool { 50 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 51 | return pbf5Test(space, uint(bf.pageLevel), uint(bf.pageNum), key) 52 | } 53 | 54 | //go:noescape 55 | func pbf6Set(space uintptr, pageLevel, pageNum uint, key string) bool 56 | 57 | //go:noescape 58 | func pbf6Test(space uintptr, pageLevel, pageNum uint, key string) bool 59 | 60 | func (bf *pbfW6) Set(key string) bool { 61 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 62 | if pbf6Set(space, uint(bf.pageLevel), uint(bf.pageNum), key) { 63 | bf.uniqueCnt++ 64 | return true 65 | } 66 | return false 67 | } 68 | 69 | func (bf *pbfW6) Test(key string) bool { 70 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 71 | return pbf6Test(space, uint(bf.pageLevel), uint(bf.pageNum), key) 72 | } 73 | 74 | //go:noescape 75 | func pbf7Set(space uintptr, pageLevel, pageNum uint, key string) bool 76 | 77 | //go:noescape 78 | func pbf7Test(space uintptr, pageLevel, pageNum uint, key string) bool 79 | 80 | func (bf *pbfW7) Set(key string) bool { 81 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 82 | if pbf7Set(space, uint(bf.pageLevel), uint(bf.pageNum), key) { 83 | bf.uniqueCnt++ 84 | return true 85 | } 86 | return false 87 | } 88 | 89 | func (bf *pbfW7) Test(key string) bool { 90 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 91 | return pbf7Test(space, uint(bf.pageLevel), uint(bf.pageNum), key) 92 | } 93 | 94 | //go:noescape 95 | func pbf8Set(space uintptr, pageLevel, pageNum uint, key string) bool 96 | 97 | //go:noescape 98 | func pbf8Test(space uintptr, pageLevel, pageNum uint, key string) bool 99 | 100 | func (bf *pbfW8) Set(key string) bool { 101 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 102 | if pbf8Set(space, uint(bf.pageLevel), uint(bf.pageNum), key) { 103 | bf.uniqueCnt++ 104 | return true 105 | } 106 | return false 107 | } 108 | 109 | func (bf *pbfW8) Test(key string) bool { 110 | space := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)).Data 111 | return pbf8Test(space, uint(bf.pageLevel), uint(bf.pageNum), key) 112 | } 113 | -------------------------------------------------------------------------------- /src/hash.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "hash.h" 6 | #if defined(USE_AESNI_HASH) 7 | #include "aesni-hash.h" 8 | #elif defined(USE_XXHASH) 9 | #include "xxh3.h" 10 | #endif 11 | 12 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 13 | #error "only support little endian" 14 | #endif 15 | 16 | namespace pbf { 17 | 18 | #if defined(USE_AESNI_HASH) 19 | V128 Hash(const uint8_t* msg, unsigned len) noexcept { 20 | union { 21 | V128 v; 22 | __m128i m; 23 | } t; 24 | t.m = AESNI_Hash128(msg, len); 25 | return t.v; 26 | } 27 | #elif defined(USE_XXHASH) 28 | V128 Hash(const uint8_t* msg, unsigned len) noexcept { 29 | auto ret = XXH3_128bits(msg, len); 30 | return {ret.low64, ret.high64}; 31 | } 32 | #else 33 | 34 | static FORCE_INLINE uint64_t Rot64(uint64_t x, unsigned k) noexcept { 35 | return (x << k) | (x >> (64U - k)); 36 | } 37 | 38 | static FORCE_INLINE void Mix(uint64_t& h0, uint64_t& h1, uint64_t& h2, uint64_t& h3) noexcept { 39 | h2 = Rot64(h2,50); h2 += h3; h0 ^= h2; 40 | h3 = Rot64(h3,52); h3 += h0; h1 ^= h3; 41 | h0 = Rot64(h0,30); h0 += h1; h2 ^= h0; 42 | h1 = Rot64(h1,41); h1 += h2; h3 ^= h1; 43 | h2 = Rot64(h2,54); h2 += h3; h0 ^= h2; 44 | h3 = Rot64(h3,48); h3 += h0; h1 ^= h3; 45 | h0 = Rot64(h0,38); h0 += h1; h2 ^= h0; 46 | h1 = Rot64(h1,37); h1 += h2; h3 ^= h1; 47 | h2 = Rot64(h2,62); h2 += h3; h0 ^= h2; 48 | h3 = Rot64(h3,34); h3 += h0; h1 ^= h3; 49 | h0 = Rot64(h0,5); h0 += h1; h2 ^= h0; 50 | h1 = Rot64(h1,36); h1 += h2; h3 ^= h1; 51 | } 52 | 53 | static FORCE_INLINE void End(uint64_t& h0, uint64_t& h1, uint64_t& h2, uint64_t& h3) noexcept { 54 | h3 ^= h2; h2 = Rot64(h2,15); h3 += h2; 55 | h0 ^= h3; h3 = Rot64(h3,52); h0 += h3; 56 | h1 ^= h0; h0 = Rot64(h0,26); h1 += h0; 57 | h2 ^= h1; h1 = Rot64(h1,51); h2 += h1; 58 | h3 ^= h2; h2 = Rot64(h2,28); h3 += h2; 59 | h0 ^= h3; h3 = Rot64(h3,9); h0 += h3; 60 | h1 ^= h0; h0 = Rot64(h0,47); h1 += h0; 61 | h2 ^= h1; h1 = Rot64(h1,54); h2 += h1; 62 | h3 ^= h2; h2 = Rot64(h2,32); h3 += h2; 63 | h0 ^= h3; h3 = Rot64(h3,25); h0 += h3; 64 | h1 ^= h0; h0 = Rot64(h0,63); h1 += h0; 65 | } 66 | 67 | //SpookyHash 68 | V128 Hash(const uint8_t* msg, unsigned len) noexcept { 69 | constexpr uint64_t magic = 0xdeadbeefdeadbeefULL; 70 | 71 | uint64_t a = 0; 72 | uint64_t b = 0; 73 | uint64_t c = magic; 74 | uint64_t d = magic; 75 | 76 | for (auto end = msg + (len&~0x1fU); msg < end; msg += 32) { 77 | auto x = (const uint64_t*)msg; 78 | c += x[0]; 79 | d += x[1]; 80 | Mix(a, b, c, d); 81 | a += x[2]; 82 | b += x[3]; 83 | } 84 | 85 | if (len & 0x10U) { 86 | auto x = (const uint64_t*)msg; 87 | c += x[0]; 88 | d += x[1]; 89 | Mix(a, b, c, d); 90 | msg += 16; 91 | } 92 | 93 | d += ((uint64_t)len) << 56U; 94 | switch (len & 0xfU) { 95 | case 15: 96 | d += ((uint64_t)msg[14]) << 48U; 97 | case 14: 98 | d += ((uint64_t)msg[13]) << 40U; 99 | case 13: 100 | d += ((uint64_t)msg[12]) << 32U; 101 | case 12: 102 | d += *(uint32_t*)(msg+8); 103 | c += *(uint64_t*)msg; 104 | break; 105 | case 11: 106 | d += ((uint64_t)msg[10]) << 16U; 107 | case 10: 108 | d += ((uint64_t)msg[9]) << 8U; 109 | case 9: 110 | d += (uint64_t)msg[8]; 111 | case 8: 112 | c += *(uint64_t*)msg; 113 | break; 114 | case 7: 115 | c += ((uint64_t)msg[6]) << 48U; 116 | case 6: 117 | c += ((uint64_t)msg[5]) << 40U; 118 | case 5: 119 | c += ((uint64_t)msg[4]) << 32U; 120 | case 4: 121 | c += *(uint32_t*)msg; 122 | break; 123 | case 3: 124 | c += ((uint64_t)msg[2]) << 16U; 125 | case 2: 126 | c += ((uint64_t)msg[1]) << 8U; 127 | case 1: 128 | c += (uint64_t)msg[0]; 129 | break; 130 | case 0: 131 | c += magic; 132 | d += magic; 133 | } 134 | End(a, b, c, d); 135 | 136 | return {a, b}; 137 | } 138 | 139 | #endif 140 | 141 | } //pbf -------------------------------------------------------------------------------- /csharp/PageBloomFilter.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | using BloomFilter; 6 | using System.Diagnostics; 7 | 8 | namespace PageBloomFilter.Benchmark { 9 | public class Program { 10 | 11 | public struct DeltaTime { 12 | public TimeSpan set; 13 | public TimeSpan test; 14 | public DeltaTime(TimeSpan set, TimeSpan test) { 15 | this.set = set; 16 | this.test = test; 17 | } 18 | } 19 | 20 | private const long N = 1000000L; 21 | 22 | private static DeltaTime DoBenchmark(PageBloomFilter bf) { 23 | var key = new Span(new byte[8]); 24 | 25 | var set = new Stopwatch(); 26 | set.Start(); 27 | for (long i = 0; i < N; i += 2) { 28 | BitConverter.TryWriteBytes(key, i); 29 | bf.Set(key); 30 | } 31 | set.Stop(); 32 | 33 | var test = new Stopwatch(); 34 | test.Start(); 35 | for (long i = 0; i < N; i++) { 36 | BitConverter.TryWriteBytes(key, i); 37 | bf.Test(key); 38 | } 39 | test.Stop(); 40 | 41 | return new DeltaTime(set.Elapsed, test.Elapsed); 42 | } 43 | 44 | private static DeltaTime DoBenchmark(IBloomFilter bf) { 45 | var key = new Span(new byte[8]); 46 | 47 | var set = new Stopwatch(); 48 | set.Start(); 49 | for (long i = 0; i < N; i += 2) { 50 | BitConverter.TryWriteBytes(key, i); 51 | bf.Add(key); 52 | } 53 | set.Stop(); 54 | 55 | var test = new Stopwatch(); 56 | test.Start(); 57 | for (long i = 0; i < N; i++) { 58 | BitConverter.TryWriteBytes(key, i); 59 | bf.Contains(key); 60 | } 61 | test.Stop(); 62 | 63 | return new DeltaTime(set.Elapsed, test.Elapsed); 64 | } 65 | 66 | public static void Main(string[] args) { 67 | var bf = PageBloomFilter.New(N, 0.01); 68 | var key = new Span(new byte[8]); 69 | 70 | // warm up 71 | for (long i = 0; i < N; i++) { 72 | BitConverter.TryWriteBytes(key, i); 73 | bf.Set(key); 74 | bf.Test(key); 75 | } 76 | 77 | const int loop = 100; 78 | var set = new TimeSpan(0); 79 | var test = new TimeSpan(0); 80 | for (int i = 0; i < loop; i++) { 81 | bf.Clear(); 82 | var delta = DoBenchmark(bf); 83 | set += delta.set; 84 | test += delta.test; 85 | } 86 | 87 | Console.Write("pbf-set: {0} ns/op\n", set.TotalNanoseconds / (loop * N / 2)); 88 | Console.Write("pbf-test: {0} ns/op\n", test.TotalNanoseconds / (loop * N)); 89 | 90 | 91 | var bf2 = FilterBuilder.Build(N, 0.01); 92 | // warm up 93 | for (long i = 0; i < N; i++) { 94 | BitConverter.TryWriteBytes(key, i); 95 | bf2.Add(key); 96 | bf2.Contains(key); 97 | } 98 | 99 | set = new TimeSpan(0); 100 | test = new TimeSpan(0); 101 | for (int i = 0; i < loop; i++) { 102 | bf.Clear(); 103 | var delta = DoBenchmark(bf2); 104 | set += delta.set; 105 | test += delta.test; 106 | } 107 | 108 | Console.Write("bf.nc-set: {0} ns/op\n", set.TotalNanoseconds / (loop * N / 2)); 109 | Console.Write("bf.nc-test: {0} ns/op\n", test.TotalNanoseconds / (loop * N)); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | mod hash; 6 | pub mod pbf; 7 | 8 | 9 | #[test] 10 | fn test_hash() { 11 | let expected = [ 12 | [0x232706fc6bf50919_u64, 0x8b72ee65b4e851c7_u64], 13 | [0x50209687d54ec67e_u64, 0x62fe85108df1cf6d_u64], 14 | [0xfbe67d8368f3fb4f_u64, 0xb54a5a89706d5a5a_u64], 15 | [0x2882d11a5846ccfa_u64, 0x6b21b0e870109222_u64], 16 | [0xf5e0d56325d6d000_u64, 0xaf8703c9f9ac75e5_u64], 17 | [0x59a0f67b7ae7a5ad_u64, 0x84d7aeabc053b848_u64], 18 | [0xf01562a268e42c21_u64, 0xdfe994ab22873e7e_u64], 19 | [0x16133104620725dd_u64, 0xa5ca36afa7182e6a_u64], 20 | [0x7a9378dcdf599479_u64, 0x30f5a569a74ecdd7_u64], 21 | [0xd9f07bdc76c20a78_u64, 0x34f0621847f7888a_u64], 22 | [0x332a4fff07df83da_u64, 0xfa40557cc0ea6b72_u64], 23 | [0x976beeefd11659dc_u64, 0x8a3187b6a72d0039_u64], 24 | [0xc3fcc139e4c6832a_u64, 0xdadfeff6e01e2f2e_u64], 25 | [0x86130593c7746a6f_u64, 0x8ac9fb14904fe39d_u64], 26 | [0x70550dbe5cdde280_u64, 0xddb95757282706c0_u64], 27 | [0x67211fbaf6b9122d_u64, 0x68f4e8f3bbc700db_u64], 28 | [0xe2d06846964b80ad_u64, 0x6005068ac75c4c20_u64], 29 | [0xd55b3c010258ce93_u64, 0x981c8b03659d9950_u64], 30 | [0x5a2507daa032fa13_u64, 0x0d1c989bfc0c6cf7_u64], 31 | [0xaf8618678ae5cd55_u64, 0xe0b75cfad427eefc_u64], 32 | [0xad5a7047e8a139d8_u64, 0x183621cf988a753e_u64], 33 | [0x8fc110192723cd5e_u64, 0x203129f80764b844_u64], 34 | [0x50170b4485d7af19_u64, 0x7f2c79d145db7d35_u64], 35 | [0x7c32444652212bf3_u64, 0x27fd51b9156e2ad2_u64], 36 | [0x90e571225cce7360_u64, 0xf743b8f6f7433428_u64], 37 | [0x9919537c1add41e1_u64, 0x7ff0158f05b261f2_u64], 38 | [0x3a70a8070883029f_u64, 0xc5dcba911815d20a_u64], 39 | [0xcc32b418290e2879_u64, 0xbb7945d6d79b5dfb_u64], 40 | [0xde493e4646077aeb_u64, 0x465c2ea52660973a_u64], 41 | [0x4d3ad9b55316f970_u64, 0x9137e3040a7d87bb_u64], 42 | [0x1547de75efe848f4_u64, 0x21ae3f08b5330aac_u64], 43 | [0xe2ead0cc6aab6aff_u64, 0x29a20bccf77e70a7_u64], 44 | [0x3dc2f4a9e9b451b4_u64, 0x27de306dde7b60d2_u64], 45 | [0xce247654a4de9f51_u64, 0x040097e45e948d66_u64], 46 | [0xbc118f2ba2305503_u64, 0x810f05d0ea32853f_u64], 47 | [0xb55cd8bdcac2a118_u64, 0x4e93b65164705d2a_u64], 48 | [0xb7c97db807c32f38_u64, 0x510723230adef63d_u64], 49 | ]; 50 | let key = "0123456789abcdefghijklmnopqrstuvwxyz".as_bytes(); 51 | for i in 0..expected.len() { 52 | let code = hash::hash128(&key[..i]); 53 | assert_eq!(expected[i], code); 54 | } 55 | } 56 | 57 | #[test] 58 | fn test_new() { 59 | let bf = pbf::new_bloom_filter(500, 0.01); 60 | assert!(bf.valid()); 61 | assert_eq!(7, bf.get_way()); 62 | assert_eq!(7, bf.get_page_level()); 63 | assert_eq!(640, bf.get_data().len()); 64 | } 65 | 66 | #[test] 67 | fn test_operate() { 68 | let _doit = |way: u8| { 69 | let mut bf = pbf::new_pbf(way, 7, 3); 70 | for i in 0..200 { 71 | assert!(bf.set(&(i as u64).to_le_bytes())); 72 | } 73 | for i in 0..200 { 74 | assert!(bf.test(&(i as u64).to_le_bytes())); 75 | } 76 | for i in 200..400 { 77 | assert!(!bf.test(&(i as u64).to_le_bytes())); 78 | } 79 | }; 80 | for i in 4..9 { 81 | _doit(i as u8); 82 | } 83 | } 84 | 85 | #[test] 86 | fn benchmark() { 87 | let n = 1000000_usize; 88 | let mut bf = pbf::new_bloom_filter(n, 0.01); 89 | 90 | let set = std::time::Instant::now(); 91 | for i in 0..(n/2) { 92 | bf.set(&(i as u64).to_le_bytes()); 93 | } 94 | println!("pbf-set: {:.2}ns/op", (set.elapsed().as_nanos() as u64) as f64 / (n/2) as f64); 95 | 96 | let test = std::time::Instant::now(); 97 | for i in 0..n { 98 | bf.test(&(i as u64).to_le_bytes()); 99 | } 100 | println!("pbf-test: {:.2}ns/op", (test.elapsed().as_nanos() as u64) as f64 / n as f64); 101 | } -------------------------------------------------------------------------------- /go/go-inject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import re 4 | import sys 5 | import struct 6 | import binascii 7 | 8 | 9 | _PTN_DATA_LINE = re.compile('^\s*[0-9a-z]+\s(.+)') 10 | _PTN_FUNC = re.compile('^([0-9a-z]+)\s<(\w+)>') 11 | _PTN_CODE = re.compile('^\s*([0-9a-z]+):\t([0-9a-z ]+)\t(.+)') 12 | _PTN_CODE_EXT = re.compile('^\s*([0-9a-z]+):\t([0-9a-z ]+)$') 13 | _PTN_CALL = re.compile('^call\s+([0-9a-z]+)\s<([^\s<>]+)>') 14 | 15 | 16 | def load_data(filename): 17 | data = bytearray() 18 | with open(filename, 'r') as f: 19 | for line in f: 20 | line = line.strip('\n') 21 | m = _PTN_DATA_LINE.match(line) 22 | if m is None: 23 | continue 24 | tail = m.groups()[0][:-18] 25 | data += binascii.a2b_hex(tail.replace(' ', '')) 26 | return data 27 | 28 | 29 | def bytes_to_go(bs): 30 | code = [] 31 | while len(bs) >= 8: 32 | x = bytearray(8) 33 | x[0] = bs[7] 34 | x[1] = bs[6] 35 | x[2] = bs[5] 36 | x[3] = bs[4] 37 | x[4] = bs[3] 38 | x[5] = bs[2] 39 | x[6] = bs[1] 40 | x[7] = bs[0] 41 | bs = bs[8:] 42 | code.append('QUAD $0x'+binascii.b2a_hex(x).decode('ascii')) 43 | if len(bs) >= 4: 44 | x = bytearray(4) 45 | x[0] = bs[3] 46 | x[1] = bs[2] 47 | x[2] = bs[1] 48 | x[3] = bs[0] 49 | bs = bs[4:] 50 | code.append('LONG $0x'+binascii.b2a_hex(x).decode('ascii')) 51 | if len(bs) >= 2: 52 | x = bytearray(2) 53 | x[0] = bs[1] 54 | x[1] = bs[0] 55 | bs = bs[2:] 56 | code.append('WORD $0x'+binascii.b2a_hex(x).decode('ascii')) 57 | if len(bs) >= 1: 58 | code.append('BYTE $0x'+binascii.b2a_hex(bs).decode('ascii')) 59 | return '; '.join(code) 60 | 61 | 62 | def main(size_file, data_file, code_file, out_file): 63 | data = load_data(size_file) 64 | assert len(data) % 8 == 0 65 | func_size = [] 66 | for off in range(0, len(data), 8): 67 | func_size.append(struct.unpack('= cend: 110 | if dend > cend: 111 | func_body.append(('data '+str(dend-cend), func_data[cend:dend])) 112 | func_list.append((name, func_body)) 113 | func_body = None 114 | continue 115 | bs = binascii.a2b_hex(bs.replace(' ', '')) 116 | assert bs == func_data[off:off+len(bs)] 117 | func_body.append((code, bs)) 118 | 119 | assert len(func_size) == 0 120 | if func_body is not None: 121 | func_list.append((name, func_body)) 122 | func_body = None 123 | 124 | with open(out_file, 'w') as f: 125 | print('#include "textflag.h"', file=f) 126 | print('', file=f) 127 | for name, body in func_list: 128 | print('TEXT {}(SB), NOSPLIT, $0'.format(name), file=f) 129 | for code, bs in body: 130 | print(' // ' + code, file=f) 131 | m = _PTN_CALL.match(code) 132 | if m is not None: 133 | addr, target = m.groups() 134 | addr = int(addr, 16) 135 | assert func_names[target] == addr 136 | assert len(bs) == 5 137 | print(' CALL {}(SB)'.format(target), file=f) 138 | continue 139 | print(' '+bytes_to_go(bs), file=f) 140 | print('', file=f) 141 | 142 | 143 | if __name__ == '__main__': 144 | if len(sys.argv) != 5: 145 | print('usage: {} size-file data-file code-file output'.format(sys.argv[0])) 146 | quit(1) 147 | main(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) -------------------------------------------------------------------------------- /src/pbf.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include 6 | #include "pbf.h" 7 | #include "pbf-internal.h" 8 | 9 | namespace pbf { 10 | 11 | void _PageBloomFilter::init(unsigned page_level, unsigned page_num, size_t unique_cnt, const uint8_t* data) { 12 | m_page_level = page_level; 13 | m_page_num = page_num; 14 | auto space = std::make_unique(data_size()); 15 | if (data == nullptr) { 16 | m_unique_cnt = 0; 17 | memset(space.get(), 0, data_size()); 18 | } else { 19 | m_unique_cnt = unique_cnt; 20 | memcpy(space.get(), data, data_size()); 21 | } 22 | m_space = std::move(space); 23 | } 24 | 25 | void _PageBloomFilter::clear() noexcept { 26 | m_unique_cnt = 0; 27 | if (m_space != nullptr) { 28 | memset(m_space.get(), 0, data_size()); 29 | } 30 | } 31 | 32 | template 33 | bool PageBloomFilter::test(const uint8_t* data, unsigned len) const noexcept { 34 | V128X t; 35 | t.v = Hash(data, len); 36 | size_t idx = PageHash(t) % m_page_num; 37 | const uint8_t* page = m_space.get() + (idx << m_page_level); 38 | return Test(page, m_page_level, t); 39 | } 40 | 41 | template 42 | bool PageBloomFilter::set(const uint8_t* data, unsigned len) noexcept { 43 | V128X t; 44 | t.v = Hash(data, len); 45 | size_t idx = PageHash(t) % m_page_num; 46 | uint8_t* page = m_space.get() + (idx << m_page_level); 47 | if (Set(page, m_page_level, t)) { 48 | m_unique_cnt++; 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | template class PageBloomFilter<4>; 55 | template class PageBloomFilter<5>; 56 | template class PageBloomFilter<6>; 57 | template class PageBloomFilter<7>; 58 | template class PageBloomFilter<8>; 59 | 60 | template 61 | class BloomFilterImp : public BloomFilter { 62 | public: 63 | size_t capacity() const noexcept { return self()->capacity(); } 64 | size_t virual_capacity(float fpr) const noexcept { return self()->virual_capacity(fpr); } 65 | unsigned way() const noexcept { return self()->way(); } 66 | bool test(const uint8_t* data, unsigned len) const noexcept { return self()->test(data, len); } 67 | bool set(const uint8_t* data, unsigned len) noexcept { return self()->set(data, len); } 68 | 69 | explicit BloomFilterImp(PageBloomFilter&& bf) { 70 | *self() = std::move(bf); 71 | } 72 | 73 | private: 74 | const PageBloomFilter* self() const noexcept { 75 | return reinterpret_cast*>(static_cast(this)); 76 | } 77 | PageBloomFilter* self() noexcept { 78 | return reinterpret_cast*>(static_cast<_PageBloomFilter*>(this)); 79 | } 80 | }; 81 | 82 | template class BloomFilterImp<4>; 83 | template class BloomFilterImp<5>; 84 | template class BloomFilterImp<6>; 85 | template class BloomFilterImp<7>; 86 | template class BloomFilterImp<8>; 87 | 88 | std::unique_ptr New(size_t item, float fpr) { 89 | #define PBF_NEW_CASE(w) \ 90 | case w: \ 91 | { \ 92 | auto tmp = Create< w >(item, fpr); \ 93 | if (!tmp) { \ 94 | return nullptr; \ 95 | } \ 96 | return std::make_unique>(std::move(tmp)); \ 97 | } 98 | switch (BestWay(fpr)) { 99 | PBF_NEW_CASE(4) 100 | PBF_NEW_CASE(5) 101 | PBF_NEW_CASE(6) 102 | PBF_NEW_CASE(7) 103 | PBF_NEW_CASE(8) 104 | } 105 | #undef PBF_NEW_CASE 106 | return nullptr; 107 | } 108 | 109 | std::unique_ptr New(unsigned way, unsigned page_level, unsigned page_num, 110 | size_t unique_cnt, const uint8_t* data) { 111 | #define PBF_NEW_CASE(w) \ 112 | case w: \ 113 | { \ 114 | PageBloomFilter< w > tmp(page_level, page_num, unique_cnt, data); \ 115 | if (!tmp) { \ 116 | return nullptr; \ 117 | } \ 118 | return std::make_unique>(std::move(tmp)); \ 119 | } 120 | switch (way) { 121 | PBF_NEW_CASE(4) 122 | PBF_NEW_CASE(5) 123 | PBF_NEW_CASE(6) 124 | PBF_NEW_CASE(7) 125 | PBF_NEW_CASE(8) 126 | } 127 | #undef PBF_NEW_CASE 128 | return nullptr; 129 | } 130 | 131 | } //pbf 132 | 133 | -------------------------------------------------------------------------------- /python/pbf.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Ruan Kunliang. 2 | # Use of this source code is governed by a BSD-style 3 | # license that can be found in the LICENSE file. 4 | 5 | import math 6 | import time 7 | import struct 8 | import _pbf 9 | 10 | 11 | class PageBloomFilter: 12 | def __init__(self, way, page_level, page_num, unique_cnt=0, data=None): 13 | assert type(way) is int and 4 <= way <= 8 and \ 14 | type(page_level) is int and (8-8/way) <= page_level <= 13 and \ 15 | type(page_num) is int and 0 < page_num < 0xffffffff 16 | self.way = way 17 | self.page_level = page_level 18 | self.page_num = page_num 19 | data_size = page_num << page_level 20 | if data is None: 21 | self.data = bytearray(data_size) 22 | self.clear() 23 | else: 24 | assert len(data) == data_size 25 | self.data = bytearray(data) 26 | self.unique_cnt = unique_cnt 27 | 28 | self._set, self._test = PageBloomFilter._funcs[way] 29 | 30 | _funcs = { 31 | 4: (_pbf.set4, _pbf.test4), 32 | 5: (_pbf.set5, _pbf.test5), 33 | 6: (_pbf.set6, _pbf.test6), 34 | 7: (_pbf.set7, _pbf.test7), 35 | 8: (_pbf.set8, _pbf.test8), 36 | } 37 | 38 | def clear(self): 39 | self.unique_cnt = 0 40 | _pbf.clear(self.data) 41 | 42 | def set(self, key): 43 | if self._set(self.data, self.page_level, key): 44 | self.unique_cnt += 1 45 | return True 46 | return False 47 | 48 | def test(self, key): 49 | return self._test(self.data, self.page_level, key) 50 | 51 | def capacity(self): 52 | return len(self.data) * 8 / self.way 53 | 54 | def virual_capacity(self, fpr): 55 | t = math.lop1p(-math.pow(fpr, 1.0/self.way)) / math.log1p(-1.0/(len(self.data) * 8)) 56 | return int(t) / self.way 57 | 58 | 59 | _LN2 = math.log(2) 60 | 61 | 62 | def create(item, fpr): 63 | """ 64 | :param item: number of items to hold 65 | :param fpr: false positive rate, 0.0005-0.1 66 | :return: PageBloomFilter 67 | """ 68 | if item < 1: 69 | item = 1 70 | 71 | if fpr > 0.1: 72 | fpr = 0.1 73 | elif fpr < 0.0005: 74 | fpr = 0.0005 75 | 76 | w = -math.log2(fpr) 77 | bpi = w / (_LN2 * 8) 78 | if w > 9: 79 | x = w - 7 80 | bpi *= 1 + 0.0025*x*x 81 | elif w > 3: 82 | bpi *= 1.01 83 | 84 | way = round(w) 85 | if way < 4: 86 | way = 4 87 | elif way > 8: 88 | way = 8 89 | 90 | n = int(bpi * item) 91 | page_level = None 92 | for i in range(6, 12): 93 | if n >= (1 << (i+4)): 94 | continue 95 | page_level = i 96 | if page_level < (8 - 8/way): 97 | page_level += 1 98 | break 99 | if page_level is None: 100 | page_level = 12 101 | 102 | page_num = (n + (1 << page_level) - 1) >> page_level 103 | return PageBloomFilter(way, page_level, page_num) 104 | 105 | 106 | def restore(way, page_level, data, unique_cnt): 107 | return PageBloomFilter(way, page_level, len(data)>>page_level, unique_cnt, data) 108 | 109 | 110 | def _test_create(): 111 | bf = create(500, 0.01) 112 | assert bf.way == 7 113 | assert bf.page_level == 7 114 | assert len(bf.data) == 640 115 | 116 | 117 | def _test_operate(way): 118 | bf = PageBloomFilter(way, 7, 3) 119 | 120 | for i in range(200): 121 | assert bf.set(struct.pack(" 10 | #include 11 | 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | static __m128i AESNI_Hash128(const uint8_t* msg, unsigned len, uint32_t seed = 0) { 18 | auto a = _mm_set1_epi32(seed); 19 | auto b = _mm_set1_epi32(len); 20 | auto m = _mm_set_epi32(0xdeadbeef, 0xffff0000, 0x01234567, 0x89abcdef); 21 | auto s = _mm_set_epi8(3, 7, 11, 15, 2, 6, 10, 14, 1, 5, 9, 13, 0, 4, 8, 12); 22 | 23 | bool greed = (((uintptr_t)msg + (len-1)) & 0xfffUL) >= 15; // do not cross page 24 | 25 | if (len > 80) { 26 | auto c = _mm_aesenc_si128(b, m); 27 | auto d = _mm_aesdec_si128(a, m); 28 | a = _mm_aesenc_si128(a, m); 29 | b = _mm_aesdec_si128(b, m); 30 | do { 31 | a = _mm_xor_si128(a, _mm_lddqu_si128((const __m128i*)msg)); 32 | b = _mm_xor_si128(b, _mm_lddqu_si128((const __m128i*)(msg + 16))); 33 | c = _mm_xor_si128(c, _mm_lddqu_si128((const __m128i*)(msg + 32))); 34 | d = _mm_xor_si128(d, _mm_lddqu_si128((const __m128i*)(msg + 48))); 35 | a = _mm_shuffle_epi8(_mm_aesenc_si128(a, m), s); 36 | b = _mm_shuffle_epi8(_mm_aesdec_si128(b, m), s); 37 | c = _mm_shuffle_epi8(_mm_aesenc_si128(c, m), s); 38 | d = _mm_shuffle_epi8(_mm_aesdec_si128(d, m), s); 39 | msg += 64; 40 | len -= 64; 41 | } while (len > 80); 42 | c = _mm_aesenc_si128(a, c); 43 | d = _mm_aesdec_si128(b, d); 44 | a = _mm_aesenc_si128(c, d); 45 | b = _mm_aesdec_si128(d, c); 46 | } 47 | 48 | auto mix = [&a, &b, m, s](__m128i x) { 49 | a = _mm_aesenc_si128(x, a); 50 | a = _mm_aesenc_si128(a, m); 51 | b = _mm_shuffle_epi8(_mm_xor_si128(x, b), s); 52 | b = _mm_shuffle_epi8(_mm_aesdec_si128(b, m), s); 53 | }; 54 | 55 | while (len >= 16) { 56 | mix(_mm_lddqu_si128((const __m128i*)msg)); 57 | msg += 16; 58 | len -= 16; 59 | } 60 | 61 | if (greed) { 62 | #define GREEDILY_READ(n, addr) \ 63 | _mm_bsrli_si128(_mm_bslli_si128(_mm_lddqu_si128((const __m128i*)addr), (16-(n))), (16-(n))) 64 | 65 | switch (len) { 66 | case 15: mix(GREEDILY_READ(15,msg)); break; 67 | case 14: mix(GREEDILY_READ(14,msg)); break; 68 | case 13: mix(GREEDILY_READ(13,msg)); break; 69 | case 12: mix(GREEDILY_READ(12,msg)); break; 70 | case 11: mix(GREEDILY_READ(11,msg)); break; 71 | case 10: mix(GREEDILY_READ(10,msg)); break; 72 | case 9: mix(GREEDILY_READ(9,msg)); break; 73 | case 8: mix((__m128i)_mm_load_sd((const double*)msg)); break; 74 | case 7: mix(GREEDILY_READ(7,msg)); break; 75 | case 6: mix(GREEDILY_READ(6,msg)); break; 76 | case 5: mix(GREEDILY_READ(5,msg)); break; 77 | case 4: mix((__m128i)_mm_load_ss((const float*)msg)); break; 78 | case 3: mix(GREEDILY_READ(3,msg)); break; 79 | case 2: mix(GREEDILY_READ(2,msg)); break; 80 | case 1: mix(GREEDILY_READ(1,msg)); break; 81 | case 0: 82 | default: // try to keep m & s from register spilling 83 | a = _mm_add_epi8(a, s); 84 | b = _mm_add_epi8(b, m); 85 | } 86 | #undef GREEDILY_READ 87 | return _mm_aesenc_si128(a, b); 88 | } 89 | 90 | uint64_t x = 0; 91 | switch (len) { 92 | case 15: 93 | x |= ((uint64_t)msg[14]) << 48U; 94 | case 14: 95 | x |= ((uint64_t)msg[13]) << 40U; 96 | case 13: 97 | x |= ((uint64_t)msg[12]) << 32U; 98 | case 12: 99 | x |= *(const uint32_t*)(msg + 8); 100 | mix(_mm_set_epi64x(x, *(const uint64_t*)msg)); 101 | break; 102 | case 11: 103 | x |= ((uint32_t)msg[10]) << 16U; 104 | case 10: 105 | x |= ((uint32_t)msg[9]) << 8U; 106 | case 9: 107 | x |= msg[8]; 108 | case 8: 109 | mix(_mm_set_epi64x(x, *(const uint64_t*)msg)); 110 | break; 111 | case 7: 112 | x |= ((uint64_t)msg[6]) << 48U; 113 | case 6: 114 | x |= ((uint64_t)msg[5]) << 40U; 115 | case 5: 116 | x |= ((uint64_t)msg[4]) << 32U; 117 | case 4: 118 | x |= *(const uint32_t*)msg; 119 | mix(_mm_set_epi64x(0, x)); 120 | break; 121 | case 3: 122 | x |= ((uint32_t)msg[2]) << 16U; 123 | case 2: 124 | x |= ((uint32_t)msg[1]) << 8U; 125 | case 1: 126 | x |= msg[0]; 127 | mix(_mm_set_epi64x(0, x)); 128 | break; 129 | case 0: 130 | default: // try to keep m & s from register spilling 131 | a = _mm_add_epi8(a, s); 132 | b = _mm_add_epi8(b, m); 133 | 134 | } 135 | return _mm_aesenc_si128(a, b); 136 | } 137 | 138 | static inline uint64_t AESNI_Hash64(const uint8_t* msg, unsigned len, uint32_t seed = 0) { 139 | union { 140 | uint64_t x[2]; 141 | __m128i v; 142 | } t; 143 | t.v = AESNI_Hash128(msg, len, seed); 144 | return t.x[0]; 145 | } 146 | 147 | #ifdef __cplusplus 148 | } // C 149 | #endif 150 | 151 | #endif // AESNI_HASH_H -------------------------------------------------------------------------------- /java/src/main/java/com/github/peterrk/pbf/Hash.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.github.peterrk.pbf; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.ByteOrder; 9 | 10 | public class Hash { 11 | public static final class V128 { 12 | public long low; 13 | public long high; 14 | 15 | V128(long low, long high) { 16 | this.low = low; 17 | this.high = high; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if(obj == this) { 23 | return true; 24 | } 25 | if(!(obj instanceof V128)) { 26 | return false; 27 | } 28 | V128 other = (V128)obj; 29 | return low == other.low && high == other.high; 30 | } 31 | } 32 | 33 | 34 | private static long rot(long x, int k) { 35 | return (x << k) | (x >>> (64 - k)); 36 | } 37 | private static final class State { 38 | long a; 39 | long b; 40 | long c; 41 | long d; 42 | 43 | State(long a, long b, long c, long d) { 44 | this.a = a; 45 | this.b = b; 46 | this.c = c; 47 | this.d = d; 48 | } 49 | 50 | void mix() { 51 | c = rot(c,50); c += d; a ^= c; 52 | d = rot(d,52); d += a; b ^= d; 53 | a = rot(a,30); a += b; c ^= a; 54 | b = rot(b,41); b += c; d ^= b; 55 | c = rot(c,54); c += d; a ^= c; 56 | d = rot(d,48); d += a; b ^= d; 57 | a = rot(a,38); a += b; c ^= a; 58 | b = rot(b,37); b += c; d ^= b; 59 | c = rot(c,62); c += d; a ^= c; 60 | d = rot(d,34); d += a; b ^= d; 61 | a = rot(a,5); a += b; c ^= a; 62 | b = rot(b,36); b += c; d ^= b; 63 | } 64 | 65 | void end() { 66 | d ^= c; c = rot(c,15); d += c; 67 | a ^= d; d = rot(d,52); a += d; 68 | b ^= a; a = rot(a,26); b += a; 69 | c ^= b; b = rot(b,51); c += b; 70 | d ^= c; c = rot(c,28); d += c; 71 | a ^= d; d = rot(d,9); a += d; 72 | b ^= a; a = rot(a,47); b += a; 73 | c ^= b; b = rot(b,54); c += b; 74 | d ^= c; c = rot(c,32); d += c; 75 | a ^= d; d = rot(d,25); a += d; 76 | b ^= a; a = rot(a,63); b += a; 77 | } 78 | } 79 | 80 | public static V128 hash128(byte[] key) { 81 | long magic = 0xdeadbeefdeadbeefL; 82 | State s = new State(0, 0, magic, magic); 83 | 84 | ByteBuffer buf = ByteBuffer.wrap(key).order(ByteOrder.LITTLE_ENDIAN); 85 | while (buf.remaining() >= 32) { 86 | s.c += buf.getLong(); 87 | s.d += buf.getLong(); 88 | s.mix(); 89 | s.a += buf.getLong(); 90 | s.b += buf.getLong(); 91 | } 92 | if (buf.remaining() >= 16) { 93 | s.c += buf.getLong(); 94 | s.d += buf.getLong(); 95 | s.mix(); 96 | } 97 | 98 | s.d += ((long)key.length) << 56; 99 | switch (key.length & 0xf) { 100 | case 15: 101 | s.d += ((long)buf.get(buf.position()+14) & 0xff) << 48; 102 | case 14: 103 | s.d += ((long)buf.get(buf.position()+13) & 0xff) << 40; 104 | case 13: 105 | s.d += ((long)buf.get(buf.position()+12) & 0xff) << 32; 106 | case 12: 107 | s.c += buf.getLong(); 108 | s.d += buf.getInt() & 0xffffffffL; 109 | break; 110 | case 11: 111 | s.d += ((long)buf.get(buf.position()+10) & 0xff) << 16; 112 | case 10: 113 | s.d += ((long)buf.get(buf.position()+9) & 0xff) << 8; 114 | case 9: 115 | s.d += (long)buf.get(buf.position()+8) & 0xff; 116 | case 8: 117 | s.c += buf.getLong(); 118 | break; 119 | case 7: 120 | s.c += ((long)buf.get(buf.position()+6) & 0xff) << 48; 121 | case 6: 122 | s.c += ((long)buf.get(buf.position()+5) & 0xff) << 40; 123 | case 5: 124 | s.c += ((long)buf.get(buf.position()+4) & 0xff) << 32; 125 | case 4: 126 | s.c += (long)buf.getInt() & 0xffffffffL; 127 | break; 128 | case 3: 129 | s.c += ((long)buf.get(buf.position()+2) & 0xff) << 16; 130 | case 2: 131 | s.c += ((long)buf.get(buf.position()+1) & 0xff) << 8; 132 | case 1: 133 | s.c += (long)buf.get() & 0xff; 134 | break; 135 | case 0: 136 | s.c += magic; 137 | s.d += magic; 138 | } 139 | s.end(); 140 | return new V128(s.a, s.b); 141 | } 142 | 143 | public static long hash64(byte[] key) { 144 | return hash128(key).low; 145 | } 146 | 147 | public static int hash32(byte[] key) { 148 | return (int)hash64(key); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter/Hash.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | namespace PageBloomFilter { 6 | public sealed class Hash { 7 | public struct V128 { 8 | public ulong low; 9 | public ulong high; 10 | public V128(ulong low, ulong high) { 11 | this.low = low; 12 | this.high = high; 13 | } 14 | } 15 | 16 | static Hash() { 17 | System.Diagnostics.Trace.Assert(BitConverter.IsLittleEndian); 18 | } 19 | 20 | private static ulong Rot(ulong x, int k) { 21 | return (x << k) | (x >>> (64 - k)); 22 | } 23 | private struct State { 24 | public ulong a, b, c, d; 25 | 26 | public State(ulong a, ulong b, ulong c, ulong d) { 27 | this.a = a; 28 | this.b = b; 29 | this.c = c; 30 | this.d = d; 31 | } 32 | 33 | public void Mix() { 34 | c = Rot(c, 50); c += d; a ^= c; 35 | d = Rot(d, 52); d += a; b ^= d; 36 | a = Rot(a, 30); a += b; c ^= a; 37 | b = Rot(b, 41); b += c; d ^= b; 38 | c = Rot(c, 54); c += d; a ^= c; 39 | d = Rot(d, 48); d += a; b ^= d; 40 | a = Rot(a, 38); a += b; c ^= a; 41 | b = Rot(b, 37); b += c; d ^= b; 42 | c = Rot(c, 62); c += d; a ^= c; 43 | d = Rot(d, 34); d += a; b ^= d; 44 | a = Rot(a, 5); a += b; c ^= a; 45 | b = Rot(b, 36); b += c; d ^= b; 46 | } 47 | 48 | public void End() { 49 | d ^= c; c = Rot(c, 15); d += c; 50 | a ^= d; d = Rot(d, 52); a += d; 51 | b ^= a; a = Rot(a, 26); b += a; 52 | c ^= b; b = Rot(b, 51); c += b; 53 | d ^= c; c = Rot(c, 28); d += c; 54 | a ^= d; d = Rot(d, 9); a += d; 55 | b ^= a; a = Rot(a, 47); b += a; 56 | c ^= b; b = Rot(b, 54); c += b; 57 | d ^= c; c = Rot(c, 32); d += c; 58 | a ^= d; d = Rot(d, 25); a += d; 59 | b ^= a; a = Rot(a, 63); b += a; 60 | } 61 | } 62 | 63 | public static V128 Hash128(ReadOnlySpan key) { 64 | ulong magic = 0xdeadbeefdeadbeefUL; 65 | var s = new State(0, 0, magic, magic); 66 | 67 | int off = 0; 68 | for (int end = key.Length & ~0x1f; off < end; off += 32) { 69 | s.c += BitConverter.ToUInt64(key[off..]); 70 | s.d += BitConverter.ToUInt64(key[(off + 8)..]); 71 | s.Mix(); 72 | s.a += BitConverter.ToUInt64(key[(off + 16)..]); 73 | s.b += BitConverter.ToUInt64(key[(off + 24)..]); 74 | } 75 | if (key.Length - off >= 16) { 76 | s.c += BitConverter.ToUInt64(key[off..]); 77 | s.d += BitConverter.ToUInt64(key[(off + 8)..]); 78 | s.Mix(); 79 | off += 16; 80 | } 81 | 82 | s.d += ((ulong)key.Length) << 56; 83 | switch (key.Length & 0xf) { 84 | case 15: 85 | s.d += ((ulong)key[off + 14]) << 48; 86 | goto case 14; 87 | case 14: 88 | s.d += ((ulong)key[off + 13]) << 40; 89 | goto case 13; 90 | case 13: 91 | s.d += ((ulong)key[off + 12]) << 32; 92 | goto case 12; 93 | case 12: 94 | s.d += BitConverter.ToUInt32(key[(off + 8)..]); 95 | s.c += BitConverter.ToUInt64(key[off..]); 96 | break; 97 | case 11: 98 | s.d += ((ulong)key[off + 10]) << 16; 99 | goto case 10; 100 | case 10: 101 | s.d += ((ulong)key[off + 9]) << 8; 102 | goto case 9; 103 | case 9: 104 | s.d += key[off + 8]; 105 | goto case 8; 106 | case 8: 107 | s.c += BitConverter.ToUInt64(key[off..]); 108 | break; 109 | case 7: 110 | s.c += ((ulong)key[off + 6]) << 48; 111 | goto case 6; 112 | case 6: 113 | s.c += ((ulong)key[off + 5]) << 40; 114 | goto case 5; 115 | case 5: 116 | s.c += ((ulong)key[off + 4]) << 32; 117 | goto case 4; 118 | case 4: 119 | s.c += BitConverter.ToUInt32(key[off..]); 120 | break; 121 | case 3: 122 | s.c += ((ulong)key[off + 2]) << 16; 123 | goto case 2; 124 | case 2: 125 | s.c += ((ulong)key[off + 1]) << 8; 126 | goto case 1; 127 | case 1: 128 | s.c += key[off]; 129 | break; 130 | case 0: 131 | s.c += magic; 132 | s.d += magic; 133 | break; 134 | } 135 | s.End(); 136 | return new V128(s.a, s.b); 137 | } 138 | 139 | public static ulong Hash64(ReadOnlySpan key) { 140 | return Hash128(key).low; 141 | } 142 | 143 | public static uint Hash32(ReadOnlySpan key) { 144 | return (uint)Hash64(key); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /java/src/test/java/com/github/peterrk/pbf/BloomFilterBenchmark.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.github.peterrk.pbf; 6 | 7 | import org.openjdk.jmh.annotations.*; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @Fork(value = 1) 14 | @BenchmarkMode(Mode.AverageTime) 15 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 16 | @Warmup(iterations = 1, time = 3, timeUnit = TimeUnit.SECONDS) 17 | @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 18 | public class BloomFilterBenchmark { 19 | 20 | private static final long N = 1000000; 21 | private static final double falsePositiveRate = 0.01; 22 | 23 | public static void main(String[] args) throws Exception { 24 | org.openjdk.jmh.Main.main(args); 25 | } 26 | 27 | @Benchmark 28 | public void pbfSet(PageBloomFilterSetState ctx) { 29 | byte[] key = new byte[8]; 30 | for (long i = 0; i < N; i++) { 31 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 32 | ctx.bf.set(key); 33 | } 34 | } 35 | 36 | @Benchmark 37 | public void pbfTest(PageBloomFilterTestState ctx) { 38 | byte[] key = new byte[8]; 39 | for (long i = 0; i < N; i++) { 40 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 41 | ctx.bf.test(key); 42 | } 43 | } 44 | 45 | @Benchmark 46 | public void guavaSet(GuavaSetState ctx) { 47 | byte[] key = new byte[8]; 48 | for (long i = 0; i < N; i++) { 49 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 50 | ctx.bf.put(key); 51 | } 52 | } 53 | 54 | @Benchmark 55 | public void guavaTest(GuavaTestState ctx) { 56 | byte[] key = new byte[8]; 57 | for (long i = 0; i < N; i++) { 58 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 59 | ctx.bf.mightContain(key); 60 | } 61 | } 62 | 63 | @Benchmark 64 | public void nikitinSet(NikitinSetState ctx) { 65 | byte[] key = new byte[8]; 66 | for (long i = 0; i < N; i++) { 67 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 68 | ctx.bf.add(key); 69 | } 70 | } 71 | 72 | @Benchmark 73 | public void nikitinTest(NikitinTestState ctx) { 74 | byte[] key = new byte[8]; 75 | for (long i = 0; i < N; i++) { 76 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 77 | ctx.bf.mightContain(key); 78 | } 79 | } 80 | 81 | @State(Scope.Thread) 82 | public static class PageBloomFilterSetState { 83 | private PageBloomFilter bf; 84 | 85 | @Setup(value = Level.Trial) 86 | public void setup() { 87 | bf = PageBloomFilter.New(N, 0.01); 88 | } 89 | 90 | @TearDown(value = Level.Invocation) 91 | public void tearDown() { 92 | bf.clear(); 93 | } 94 | } 95 | 96 | @State(Scope.Thread) 97 | public static class PageBloomFilterTestState { 98 | private PageBloomFilter bf; 99 | 100 | @Setup(value = Level.Trial) 101 | public void setup() { 102 | bf = PageBloomFilter.New(N, falsePositiveRate); 103 | byte[] key = new byte[8]; 104 | for (long i = 0; i < N; i += 2) { 105 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 106 | bf.set(key); 107 | } 108 | } 109 | } 110 | 111 | @State(Scope.Thread) 112 | public static class GuavaSetState { 113 | private com.google.common.hash.BloomFilter bf; 114 | 115 | @Setup(value = Level.Trial) 116 | public void setup() { 117 | bf = com.google.common.hash.BloomFilter.create( 118 | com.google.common.hash.Funnels.byteArrayFunnel(), N, falsePositiveRate); 119 | } 120 | } 121 | 122 | @State(Scope.Thread) 123 | public static class GuavaTestState { 124 | private com.google.common.hash.BloomFilter bf; 125 | 126 | @Setup(value = Level.Trial) 127 | public void setup() { 128 | bf = com.google.common.hash.BloomFilter.create( 129 | com.google.common.hash.Funnels.byteArrayFunnel(), N, falsePositiveRate); 130 | byte[] key = new byte[8]; 131 | for (long i = 0; i < N; i += 2) { 132 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 133 | bf.put(key); 134 | } 135 | } 136 | } 137 | 138 | @State(Scope.Thread) 139 | public static class NikitinSetState { 140 | private bloomfilter.mutable.BloomFilter bf; 141 | 142 | @Setup(value = Level.Trial) 143 | public void setup() { 144 | bf = bloomfilter.mutable.BloomFilter.apply( 145 | N, falsePositiveRate, 146 | bloomfilter.CanGenerateHashFrom.CanGenerateHashFromByteArray$.MODULE$); 147 | } 148 | } 149 | 150 | @State(Scope.Thread) 151 | public static class NikitinTestState { 152 | private bloomfilter.mutable.BloomFilter bf; 153 | 154 | @Setup(value = Level.Trial) 155 | public void setup() { 156 | bf = bloomfilter.mutable.BloomFilter.apply( 157 | N, falsePositiveRate, 158 | bloomfilter.CanGenerateHashFrom.CanGenerateHashFromByteArray$.MODULE$); 159 | byte[] key = new byte[8]; 160 | for (long i = 0; i < N; i += 2) { 161 | ByteBuffer.wrap(key).order(ByteOrder.nativeOrder()).putLong(i); 162 | bf.add(key); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /rust/src/hash.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | use std::num::Wrapping; 6 | 7 | #[inline(always)] 8 | fn rot(x: Wrapping, k: u8) -> Wrapping { 9 | return Wrapping((x.0 << k) | (x.0 >> (64 - k))); 10 | } 11 | 12 | struct State { 13 | a: Wrapping, 14 | b: Wrapping, 15 | c: Wrapping, 16 | d: Wrapping, 17 | } 18 | 19 | impl State { 20 | #[inline(always)] 21 | pub fn mix(&mut self) { 22 | self.c = rot(self.c,50); self.c += self.d; self.a ^= self.c; 23 | self.d = rot(self.d,52); self.d += self.a; self.b ^= self.d; 24 | self.a = rot(self.a,30); self.a += self.b; self.c ^= self.a; 25 | self.b = rot(self.b,41); self.b += self.c; self.d ^= self.b; 26 | self.c = rot(self.c,54); self.c += self.d; self.a ^= self.c; 27 | self.d = rot(self.d,48); self.d += self.a; self.b ^= self.d; 28 | self.a = rot(self.a,38); self.a += self.b; self.c ^= self.a; 29 | self.b = rot(self.b,37); self.b += self.c; self.d ^= self.b; 30 | self.c = rot(self.c,62); self.c += self.d; self.a ^= self.c; 31 | self.d = rot(self.d,34); self.d += self.a; self.b ^= self.d; 32 | self.a = rot(self.a,5); self.a += self.b; self.c ^= self.a; 33 | self.b = rot(self.b,36); self.b += self.c; self.d ^= self.b; 34 | } 35 | 36 | #[inline(always)] 37 | pub fn end(&mut self) { 38 | self.d ^= self.c; self.c = rot(self.c,15); self.d += self.c; 39 | self.a ^= self.d; self.d = rot(self.d,52); self.a += self.d; 40 | self.b ^= self.a; self.a = rot(self.a,26); self.b += self.a; 41 | self.c ^= self.b; self.b = rot(self.b,51); self.c += self.b; 42 | self.d ^= self.c; self.c = rot(self.c,28); self.d += self.c; 43 | self.a ^= self.d; self.d = rot(self.d,9); self.a += self.d; 44 | self.b ^= self.a; self.a = rot(self.a,47); self.b += self.a; 45 | self.c ^= self.b; self.b = rot(self.b,54); self.c += self.b; 46 | self.d ^= self.c; self.c = rot(self.c,32); self.d += self.c; 47 | self.a ^= self.d; self.d = rot(self.d,25); self.a += self.d; 48 | self.b ^= self.a; self.a = rot(self.a,63); self.b += self.a; 49 | } 50 | } 51 | 52 | #[inline(always)] 53 | fn u64to64(data: &[u8]) -> u64 { 54 | return u64::from_le_bytes(data.try_into().unwrap()); 55 | } 56 | 57 | #[inline(always)] 58 | fn u32to64(data: &[u8]) -> u64 { 59 | return u32::from_le_bytes(data.try_into().unwrap()) as u64; 60 | } 61 | 62 | #[inline(always)] 63 | fn u16to64(data: &[u8]) -> u64 { 64 | return u16::from_le_bytes(data.try_into().unwrap()) as u64; 65 | } 66 | 67 | pub fn hash128(msg: &[u8]) -> [u64; 2] { 68 | let magic = Wrapping(0xdeadbeefdeadbeef_u64); 69 | let mut s = State {a: Wrapping(0_u64), b: Wrapping(0_u64), c: magic, d: magic}; 70 | 71 | let mut off = 0_usize; 72 | while off < (msg.len() & !0x1f_usize) { 73 | s.c += u64to64(&msg[off .. off+8]); 74 | s.d += u64to64(&msg[off+8 .. off+16]); 75 | s.mix(); 76 | s.a += u64to64(&msg[off+16 .. off+24]); 77 | s.b += u64to64(&msg[off+24 .. off+32]); 78 | off += 32; 79 | } 80 | if msg.len() - off >= 16 { 81 | s.c += u64to64(&msg[off .. off+8]); 82 | s.d += u64to64(&msg[off+8 .. off+16]); 83 | s.mix(); 84 | off += 16; 85 | } 86 | 87 | s.d += (msg.len() as u64) << 56; 88 | match msg.len() & 0xf_usize { 89 | 15 => { 90 | s.c += u64to64(&msg[off .. off+8]); 91 | s.d += ((msg[off+14] as u64) << 48) 92 | | (u16to64(&msg[off+12 .. off+14]) << 32) 93 | | u32to64(&msg[off+8 .. off+12]); 94 | }, 95 | 14 => { 96 | s.c += u64to64(&msg[off .. off+8]); 97 | s.d += (u16to64(&msg[off+12 .. off+14]) << 32) 98 | | u32to64(&msg[off+8 .. off+12]); 99 | }, 100 | 13 => { 101 | s.c += u64to64(&msg[off .. off+8]); 102 | s.d += ((msg[off+12] as u64) << 32) | u32to64(&msg[off+8 .. off+12]); 103 | }, 104 | 12 => { 105 | s.c += u64to64(&msg[off .. off+8]); 106 | s.d += u32to64(&msg[off+8 .. off+12]); 107 | }, 108 | 11 => { 109 | s.c += u64to64(&msg[off .. off+8]); 110 | s.d += ((msg[off+10] as u64) << 16) | u16to64(&msg[off+8 .. off+10]); 111 | }, 112 | 10 => { 113 | s.c += u64to64(&msg[off .. off+8]); 114 | s.d += u16to64(&msg[off+8 .. off+10]); 115 | }, 116 | 9 => { 117 | s.c += u64to64(&msg[off .. off+8]); 118 | s.d += msg[off+8] as u64; 119 | }, 120 | 8 => { 121 | s.c += u64to64(&msg[off .. off+8]); 122 | }, 123 | 7 => { 124 | s.c += ((msg[off+6] as u64) << 48) 125 | | (u16to64(&msg[off+4 .. off+6]) << 32) 126 | | u32to64(&msg[off .. off+4]); 127 | }, 128 | 6 => { 129 | s.c += (u16to64(&msg[off+4 .. off+6]) << 32) 130 | | u32to64(&msg[off .. off+4]); 131 | }, 132 | 5 => { 133 | s.c += ((msg[off+4] as u64) << 32) | u32to64(&msg[off .. off+4]); 134 | }, 135 | 4 => { 136 | s.c += u32to64(&msg[off .. off+4]); 137 | }, 138 | 3 => { 139 | s.c += ((msg[off+2] as u64) << 16) | u16to64(&msg[off .. off+2]); 140 | }, 141 | 2 => { 142 | s.c += u16to64(&msg[off .. off+2]); 143 | }, 144 | 1 => { 145 | s.c += msg[off] as u64; 146 | }, 147 | 0 => { 148 | s.c += magic; 149 | s.d += magic; 150 | }, 151 | _ => {} 152 | } 153 | s.end(); 154 | return [s.a.0, s.b.0]; 155 | } -------------------------------------------------------------------------------- /go/pbf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pbf 6 | 7 | import ( 8 | "math" 9 | "reflect" 10 | "unsafe" 11 | ) 12 | 13 | type PageBloomFilter interface { 14 | Clear() 15 | Data() []byte 16 | Unique() int 17 | PageLevel() uint32 18 | Way() uint32 19 | Cap() int 20 | VirtualCap(fpr float64) int 21 | Set(key string) bool // return true if key not exists 22 | Test(key string) bool // return true if key exists 23 | } 24 | 25 | // item: number of items to hold 26 | // fpr: expected false positive rate, 0.0005-0.1 27 | // New clean PageBloomFilter with auto-selected parameters 28 | func NewBloomFilter(item int, fpr float64) PageBloomFilter { 29 | if item < 1 { 30 | item = 1 31 | } 32 | if fpr > 0.1 { 33 | fpr = 0.1 34 | } else if fpr < 0.0005 { 35 | fpr = 0.0005 36 | } 37 | w := -math.Log2(fpr) 38 | bpi := w / (math.Ln2 * 8) 39 | if w > 9 { 40 | x := w - 7 41 | bpi *= 1 + 0.0025*x*x 42 | } else if w > 3 { 43 | bpi *= 1.01 44 | } 45 | way := uint32(math.Round(w)) 46 | if way < 4 { 47 | way = 4 48 | } else if way > 8 { 49 | way = 8 50 | } 51 | 52 | n := uint64(bpi * float64(item)) 53 | pageLevel := uint32(0) 54 | for i := uint32(6); i < 12; i++ { 55 | if n < (1 << (i + 4)) { 56 | pageLevel = i 57 | if pageLevel < (8 - 8/way) { 58 | pageLevel++ 59 | } 60 | break 61 | } 62 | } 63 | if pageLevel == 0 { 64 | pageLevel = 12 65 | } 66 | 67 | m := (n + (1 << pageLevel) - 1) >> pageLevel 68 | if m > math.MaxInt32 { 69 | return nil 70 | } 71 | pageNum := uint32(m) 72 | return NewPageBloomFilter(way, pageLevel, pageNum) 73 | } 74 | 75 | // way: 4-8 76 | // pageLevel: log2(page size), 7-13 77 | // pageNum: number of pages 78 | // New clean PageBloomFilter 79 | func NewPageBloomFilter(way, pageLevel, pageNum uint32) PageBloomFilter { 80 | if way < 4 || way > 8 || pageNum == 0 || 81 | pageLevel < (8-8/way) || pageLevel > 13 { 82 | return nil 83 | } 84 | return cast(way, &pageBloomFilter{ 85 | pageLevel: pageLevel, 86 | pageNum: pageNum, 87 | uniqueCnt: 0, 88 | data: make([]byte, int(pageNum)< 8 || 113 | pageLevel < (8-8/way) || pageLevel > 13 || 114 | len(data) == 0 || len(data)%pageSize != 0 { 115 | return nil 116 | } 117 | temp := make([]byte, len(data)) 118 | copy(temp, data) 119 | return cast(way, &pageBloomFilter{ 120 | pageLevel: pageLevel, 121 | pageNum: uint32(len(data) / pageSize), 122 | uniqueCnt: uniqueCnt, 123 | data: temp, 124 | }) 125 | } 126 | 127 | type pageBloomFilter struct { 128 | pageLevel uint32 129 | pageNum uint32 130 | uniqueCnt int 131 | data []byte 132 | } 133 | 134 | // clear data 135 | func (bf *pageBloomFilter) Clear() { 136 | bf.uniqueCnt = 0 137 | slc := (*reflect.SliceHeader)(unsafe.Pointer(&bf.data)) 138 | var vec []uint64 139 | ref := (*reflect.SliceHeader)(unsafe.Pointer(&vec)) 140 | ref.Data = slc.Data 141 | ref.Len = slc.Len / 8 142 | ref.Cap = ref.Len 143 | for i := 0; i < len(vec); i++ { 144 | vec[i] = 0 145 | } 146 | } 147 | 148 | // get inner data 149 | func (bf *pageBloomFilter) Data() []byte { 150 | return bf.data 151 | } 152 | 153 | // approximate number of unique items 154 | func (bf *pageBloomFilter) Unique() int { 155 | return bf.uniqueCnt 156 | } 157 | 158 | // log2(page size) 159 | func (bf *pageBloomFilter) PageLevel() uint32 { 160 | return bf.pageLevel 161 | } 162 | 163 | func (bf *pageBloomFilter) virtualCap(way uint32, fpr float64) int { 164 | t := math.Log1p(-math.Pow(fpr, 1.0/float64(way))) / 165 | math.Log1p(-1.0/float64(len(bf.data)*8)) 166 | return int(int64(t) / int64(way)) 167 | } 168 | 169 | type pbfW4 struct { 170 | pageBloomFilter 171 | } 172 | 173 | func (bf *pbfW4) Way() uint32 { 174 | return 4 175 | } 176 | 177 | func (bf *pbfW4) Cap() int { 178 | return len(bf.data) * 8 / int(bf.Way()) 179 | } 180 | 181 | func (bf *pbfW4) VirtualCap(fpr float64) int { 182 | return bf.virtualCap(bf.Way(), fpr) 183 | } 184 | 185 | type pbfW5 struct { 186 | pageBloomFilter 187 | } 188 | 189 | func (bf *pbfW5) Way() uint32 { 190 | return 5 191 | } 192 | 193 | func (bf *pbfW5) Cap() int { 194 | return len(bf.data) * 8 / int(bf.Way()) 195 | } 196 | 197 | func (bf *pbfW5) VirtualCap(fpr float64) int { 198 | return bf.virtualCap(bf.Way(), fpr) 199 | } 200 | 201 | type pbfW6 struct { 202 | pageBloomFilter 203 | } 204 | 205 | func (bf *pbfW6) Way() uint32 { 206 | return 6 207 | } 208 | 209 | func (bf *pbfW6) Cap() int { 210 | return len(bf.data) * 8 / int(bf.Way()) 211 | } 212 | 213 | func (bf *pbfW6) VirtualCap(fpr float64) int { 214 | return bf.virtualCap(bf.Way(), fpr) 215 | } 216 | 217 | type pbfW7 struct { 218 | pageBloomFilter 219 | } 220 | 221 | func (bf *pbfW7) Way() uint32 { 222 | return 7 223 | } 224 | 225 | func (bf *pbfW7) Cap() int { 226 | return len(bf.data) * 8 / int(bf.Way()) 227 | } 228 | 229 | func (bf *pbfW7) VirtualCap(fpr float64) int { 230 | return bf.virtualCap(bf.Way(), fpr) 231 | } 232 | 233 | type pbfW8 struct { 234 | pageBloomFilter 235 | } 236 | 237 | func (bf *pbfW8) Way() uint32 { 238 | return 8 239 | } 240 | 241 | func (bf *pbfW8) Cap() int { 242 | return len(bf.data) * 8 / int(bf.Way()) 243 | } 244 | 245 | func (bf *pbfW8) VirtualCap(fpr float64) int { 246 | return bf.virtualCap(bf.Way(), fpr) 247 | } 248 | -------------------------------------------------------------------------------- /go/impl.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !amd64 || go1.21 6 | 7 | package pbf 8 | 9 | import ( 10 | "encoding/binary" 11 | "unsafe" 12 | ) 13 | 14 | func (bf *pbfW4) Set(key string) bool { 15 | return bf.set(4, key) 16 | } 17 | 18 | func (bf *pbfW4) Test(key string) bool { 19 | return bf.test(4, key) 20 | } 21 | 22 | func (bf *pbfW5) Set(key string) bool { 23 | return bf.set(5, key) 24 | } 25 | 26 | func (bf *pbfW5) Test(key string) bool { 27 | return bf.test(5, key) 28 | } 29 | 30 | func (bf *pbfW6) Set(key string) bool { 31 | return bf.set(6, key) 32 | } 33 | 34 | func (bf *pbfW6) Test(key string) bool { 35 | return bf.test(6, key) 36 | } 37 | 38 | func (bf *pbfW7) Set(key string) bool { 39 | return bf.set(7, key) 40 | } 41 | 42 | func (bf *pbfW7) Test(key string) bool { 43 | return bf.test(7, key) 44 | } 45 | 46 | func (bf *pbfW8) Set(key string) bool { 47 | return bf.set(8, key) 48 | } 49 | 50 | func (bf *pbfW8) Test(key string) bool { 51 | return bf.test(8, key) 52 | } 53 | 54 | func (bf *pageBloomFilter) set(way uint32, key string) bool { 55 | code, v := hash(key) 56 | page := bf.data[int(code%bf.pageNum)<>3] >> (idx & 7) 64 | page[idx>>3] |= bit 65 | } 66 | if hit != 0 { 67 | return false 68 | } 69 | bf.uniqueCnt++ 70 | return true 71 | } 72 | 73 | func (bf *pageBloomFilter) test(way uint32, key string) bool { 74 | code, v := hash(key) 75 | page := bf.data[int(code%bf.pageNum)<>3] & bit) == 0 { 82 | return false 83 | } 84 | } 85 | return true 86 | } 87 | 88 | func rot32(x uint32, k int) uint32 { 89 | return (x << k) | (x >> (32 - k)) 90 | } 91 | 92 | func hash(str string) (uint32, [8]uint16) { 93 | l, h := hash128(str) 94 | w := [4]uint32{uint32(l), uint32(l >> 32), uint32(h), uint32(h >> 32)} 95 | return rot32(w[0], 8) ^ rot32(w[1], 6) ^ rot32(w[2], 4) ^ rot32(w[3], 2), 96 | [8]uint16{ 97 | uint16(w[0]), uint16(w[0] >> 16), 98 | uint16(w[1]), uint16(w[1] >> 16), 99 | uint16(w[2]), uint16(w[2] >> 16), 100 | uint16(w[3]), uint16(w[3] >> 16), 101 | } 102 | } 103 | 104 | func rot64(x uint64, k int) uint64 { 105 | return (x << k) | (x >> (64 - k)) 106 | } 107 | 108 | type state struct { 109 | a, b, c, d uint64 110 | } 111 | 112 | func (s *state) mix() { 113 | s.c = rot64(s.c, 50) 114 | s.c += s.d 115 | s.a ^= s.c 116 | s.d = rot64(s.d, 52) 117 | s.d += s.a 118 | s.b ^= s.d 119 | s.a = rot64(s.a, 30) 120 | s.a += s.b 121 | s.c ^= s.a 122 | s.b = rot64(s.b, 41) 123 | s.b += s.c 124 | s.d ^= s.b 125 | s.c = rot64(s.c, 54) 126 | s.c += s.d 127 | s.a ^= s.c 128 | s.d = rot64(s.d, 48) 129 | s.d += s.a 130 | s.b ^= s.d 131 | s.a = rot64(s.a, 38) 132 | s.a += s.b 133 | s.c ^= s.a 134 | s.b = rot64(s.b, 37) 135 | s.b += s.c 136 | s.d ^= s.b 137 | s.c = rot64(s.c, 62) 138 | s.c += s.d 139 | s.a ^= s.c 140 | s.d = rot64(s.d, 34) 141 | s.d += s.a 142 | s.b ^= s.d 143 | s.a = rot64(s.a, 5) 144 | s.a += s.b 145 | s.c ^= s.a 146 | s.b = rot64(s.b, 36) 147 | s.b += s.c 148 | s.d ^= s.b 149 | } 150 | 151 | func (s *state) end() { 152 | s.d ^= s.c 153 | s.c = rot64(s.c, 15) 154 | s.d += s.c 155 | s.a ^= s.d 156 | s.d = rot64(s.d, 52) 157 | s.a += s.d 158 | s.b ^= s.a 159 | s.a = rot64(s.a, 26) 160 | s.b += s.a 161 | s.c ^= s.b 162 | s.b = rot64(s.b, 51) 163 | s.c += s.b 164 | s.d ^= s.c 165 | s.c = rot64(s.c, 28) 166 | s.d += s.c 167 | s.a ^= s.d 168 | s.d = rot64(s.d, 9) 169 | s.a += s.d 170 | s.b ^= s.a 171 | s.a = rot64(s.a, 47) 172 | s.b += s.a 173 | s.c ^= s.b 174 | s.b = rot64(s.b, 54) 175 | s.c += s.b 176 | s.d ^= s.c 177 | s.c = rot64(s.c, 32) 178 | s.d += s.c 179 | s.a ^= s.d 180 | s.d = rot64(s.d, 25) 181 | s.a += s.d 182 | s.b ^= s.a 183 | s.a = rot64(s.a, 63) 184 | s.b += s.a 185 | } 186 | 187 | func unsafeStrToBytes(s string) []byte { 188 | return *(*[]byte)(unsafe.Pointer( 189 | &struct { 190 | string 191 | Cap int 192 | }{s, len(s)}, 193 | )) 194 | } 195 | 196 | func u64to64(str string) uint64 { 197 | return binary.LittleEndian.Uint64(unsafeStrToBytes(str)) 198 | } 199 | 200 | func u32to64(str string) uint64 { 201 | return uint64(binary.LittleEndian.Uint32(unsafeStrToBytes(str))) 202 | } 203 | 204 | func u16to64(str string) uint64 { 205 | return uint64(binary.LittleEndian.Uint16(unsafeStrToBytes(str))) 206 | } 207 | 208 | func hash128(str string) (uint64, uint64) { 209 | const magic uint64 = 0xdeadbeefdeadbeef 210 | s := state{0, 0, magic, magic} 211 | l := uint64(len(str)) 212 | 213 | for ; len(str) >= 32; str = str[32:] { 214 | s.c += u64to64(str) 215 | s.d += u64to64(str[8:]) 216 | s.mix() 217 | s.a += u64to64(str[16:]) 218 | s.b += u64to64(str[24:]) 219 | } 220 | if len(str) >= 16 { 221 | s.c += u64to64(str) 222 | s.d += u64to64(str[8:]) 223 | s.mix() 224 | str = str[16:] 225 | } 226 | 227 | s.d += l << 56 228 | switch len(str) { 229 | case 15: 230 | s.d += uint64(str[14]) << 48 231 | fallthrough 232 | case 14: 233 | s.d += uint64(str[13]) << 40 234 | fallthrough 235 | case 13: 236 | s.d += uint64(str[12]) << 32 237 | fallthrough 238 | case 12: 239 | s.d += u32to64(str[8:]) 240 | s.c += u64to64(str) 241 | case 11: 242 | s.d += uint64(str[10]) << 16 243 | fallthrough 244 | case 10: 245 | s.d += uint64(str[9]) << 8 246 | fallthrough 247 | case 9: 248 | s.d += uint64(str[8]) 249 | fallthrough 250 | case 8: 251 | s.c += u64to64(str) 252 | case 7: 253 | s.c += uint64(str[6]) << 48 254 | fallthrough 255 | case 6: 256 | s.c += uint64(str[5]) << 40 257 | fallthrough 258 | case 5: 259 | s.c += uint64(str[4]) << 32 260 | fallthrough 261 | case 4: 262 | s.c += u32to64(str) 263 | case 3: 264 | s.c += uint64(str[2]) << 16 265 | fallthrough 266 | case 2: 267 | s.c += uint64(str[1]) << 8 268 | fallthrough 269 | case 1: 270 | s.c += uint64(str[0]) 271 | case 0: 272 | s.c += magic 273 | s.d += magic 274 | } 275 | s.end() 276 | return s.a, s.b 277 | } 278 | -------------------------------------------------------------------------------- /include/pbf.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #pragma once 6 | #ifndef PAGE_BLOOM_FILTER_H 7 | #define PAGE_BLOOM_FILTER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace pbf { 18 | 19 | //Lemire-Kaser-Kurz 20 | template 21 | class Divisor { 22 | private: 23 | static_assert(std::is_same::value || std::is_same::value 24 | || std::is_same::value, ""); 25 | Word m_val = 0; 26 | #ifndef DISABLE_SOFT_DIVIDE 27 | static constexpr unsigned BITWIDTH = sizeof(Word)*8; 28 | using DoubleWord = typename std::conditional::value, uint16_t, 29 | typename std::conditional::value, uint32_t, uint64_t>::type>::type; 30 | using QuaterWord = typename std::conditional::value, uint32_t, 31 | typename std::conditional::value, uint64_t, __uint128_t>::type>::type; 32 | DoubleWord m_fac = 0; 33 | #endif 34 | 35 | public: 36 | Word value() const noexcept { return m_val; } 37 | Divisor() noexcept = default; 38 | explicit Divisor(Word n) noexcept { *this = n; } 39 | 40 | Divisor& operator=(Word n) noexcept { 41 | m_val = n; 42 | #ifndef DISABLE_SOFT_DIVIDE 43 | if (n == 0) { 44 | m_fac = 0; 45 | } else { 46 | constexpr DoubleWord zero = 0; 47 | m_fac = (DoubleWord)~zero / n + 1; 48 | } 49 | #endif 50 | return *this; 51 | } 52 | 53 | Word div(Word m) const noexcept { 54 | #ifdef DISABLE_SOFT_DIVIDE 55 | return m / m_val; 56 | #else 57 | Word q = (m * (QuaterWord)m_fac) >> (BITWIDTH * 2); 58 | if (m_fac == 0) { 59 | q = m; 60 | } 61 | return q; 62 | #endif 63 | } 64 | 65 | Word mod(Word m) const noexcept { 66 | #ifdef DISABLE_SOFT_DIVIDE 67 | return m % m_val; 68 | #else 69 | return ((QuaterWord)m_val * (DoubleWord)(m * m_fac)) >> (BITWIDTH * 2); 70 | #endif 71 | } 72 | }; 73 | 74 | template 75 | static inline Word operator/(Word m, const Divisor& d) noexcept { 76 | return d.div(m); 77 | } 78 | 79 | template 80 | static inline Word operator%(Word m, const Divisor& d) noexcept { 81 | return d.mod(m); 82 | } 83 | 84 | 85 | class _PageBloomFilter { 86 | public: 87 | bool operator!() const noexcept { return m_space == nullptr; } 88 | unsigned page_level() const noexcept { return m_page_level; } 89 | unsigned page_num() const noexcept { return m_page_num.value(); } 90 | size_t unique_cnt() const noexcept { return m_unique_cnt; } 91 | const uint8_t* data() const noexcept { return m_space.get(); } 92 | size_t data_size() const noexcept { 93 | return static_cast(m_page_num.value()) << m_page_level; 94 | } 95 | void clear() noexcept; 96 | 97 | protected: 98 | unsigned m_page_level = 0; 99 | Divisor m_page_num; 100 | size_t m_unique_cnt = 0; 101 | std::unique_ptr m_space; 102 | 103 | void init(unsigned page_level, unsigned page_num, size_t unique_cnt, const uint8_t* data); 104 | }; 105 | 106 | template 107 | class PageBloomFilter final : public _PageBloomFilter { 108 | public: 109 | static_assert(N >= 4 && N <= 8, "N should be 4-8"); 110 | 111 | // page_level should be (8-8/N) ~ 13 112 | PageBloomFilter(unsigned page_level, unsigned page_num, size_t unique_cnt=0, const uint8_t* data=nullptr) { 113 | if (page_level < (8-8/N) || page_level > 13 || page_num == 0) { 114 | return; 115 | } 116 | init(page_level, page_num, unique_cnt, data); 117 | } 118 | 119 | // unique_cnt/capacity should be 50%-80% 120 | size_t capacity() const noexcept { 121 | return data_size() * 8 / N; 122 | } 123 | size_t virual_capacity(float fpr) const noexcept { 124 | auto t = std::log1p(-std::pow(static_cast(fpr), 1.0 / N)) / std::log1p(-1.0 / (data_size()*8)); 125 | return static_cast(t) / N; 126 | } 127 | unsigned way() const noexcept { return N; } 128 | 129 | bool test(const uint8_t* data, unsigned len) const noexcept; 130 | bool set(const uint8_t* data, unsigned len) noexcept; 131 | }; 132 | 133 | extern template class PageBloomFilter<4>; 134 | extern template class PageBloomFilter<5>; 135 | extern template class PageBloomFilter<6>; 136 | extern template class PageBloomFilter<7>; 137 | extern template class PageBloomFilter<8>; 138 | 139 | static constexpr unsigned BestWay(float fpr) noexcept { 140 | fpr = std::min(std::max(fpr, 0.0005f), 0.1f); 141 | auto n = static_cast(2.0f / fpr); 142 | n += 1; 143 | for (unsigned i = 1; i < 32; i++) { 144 | if ((n & (0xfffffffe << i)) == 0) { 145 | return std::min(std::max(i, 4u), 8u); 146 | } 147 | } 148 | return 0; 149 | } 150 | 151 | template 152 | static PageBloomFilter Create(size_t item, float fpr) { 153 | assert(N == BestWay(fpr)); 154 | item = std::max(item, 1UL); 155 | fpr = std::min(std::max(fpr, 0.0005f), 0.1f); 156 | auto w = -std::log2(fpr); 157 | auto bpi = w / (std::log(2) * 8); 158 | if (w > 9) { 159 | auto x = w - 7; 160 | bpi *= 1 + 0.0025*x*x; 161 | } else if (w > 3) { 162 | bpi *= 1.01; 163 | } 164 | auto n = static_cast(bpi * static_cast(item)); 165 | unsigned page_level = 0; 166 | for (unsigned i = 6; i < 12; i++) { 167 | if (n < (1UL << (i + 4))) { 168 | page_level = i; 169 | if (page_level < (8 - 8/N)) { 170 | page_level++; 171 | } 172 | break; 173 | } 174 | } 175 | if (page_level == 0) { 176 | page_level = 12; 177 | } 178 | size_t page_num = (n + (1UL << page_level) - 1) >> page_level; 179 | if (page_num > INT32_MAX) { 180 | page_num = 0; 181 | } 182 | return PageBloomFilter(page_level, page_num); 183 | } 184 | 185 | struct BloomFilter : public _PageBloomFilter { 186 | virtual ~BloomFilter() = default; 187 | virtual size_t capacity() const noexcept = 0; 188 | virtual size_t virual_capacity(float fpr) const noexcept = 0; 189 | virtual unsigned way() const noexcept = 0; 190 | virtual bool test(const uint8_t* data, unsigned len) const noexcept = 0; 191 | virtual bool set(const uint8_t* data, unsigned len) noexcept = 0; 192 | }; 193 | 194 | extern std::unique_ptr New(size_t item, float fpr); 195 | extern std::unique_ptr New(unsigned way, unsigned page_level, unsigned page_num, 196 | size_t unique_cnt=0, const uint8_t* data=nullptr); 197 | 198 | static inline std::unique_ptr New(unsigned way, unsigned page_level, 199 | const uint8_t* data, size_t data_size, size_t unique_cnt) { 200 | return New(way, page_level, data_size>>page_level, unique_cnt, data); 201 | } 202 | 203 | } //pbf 204 | 205 | #define NEW_BLOOM_FILTER(item, fpr) pbf::Create(item, fpr) 206 | 207 | #endif //PAGE_BLOOM_FILTER_H 208 | -------------------------------------------------------------------------------- /rust/src/pbf.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | use crate::hash::hash128; 6 | use std::cmp::min; 7 | use std::cmp::max; 8 | 9 | pub trait BloomFilter { 10 | fn get_way(&self) -> u8; 11 | fn get_page_level(&self) -> u8; 12 | fn get_page_num(&self) -> u32; 13 | fn get_unique_cnt(&self) -> usize; 14 | fn get_data(&self) -> &Vec; 15 | fn capacity(&self) -> usize; 16 | fn virtual_capacity(&self, fpr: f32) -> usize; 17 | fn clear(&mut self); 18 | fn valid(&self) -> bool; 19 | fn set(&mut self, key: &[u8]) -> bool; 20 | fn test(&self, key: &[u8]) -> bool; 21 | } 22 | 23 | pub fn new_bloom_filter(item: usize, fpr: f32) -> Box { 24 | let mut rate = fpr; 25 | if rate < 0.0005 { 26 | rate = 0.0005; 27 | } else if rate > 0.1 { 28 | rate = 0.1; 29 | } 30 | let w = -f32::log2(rate); 31 | let ln2 = f32::ln(2.0 as f32); 32 | let mut bpi = (w / (ln2 * 8.0)) as f64; 33 | if w > 9.0 { 34 | let x = (w - 7.0) as f64; 35 | bpi *= 1.0 + 0.0025*x*x; 36 | } else if w > 3.0 { 37 | bpi *= 1.01; 38 | } 39 | let way = min(max(f32::round(w) as u8, 4), 8); 40 | 41 | let n = (bpi * max(item, 1) as f64) as usize; 42 | let mut page_level = 0_u8; 43 | for i in 6..12 { 44 | if n < (1_usize << (i + 4)) { 45 | page_level = i; 46 | if page_level < (8 - 8/way) { 47 | page_level += 1; 48 | } 49 | break; 50 | } 51 | } 52 | if page_level == 0 { 53 | page_level = 12 54 | } 55 | 56 | let page_num = (n + (1 << page_level) - 1) >> page_level; 57 | if page_num > 0xffffffff { 58 | panic!("too many items"); 59 | } 60 | 61 | match way { 62 | 8 => Box::new(PageBloomFilter::<8>::new(page_level, page_num as u32)), 63 | 7 => Box::new(PageBloomFilter::<7>::new(page_level, page_num as u32)), 64 | 6 => Box::new(PageBloomFilter::<6>::new(page_level, page_num as u32)), 65 | 5 => Box::new(PageBloomFilter::<5>::new(page_level, page_num as u32)), 66 | 4 => Box::new(PageBloomFilter::<4>::new(page_level, page_num as u32)), 67 | _ => panic!("no way"), 68 | } 69 | } 70 | 71 | pub fn new_pbf(way: u8, page_level: u8, page_num: u32) -> Box { 72 | match way { 73 | 8 => Box::new(PageBloomFilter::<8>::new(page_level, page_num)), 74 | 7 => Box::new(PageBloomFilter::<7>::new(page_level, page_num)), 75 | 6 => Box::new(PageBloomFilter::<6>::new(page_level, page_num)), 76 | 5 => Box::new(PageBloomFilter::<5>::new(page_level, page_num)), 77 | 4 => Box::new(PageBloomFilter::<4>::new(page_level, page_num)), 78 | _ => panic!("no way"), 79 | } 80 | } 81 | 82 | pub fn restore_pbf(way: u8, page_level: u8, data: &Vec, unique_cnt: usize) -> Box { 83 | match way { 84 | 8 => Box::new(PageBloomFilter::<8>::recover(page_level, data, unique_cnt)), 85 | 7 => Box::new(PageBloomFilter::<7>::recover(page_level, data, unique_cnt)), 86 | 6 => Box::new(PageBloomFilter::<6>::recover(page_level, data, unique_cnt)), 87 | 5 => Box::new(PageBloomFilter::<5>::recover(page_level, data, unique_cnt)), 88 | 4 => Box::new(PageBloomFilter::<4>::recover(page_level, data, unique_cnt)), 89 | _ => panic!("no way"), 90 | } 91 | } 92 | 93 | pub struct PageBloomFilter { 94 | page_level: u8, 95 | page_num: u32, 96 | unique_cnt: usize, 97 | data: Vec, 98 | } 99 | 100 | impl BloomFilter for PageBloomFilter { 101 | fn get_way(&self) -> u8 { 102 | return W; 103 | } 104 | fn get_page_level(&self) -> u8 { 105 | return self.page_level; 106 | } 107 | fn get_page_num(&self) -> u32 { 108 | return self.page_num; 109 | } 110 | fn get_unique_cnt(&self) -> usize { 111 | return self.unique_cnt; 112 | } 113 | fn get_data(&self) -> &Vec { 114 | return &self.data; 115 | } 116 | 117 | fn capacity(&self) -> usize { 118 | return self.data.len() * 8 / W as usize; 119 | } 120 | fn virtual_capacity(&self, fpr: f32) -> usize { 121 | let t = f64::ln_1p(-f64::powf(fpr as f64, 1.0 / W as f64)) / 122 | f64::ln_1p(-1.0 / (self.data.len() * 8) as f64); 123 | return t as usize / W as usize; 124 | } 125 | 126 | fn clear(&mut self) { 127 | self.data.fill(0_u8); 128 | } 129 | fn valid(&self) -> bool { 130 | return self.data.len() != 0; 131 | } 132 | 133 | fn set(&mut self, key: &[u8]) -> bool { 134 | let (code, v) = page_hash(key); 135 | let off = ((code % self.page_num) as usize) << self.page_level; 136 | let mut hit = 1_u8; 137 | let mask = ((1 << (self.page_level + 3)) - 1) as u16; 138 | for i in 0..W { 139 | let idx = v[i as usize] & mask; 140 | let bit = 1_u8 << (idx & 7); 141 | hit &= self.data[off+(idx>>3) as usize] >> (idx & 7); 142 | self.data[off+(idx>>3) as usize] |= bit; 143 | } 144 | if hit != 0 { 145 | return false; 146 | } 147 | self.unique_cnt += 1; 148 | return true; 149 | } 150 | 151 | fn test(&self, key: &[u8]) -> bool { 152 | let (code, v) = page_hash(key); 153 | let off = ((code % self.page_num) as usize) << self.page_level; 154 | 155 | let mask = ((1 << (self.page_level + 3)) - 1) as u16; 156 | for i in 0..W { 157 | let idx = v[i as usize] & mask; 158 | let bit = 1_u8 << (idx & 7); 159 | if (self.data[off+(idx>>3) as usize] & bit) == 0 { 160 | return false; 161 | } 162 | } 163 | return true; 164 | } 165 | } 166 | 167 | impl PageBloomFilter { 168 | pub fn new(page_level: u8, page_num: u32) -> Self { 169 | if W < 4 || W > 8 { 170 | panic!("way should be 4-8"); 171 | } 172 | if page_level < (8-8/W) || page_level > 13 { 173 | panic!("pageLevel should be 7-13"); 174 | } 175 | if page_num <= 0 { 176 | panic!("pageNum should be positive"); 177 | } 178 | return Self { 179 | page_level: page_level, 180 | page_num: page_num, 181 | unique_cnt: 0, 182 | data: vec![0_u8; (page_num as usize) << page_level], 183 | }; 184 | } 185 | 186 | pub fn recover(page_level: u8, data: &Vec, unique_cnt: usize) -> Self { 187 | if W < 4 || W > 8 { 188 | panic!("way should be 4-8"); 189 | } 190 | if page_level < (8-8/W) || page_level > 13 { 191 | panic!("pageLevel should be 7-13"); 192 | } 193 | let page_size = 1_usize << page_level; 194 | if data.len() == 0 || data.len()%page_size != 0 { 195 | panic!("illegal data size"); 196 | } 197 | let page_num = data.len() / page_size; 198 | if page_num > 0xffffffff { 199 | panic!("too big data"); 200 | } 201 | return Self { 202 | page_level: page_level, 203 | page_num: page_num as u32, 204 | unique_cnt: unique_cnt, 205 | data: data.clone(), 206 | }; 207 | } 208 | } 209 | 210 | #[inline(always)] 211 | fn rot(x: u32, k: u8) -> u32 { 212 | return (x << k) | (x >> (32 - k)); 213 | } 214 | 215 | #[inline(always)] 216 | fn page_hash(key: &[u8]) -> (u32, [u16; 8]) { 217 | let code = hash128(key); 218 | let w = [ 219 | code[0] as u32, (code[0] >> 32) as u32, 220 | code[1] as u32, (code[1] >> 32) as u32, 221 | ]; 222 | return (rot(w[0], 8) ^ rot(w[1], 6) ^ rot(w[2], 4) ^ rot(w[3], 2), 223 | [ 224 | w[0] as u16, (w[0] >> 16) as u16, 225 | w[1] as u16, (w[1] >> 16) as u16, 226 | w[2] as u16, (w[2] >> 16) as u16, 227 | w[3] as u16, (w[3] >> 16) as u16, 228 | ]) 229 | } -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # PageBloomFilter 2 | 3 | 采用分页设计的布隆过滤器,兼顾存储密度与访问性能。 4 | 5 | ## C++ 6 | ```cpp 7 | auto bf = NEW_BLOOM_FILTER(500, 0.01); 8 | if (bf.set("Hello")) { 9 | std::cout << "set new Hello" << std::endl; 10 | } 11 | if (bf.test("Hello")) { 12 | std::cout << "find Hello" << std::endl; 13 | } 14 | ``` 15 | 在AESNI指令加持下,性能一骑绝尘,标准版也足够惊艳。 16 | ``` 17 | // U7-155H & Clang-18 18 | pbf-set: 10.9247 ns/op 19 | pbf-test: 6.0765 ns/op 20 | pbf-aesni-set: 8.3275 ns/op 21 | pbf-aesni-test: 4.3405 ns/op 22 | libbf-set: 36.6518 ns/op 23 | libbf-test: 31.7608 ns/op 24 | libbloom-set: 33.0665 ns/op 25 | libbloom-test: 13.5359 ns/op 26 | ``` 27 | 28 | ## Go 29 | 30 | ```go 31 | // import "github.com/PeterRK/PageBloomFilter/go" 32 | // 有效容量500,假阳率0.01 33 | bf := pbf.NewBloomFilter(500, 0.01) 34 | if bf.Set("Hello") { 35 | fmt.Println("set new Hello") 36 | } 37 | if bf.Test("Hello") { 38 | fmt.Println("find Hello") 39 | } 40 | ``` 41 | 42 | 除了原生实现,在AMD64环境中还提供基于**函数注入技术**的实现,具体而言就是将C函数编译后注入到Go程序中以免除CGO的调用开销。在Xeon-8374C上测试50万元素,Go注入版较原生版有一倍左右的性能提升,仅比C++版略慢。 43 | 44 | ``` 45 | name old time/op new time/op delta 46 | Set4 53.6ns ± 6% 26.5ns ± 6% -50.52% (p=0.000 n=20+20) 47 | Test4 40.5ns ± 5% 21.2ns ± 5% -47.63% (p=0.000 n=20+18) 48 | Set5 56.4ns ± 5% 28.0ns ± 5% -50.34% (p=0.000 n=20+19) 49 | Test5 41.5ns ± 3% 18.8ns ± 7% -54.72% (p=0.000 n=20+19) 50 | Set6 57.6ns ± 5% 29.1ns ± 5% -49.44% (p=0.000 n=20+20) 51 | Test6 42.2ns ± 4% 18.5ns ± 7% -56.22% (p=0.000 n=20+18) 52 | Set7 58.8ns ± 4% 30.8ns ± 9% -47.68% (p=0.000 n=20+20) 53 | Test7 43.9ns ± 6% 18.9ns ± 8% -56.98% (p=0.000 n=20+19) 54 | Set8 58.4ns ± 9% 32.4ns ± 5% -44.53% (p=0.000 n=20+19) 55 | Test8 44.8ns ± 2% 18.4ns ± 7% -58.86% (p=0.000 n=19+20) 56 | ``` 57 | 58 | AMD64环境中注入版默认开启,编译前最好先执行[go-inject.sh](go/go-inject.sh)生成新的注入函数。注入函数生成脚本依赖clang和binutils,以及python,建议在Linux环境执行。 59 | 60 | [测评](https://gist.github.com/PeterRK/b0df9e80caaaee1e9349e295cb435a67) 表明本实现比知名的 [bits-and-blooms](https://github.com/bits-and-blooms/bloom)和[Tyler Treat版](https://github.com/tylertreat/BoomFilters)要快2倍: 61 | ``` 62 | // i7-10710U & Go-1.20 63 | BenchmarkPageBloomFilterSet-6 1000000 32.70 ns/op 64 | BenchmarkPageBloomFilterTest-6 1000000 20.23 ns/op 65 | BenchmarkBitsAndBloomSet-6 1000000 120.5 ns/op 66 | BenchmarkBitsAndBloomTest-6 1000000 81.46 ns/op 67 | BenchmarkTylerTreatSet-6 1000000 98.30 ns/op 68 | BenchmarkTylerTreatTest-6 1000000 60.69 ns/op 69 | 70 | // U7-155H & Go-1.20 71 | BenchmarkPageBloomFilterSet-16 1000000 13.95 ns/op 72 | BenchmarkPageBloomFilterTest-16 1000000 8.40 ns/op 73 | BenchmarkBitsAndBloomSet-16 1000000 44.57 ns/op 74 | BenchmarkBitsAndBloomTest-16 1000000 37.94 ns/op 75 | BenchmarkTylerTreatSet-16 1000000 43.94 ns/op 76 | BenchmarkTylerTreatTest-16 1000000 20.80 ns/op 77 | 78 | // U7-155H & Go-1.24 (to fix: injection is broken since Go-1.21) 79 | BenchmarkPageBloomFilterSet-16 1000000 26.35 ns/op 80 | BenchmarkPageBloomFilterTest-16 1000000 23.97 ns/op 81 | ``` 82 | 83 | ## Java 84 | ```java 85 | PageBloomFilter bf = PageBloomFilter.New(500, 0.01); 86 | byte[] hello = "Hello".getBytes("UTF-8"); 87 | if (bf.set(hello)) { 88 | System.out.println("set new Hello"); 89 | } 90 | if (bf.test(hello)) { 91 | System.out.println("find Hello"); 92 | } 93 | ``` 94 | [测评](java/src/test/java/com/github/peterrk/pbf/BloomFilterBenchmark.java) 表明本实现比Google的[Guava](https://github.com/google/guava)要快很多,而有时稍逊于Alexandr Nikitin的[bloom-filter-scala](https://github.com/alexandrnikitin/bloom-filter-scala)。由于缺少针对性优化,Java版没有Go版快。 95 | ``` 96 | // i7-10710U & OpenJDK-17 97 | pbfSet 50.962 ns/op 98 | pbfTest 40.465 ns/op 99 | guavaSet 133.514 ns/op 100 | guavaTest 112.318 ns/op 101 | nikitinSet 86.931 ns/op 102 | nikitinTest 62.133 ns/op 103 | 104 | // U7-155H & OpenJDK-21 105 | pbfSet 24.562 ns/op 106 | pbfTest 20.511 ns/op 107 | guavaSet 44.889 ns/op 108 | guavaTest 45.652 ns/op 109 | nikitinSet 22.474 ns/op 110 | nikitinTest 18.489 ns/op 111 | ``` 112 | 113 | ## C# 114 | ```csharp 115 | var bf = PageBloomFilter.New(500, 0.01); 116 | var hello = Encoding.ASCII.GetBytes("Hello") 117 | if (bf.Set(hello)) { 118 | Console.WriteLine("set new Hello"); 119 | } 120 | if (bf.Test(hello)) { 121 | Console.WriteLine("find Hello"); 122 | } 123 | ``` 124 | C#版代码和Java版高度一致,不过跑出来要慢不少。性能胜过[BloomFilter.NetCore](https://github.com/vla/BloomFilter.NetCore)。 125 | ``` 126 | // i7-10710U & .NET-7 127 | pbf-set: 83.461274 ns/op 128 | pbf-test: 74.953785 ns/op 129 | 130 | // U7-155H & .NET-9 131 | pbf-set: 28.63103 ns/op 132 | pbf-test: 22.88545 ns/op 133 | bf.net-set: 41.66280 ns/op 134 | bf.net-test: 40.12608 ns/op 135 | ``` 136 | 137 | ## Python 138 | ```python 139 | bf = pbf.create(500, 0.01) 140 | if bf.set("Hello"): 141 | print("set new Hello") 142 | if bf.test("Hello"): 143 | print("find Hello") 144 | ``` 145 | Python版基于C扩展实现,虽然还是慢,不过足以吊打[pybloom](https://github.com/jaybaird/python-bloomfilter)。 146 | ``` 147 | // i7-10710U & Python-3.11 148 | pbf-set: 307.835638 ns/op 149 | pbf-test: 289.679349 ns/op 150 | pybloom-set: 2770.372372 ns/op 151 | pybloom-test: 2417.377588 ns/op 152 | 153 | // U7-155H & Python-3.12 154 | pbf-set: 127.227066 ns/op 155 | pbf-test: 101.274176 ns/op 156 | ``` 157 | 158 | ## Rust 159 | ```rust 160 | let mut bf = pbf::new_bloom_filter(500, 0.01); 161 | let hello = "Hello".as_bytes(); 162 | if (bf.set(hello)) { 163 | println!("set new Hello"); 164 | } 165 | if (bf.test(hello)) { 166 | println!("find Hello"); 167 | } 168 | ``` 169 | Rust版也缺少针对性优化,照样快过Java。看上去比[fastbloom](https://github.com/tomtomwombat/fastbloom)和[rust-bloom-filter](https://github.com/jedisct1/rust-bloom-filter)强。 170 | ``` 171 | // i7-10710U & Rust-1.65 172 | pbf-set: 45.99ns/op 173 | pbf-test: 27.81ns/op 174 | 175 | // U7-155H & Rust-1.80 176 | pbf-set: 19.85ns/op 177 | pbf-test: 12.50ns/op 178 | fastbloom-set: 19.97ns/op 179 | fastbloom-test: 14.93ns/op 180 | rbf-set: 36.51ns/op 181 | rbf-test: 24.93ns/op 182 | ``` 183 | 184 | ## 横向比较 185 | ![](images/U7-155H.png) 186 | 将在U7-155H上的测试数据放到一起看,可以得到性能排位:C++,Go,Rust,Java,C#,Python。 187 | 188 | ## 序列化与反序列化 189 | 不同语言实现的数据结构是一致(除了C++的aesni加强版),可以跨语言使用。虽然这里不提供专门的序列化和反序列化API,但也很容易实现:保存和加载`way`、`page_level`、`unique_cnt`三个参数,以及`data`的位图即可。其中`way`和`page_level`是个很小的整数, 可以分别用4bit表示。 190 | ```cpp 191 | // C++ 192 | auto bf = pbf::New(500, 0.01); 193 | auto bf2 = pbf::New(bf->way(), bf->page_level(), bf->data(), bf->data_size(), bf->unique_cnt()); 194 | 195 | // 示例格式(并非标准) 196 | struct Pack { 197 | uint32_t way : 4; 198 | uint32_t page_level : 4; 199 | uint32_t unique_cnt : 24; 200 | uint32_t data_size; 201 | uint8_t data[0]; 202 | }; 203 | ``` 204 | ```go 205 | // GO 206 | bf := pbf.NewBloomFilter(500, 0.01) 207 | bf2 := pbf.CreatePageBloomFilter(bf.Way(), bf.PageLevel(), bf.Data(), bf.Unique()) 208 | ``` 209 | ```java 210 | // Java 211 | PageBloomFilter bf = PageBloomFilter.New(500, 0.01); 212 | PageBloomFilter bf2 = PageBloomFilter.New(bf.getWay(), bf.getPageLevel(), bf.getData(), bf.getUniqueCnt()); 213 | ``` 214 | ```csharp 215 | // C# 216 | var bf = PageBloomFilter.New(500, 0.01); 217 | var bf2 = PageBloomFilter.New(bf.Way, bf.PageLevel, bf.Data, bf.UniqueCnt); 218 | ``` 219 | ```python 220 | # Python 221 | bf = pbf.create(500, 0.01) 222 | bf2 = pbf.restore(bf.way, bf.page_level, bf.data, bf.unique_cnt) 223 | ``` 224 | ```rust 225 | // Rust 226 | let mut bf = pbf::new_bloom_filter(500, 0.01); 227 | let mut bf2 = pbf::restore_pbf(bf.get_way(), bf.get_page_level(), bf.get_data(), bf.get_unique_cnt()); 228 | ``` 229 | 230 | ## 详细测试 231 | ![](images/Xeon-8374C.png) 232 | 在Xeon-8374C上测试50万元素,平均每次操作小于25ns,SIMD能有效加速查询操作。 233 | 234 | ![](images/EPYC-7K83.png) 235 | 在EPYC-7K83上测试表现略逊,SIMD加速效果不明显。 236 | 237 | ![](images/Xeon-8475B.png) 238 | 在Xeon-8475B上测试SIMD模式,使用aesni-hash可获得显著加速(**小于7ns的test操作**)。 239 | 240 | ![](images/EPYC-9T24.png) 241 | 在EPYC-9T24上测试SIMD模式,使用aesni-hash也可获得显著加速,但没有Intel平台上显著。 242 | 243 | ## 理论分析 244 | 245 | ### 每元素字节数与假阳率的关系 246 | ![](images/byte.png) 247 | 248 | ### 容积率与假阳率的关系 249 | ![](images/ratio.png) 250 | 251 | --- 252 | [【中文】](README-CN.md) [【英文】](README.md) 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PageBloomFilter 2 | 3 | Bloom filter with page, designed for storage density and query speed. 4 | 5 | ## C++ 6 | ```cpp 7 | auto bf = NEW_BLOOM_FILTER(500, 0.01); 8 | if (bf.set("Hello")) { 9 | std::cout << "set new Hello" << std::endl; 10 | } 11 | if (bf.test("Hello")) { 12 | std::cout << "find Hello" << std::endl; 13 | } 14 | ``` 15 | C++ implement runs extremely fast with aesni instruction. The standard compatible version is also good enough. 16 | ``` 17 | // U7-155H & Clang-18 18 | pbf-set: 10.9247 ns/op 19 | pbf-test: 6.0765 ns/op 20 | pbf-aesni-set: 8.3275 ns/op 21 | pbf-aesni-test: 4.3405 ns/op 22 | libbf-set: 36.6518 ns/op 23 | libbf-test: 31.7608 ns/op 24 | libbloom-set: 33.0665 ns/op 25 | libbloom-test: 13.5359 ns/op 26 | ``` 27 | 28 | ## Go 29 | 30 | ```go 31 | // import "github.com/PeterRK/PageBloomFilter/go" 32 | // BloomFilter with 0.01 false positive rate for 500 items 33 | bf := pbf.NewBloomFilter(500, 0.01) 34 | if bf.Set("Hello") { 35 | fmt.Println("set new Hello") 36 | } 37 | if bf.Test("Hello") { 38 | fmt.Println("find Hello") 39 | } 40 | ``` 41 | 42 | **Function injection techique** is avaliable for AMD64. It uses code compiled by clang in Go without CGO. A benchmark with 500k elements on a Xeon-8374C machine shows new implement runs much fast than the pure go implement. 43 | 44 | ``` 45 | name old time/op new time/op delta 46 | Set4 53.6ns ± 6% 26.5ns ± 6% -50.52% (p=0.000 n=20+20) 47 | Test4 40.5ns ± 5% 21.2ns ± 5% -47.63% (p=0.000 n=20+18) 48 | Set5 56.4ns ± 5% 28.0ns ± 5% -50.34% (p=0.000 n=20+19) 49 | Test5 41.5ns ± 3% 18.8ns ± 7% -54.72% (p=0.000 n=20+19) 50 | Set6 57.6ns ± 5% 29.1ns ± 5% -49.44% (p=0.000 n=20+20) 51 | Test6 42.2ns ± 4% 18.5ns ± 7% -56.22% (p=0.000 n=20+18) 52 | Set7 58.8ns ± 4% 30.8ns ± 9% -47.68% (p=0.000 n=20+20) 53 | Test7 43.9ns ± 6% 18.9ns ± 8% -56.98% (p=0.000 n=20+19) 54 | Set8 58.4ns ± 9% 32.4ns ± 5% -44.53% (p=0.000 n=20+19) 55 | Test8 44.8ns ± 2% 18.4ns ± 7% -58.86% (p=0.000 n=19+20) 56 | ``` 57 | 58 | We suggest that user should execute [go-inject.sh](go/go-inject.sh) to gnerate new injecting code before build. Clang, binutils and python are needed. 59 | 60 | [Benchmark](https://gist.github.com/PeterRK/b0df9e80caaaee1e9349e295cb435a67) shows it runs 2x time faster than other famous bloom filter implements, [bits-and-blooms](https://github.com/bits-and-blooms/bloom) and [Tyler Treat's](https://github.com/tylertreat/BoomFilters): 61 | ``` 62 | // i7-10710U & Go-1.20 63 | BenchmarkPageBloomFilterSet-6 1000000 32.70 ns/op 64 | BenchmarkPageBloomFilterTest-6 1000000 20.23 ns/op 65 | BenchmarkBitsAndBloomSet-6 1000000 120.5 ns/op 66 | BenchmarkBitsAndBloomTest-6 1000000 81.46 ns/op 67 | BenchmarkTylerTreatSet-6 1000000 98.30 ns/op 68 | BenchmarkTylerTreatTest-6 1000000 60.69 ns/op 69 | 70 | // U7-155H & Go-1.20 71 | BenchmarkPageBloomFilterSet-16 1000000 13.95 ns/op 72 | BenchmarkPageBloomFilterTest-16 1000000 8.40 ns/op 73 | BenchmarkBitsAndBloomSet-16 1000000 44.57 ns/op 74 | BenchmarkBitsAndBloomTest-16 1000000 37.94 ns/op 75 | BenchmarkTylerTreatSet-16 1000000 43.94 ns/op 76 | BenchmarkTylerTreatTest-16 1000000 20.80 ns/op 77 | 78 | // U7-155H & Go-1.24 (to fix: injection is broken since Go-1.21) 79 | BenchmarkPageBloomFilterSet-16 1000000 26.35 ns/op 80 | BenchmarkPageBloomFilterTest-16 1000000 23.97 ns/op 81 | ``` 82 | 83 | ## Java 84 | ```java 85 | PageBloomFilter bf = PageBloomFilter.New(500, 0.01); 86 | byte[] hello = "Hello".getBytes("UTF-8"); 87 | if (bf.set(hello)) { 88 | System.out.println("set new Hello"); 89 | } 90 | if (bf.test(hello)) { 91 | System.out.println("find Hello"); 92 | } 93 | ``` 94 | [Benchmark](java/src/test/java/com/github/peterrk/pbf/BloomFilterBenchmark.java) shows it runs much faster than Google's [Guava](https://github.com/google/guava), but sometimes a liitle slower than Alexandr Nikitin's [bloom-filter-scala](https://github.com/alexandrnikitin/bloom-filter-scala). We see Java version without dedicated optimization is inferior to the Go version. 95 | ``` 96 | // i7-10710U & OpenJDK-17 97 | pbfSet 50.962 ns/op 98 | pbfTest 40.465 ns/op 99 | guavaSet 133.514 ns/op 100 | guavaTest 112.318 ns/op 101 | nikitinSet 86.931 ns/op 102 | nikitinTest 62.133 ns/op 103 | 104 | // U7-155H & OpenJDK-21 105 | pbfSet 24.562 ns/op 106 | pbfTest 20.511 ns/op 107 | guavaSet 44.889 ns/op 108 | guavaTest 45.652 ns/op 109 | nikitinSet 22.474 ns/op 110 | nikitinTest 18.489 ns/op 111 | ``` 112 | 113 | ## C# 114 | ```csharp 115 | var bf = PageBloomFilter.New(500, 0.01); 116 | var hello = Encoding.ASCII.GetBytes("Hello") 117 | if (bf.Set(hello)) { 118 | Console.WriteLine("set new Hello"); 119 | } 120 | if (bf.Test(hello)) { 121 | Console.WriteLine("find Hello"); 122 | } 123 | ``` 124 | C# code is very similar to Java, but runs slower. It's faster than [BloomFilter.NetCore](https://github.com/vla/BloomFilter.NetCore). 125 | ``` 126 | // i7-10710U & .NET-7 127 | pbf-set: 83.461274 ns/op 128 | pbf-test: 74.953785 ns/op 129 | 130 | // U7-155H & .NET-9 131 | pbf-set: 28.63103 ns/op 132 | pbf-test: 22.88545 ns/op 133 | bf.net-set: 41.66280 ns/op 134 | bf.net-test: 40.12608 ns/op 135 | ``` 136 | 137 | ## Python 138 | ```python 139 | bf = pbf.create(500, 0.01) 140 | if bf.set("Hello"): 141 | print("set new Hello") 142 | if bf.test("Hello"): 143 | print("find Hello") 144 | ``` 145 | Python with c extension is still slow, but 8x time faster than [pybloom](https://github.com/jaybaird/python-bloomfilter). 146 | ``` 147 | // i7-10710U & Python-3.11 148 | pbf-set: 307.835638 ns/op 149 | pbf-test: 289.679349 ns/op 150 | pybloom-set: 2770.372372 ns/op 151 | pybloom-test: 2417.377588 ns/op 152 | 153 | // U7-155H & Python-3.12 154 | pbf-set: 127.227066 ns/op 155 | pbf-test: 101.274176 ns/op 156 | ``` 157 | 158 | ## Rust 159 | ```rust 160 | let mut bf = pbf::new_bloom_filter(500, 0.01); 161 | let hello = "Hello".as_bytes(); 162 | if (bf.set(hello)) { 163 | println!("set new Hello"); 164 | } 165 | if (bf.test(hello)) { 166 | println!("find Hello"); 167 | } 168 | ``` 169 | Rust verison is also lack of dedicated optimiztion, but faster than Java version a lot. It shows some advantage in performance against [fastbloom](https://github.com/tomtomwombat/fastbloom) and [rust-bloom-filter](https://github.com/jedisct1/rust-bloom-filter). 170 | ``` 171 | // i7-10710U & Rust-1.65 172 | pbf-set: 45.99ns/op 173 | pbf-test: 27.81ns/op 174 | 175 | // U7-155H & Rust-1.80 176 | pbf-set: 19.85ns/op 177 | pbf-test: 12.50ns/op 178 | fastbloom-set: 19.97ns/op 179 | fastbloom-test: 14.93ns/op 180 | rbf-set: 36.51ns/op 181 | rbf-test: 24.93ns/op 182 | ``` 183 | 184 | ## Ranking 185 | ![](images/U7-155H.png) 186 | With test data on U7-155H machine, we got performance rank: C++, Go, Rust, Java, C#, Python. 187 | 188 | ## Serialize & Deserialize 189 | Data structures of different implements, except C++ with aesni, are consistent, so you can do cross-language serializing and deserializing without dedicated serialize & deserialize APIs. Just save and restore 3 scalar parameters `way`, `page_level`, `unique_cnt`, and the `data` bitmap. Values of `way` and `page_level` are always tiny integers, which can be represented by 4 bit. 190 | ```cpp 191 | // C++ 192 | auto bf = pbf::New(500, 0.01); 193 | auto bf2 = pbf::New(bf->way(), bf->page_level(), bf->data(), bf->data_size(), bf->unique_cnt()); 194 | 195 | // A example (not standard) format 196 | struct Pack { 197 | uint32_t way : 4; 198 | uint32_t page_level : 4; 199 | uint32_t unique_cnt : 24; 200 | uint32_t data_size; 201 | uint8_t data[0]; 202 | }; 203 | ``` 204 | ```go 205 | // GO 206 | bf := pbf.NewBloomFilter(500, 0.01) 207 | bf2 := pbf.CreatePageBloomFilter(bf.Way(), bf.PageLevel(), bf.Data(), bf.Unique()) 208 | ``` 209 | ```java 210 | // Java 211 | PageBloomFilter bf = PageBloomFilter.New(500, 0.01); 212 | PageBloomFilter bf2 = PageBloomFilter.New(bf.getWay(), bf.getPageLevel(), bf.getData(), bf.getUniqueCnt()); 213 | ``` 214 | ```csharp 215 | // C# 216 | var bf = PageBloomFilter.New(500, 0.01); 217 | var bf2 = PageBloomFilter.New(bf.Way, bf.PageLevel, bf.Data, bf.UniqueCnt); 218 | ``` 219 | ```python 220 | # Python 221 | bf = pbf.create(500, 0.01) 222 | bf2 = pbf.restore(bf.way, bf.page_level, bf.data, bf.unique_cnt) 223 | ``` 224 | ```rust 225 | // Rust 226 | let mut bf = pbf::new_bloom_filter(500, 0.01); 227 | let mut bf2 = pbf::restore_pbf(bf.get_way(), bf.get_page_level(), bf.get_data(), bf.get_unique_cnt()); 228 | ``` 229 | 230 | ## Detail Benchmark 231 | ![](images/Xeon-8374C.png) 232 | We got average latency per operation under 25ns in a benchmark with 500k elements on a Xeon-8374C machine. SIMD brings significant speed-up. 233 | 234 | ![](images/EPYC-7K83.png) 235 | It runs slower on EPYC-7K83 machine. 236 | 237 | ![](images/Xeon-8475B.png) 238 | Running test with SIMD on Xeon-8475B machine, we found aesni-hash helps a lot (**amazing fast test operation under 7ns**). 239 | 240 | ![](images/EPYC-9T24.png) 241 | Running test with SIMD on EPYC-9T24 machine, we found aesni-hash helps a little. 242 | 243 | ## Theoretical Analysis 244 | 245 | ### Bytes per element - False positive rate 246 | ![](images/byte.png) 247 | 248 | ### Occupancy rate - False positive rate 249 | ![](images/ratio.png) 250 | 251 | --- 252 | [【Chinese】](README-CN.md) [【English】](README.md) 253 | -------------------------------------------------------------------------------- /java/src/main/java/com/github/peterrk/pbf/PageBloomFilter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package com.github.peterrk.pbf; 6 | 7 | import java.lang.Math; 8 | import java.util.Arrays; 9 | 10 | public abstract class PageBloomFilter { 11 | private final int pageLevel; 12 | private final int pageNum; 13 | private long uniqueCnt; 14 | private final byte[] data; 15 | 16 | public abstract int getWay(); 17 | public int getPageLevel() { return pageLevel; } 18 | public int getPageNum() { return pageNum; } 19 | 20 | public long getUniqueCnt() { return uniqueCnt; } 21 | public byte[] getData() { return data; } 22 | 23 | public long capacity() { 24 | return (long)data.length * 8 / getWay(); 25 | } 26 | public long virtualCapacity(double falsePositiveRate) { 27 | double t = Math.log1p(-Math.pow(falsePositiveRate, 1.0/getWay())) 28 | / Math.log1p(-1.0/(data.length * 8)); 29 | return (long)t / getWay(); 30 | } 31 | 32 | public abstract boolean set(byte[] key); 33 | public abstract boolean test(byte[] key); 34 | 35 | private static final double LN2 = Math.log(2); 36 | 37 | public static PageBloomFilter New(long item, double falsePositiveRate) { 38 | if (item < 1) { 39 | item = 1; 40 | } 41 | if (falsePositiveRate > 0.1) { 42 | falsePositiveRate = 0.1; 43 | } else if (falsePositiveRate < 0.0005) { 44 | falsePositiveRate = 0.0005; 45 | } 46 | double w = -Math.log(falsePositiveRate) / LN2; 47 | double bytesPerItem = w / (LN2 * 8); 48 | if (w > 9) { 49 | double x = w - 7; 50 | bytesPerItem *= 1 + 0.0025*x*x; 51 | } else if (w > 3) { 52 | bytesPerItem *= 1.01; 53 | } 54 | int way = Math.round((float)w); 55 | if (way < 4) { 56 | way = 4; 57 | } else if (way > 8) { 58 | way = 8; 59 | } 60 | 61 | long n = (long)(bytesPerItem * item); 62 | int pageLevel = 0; 63 | for (int i = 6; i < 12; i++) { 64 | if (n < (1L << (i + 4))) { 65 | pageLevel = i; 66 | if (pageLevel < (8 - 8/way)) { 67 | pageLevel++; 68 | } 69 | break; 70 | } 71 | } 72 | if (pageLevel == 0) { 73 | pageLevel = 12; 74 | } 75 | 76 | long pageNum = (n + (1L << pageLevel) - 1L) >> pageLevel; 77 | if (pageNum > Integer.MAX_VALUE) { 78 | throw new IllegalArgumentException("too many items"); 79 | } 80 | return New(way, pageLevel, (int)pageNum); 81 | } 82 | 83 | public static PageBloomFilter New(int way, int pageLevel, int pageNum) { 84 | switch (way) { 85 | case 4: return new PageBloomFilter.Way4(pageLevel, pageNum); 86 | case 5: return new PageBloomFilter.Way5(pageLevel, pageNum); 87 | case 6: return new PageBloomFilter.Way6(pageLevel, pageNum); 88 | case 7: return new PageBloomFilter.Way7(pageLevel, pageNum); 89 | case 8: return new PageBloomFilter.Way8(pageLevel, pageNum); 90 | default: throw new RuntimeException("illegal way"); 91 | } 92 | } 93 | 94 | protected PageBloomFilter(int way, int pageLevel, int pageNum) { 95 | if (way < 4 || way > 8) { 96 | throw new IllegalArgumentException("way should be 4-8"); 97 | } 98 | if (pageLevel < (8-8/way) || pageLevel > 13) { 99 | throw new IllegalArgumentException("pageLevel should be 7-13"); 100 | } 101 | if (pageNum <= 0) { 102 | throw new IllegalArgumentException("pageNum should be positive"); 103 | } 104 | this.pageLevel = pageLevel; 105 | this.pageNum = pageNum; 106 | this.uniqueCnt = 0; 107 | this.data = new byte[pageNum< 8) { 123 | throw new IllegalArgumentException("way should be 4-8"); 124 | } 125 | if (pageLevel < (8-8/way) || pageLevel > 13) { 126 | throw new IllegalArgumentException("pageLevel should be 7-13"); 127 | } 128 | int pageSize = 1 << pageLevel; 129 | if (data == null || data.length == 0 || data.length%pageSize != 0) { 130 | throw new IllegalArgumentException("illegal data size"); 131 | } 132 | this.pageLevel = pageLevel; 133 | this.pageNum = data.length / pageSize; 134 | this.uniqueCnt = uniqueCnt; 135 | this.data = data; 136 | } 137 | 138 | public void clear() { 139 | Arrays.fill(data, (byte)0); 140 | } 141 | 142 | private static int rot(int x, int k) { 143 | return (x << k) | (x >>> (32 - k)); 144 | } 145 | 146 | private static final class HashResult { 147 | int offset = 0; 148 | int[] codes = new int[8]; 149 | } 150 | private HashResult hash(byte[] key) { 151 | Hash.V128 code = Hash.hash128(key); 152 | int w0 = (int)code.low; 153 | int w1 = (int)(code.low>>>32); 154 | int w2 = (int)code.high; 155 | int w3 = (int)(code.high>>>32); 156 | HashResult ret = new HashResult(); 157 | long x = rot(w0, 8) ^ rot(w1, 6) ^ rot(w2, 4) ^ rot(w3, 2); 158 | ret.offset = (int)((x & 0xffffffffL) % pageNum); 159 | ret.offset <<= pageLevel; 160 | ret.codes[0] = w0 & 0xffff; 161 | ret.codes[1] = w0 >>> 16; 162 | ret.codes[2] = w1 & 0xffff; 163 | ret.codes[3] = w1 >>> 16; 164 | ret.codes[4] = w2 & 0xffff; 165 | ret.codes[5] = w2 >>> 16; 166 | ret.codes[6] = w3 & 0xffff; 167 | ret.codes[7] = w3 >>> 16; 168 | return ret; 169 | } 170 | 171 | protected boolean set(int way, byte[] key) { 172 | HashResult h = hash(key); 173 | int mask = (1 << (pageLevel+3)) - 1; 174 | byte hit = 1; 175 | for (int i = 0; i < way; i++) { 176 | int idx = h.codes[i] & mask; 177 | byte bit = (byte)(1 << (idx & 7)); 178 | hit &= (byte)(data[h.offset+(idx>>>3)] >>> (idx & 7)); 179 | data[h.offset+(idx>>>3)] |= bit; 180 | } 181 | if (hit != 0) { 182 | return false; 183 | } 184 | uniqueCnt++; 185 | return true; 186 | } 187 | 188 | protected boolean test(int way, byte[] key) { 189 | HashResult h = hash(key); 190 | int mask = (1 << (pageLevel+3)) - 1; 191 | for (int i = 0; i < way; i++) { 192 | int idx = h.codes[i] & mask; 193 | byte bit = (byte)(1 << (idx & 7)); 194 | if ((data[h.offset+(idx>>>3)] & bit) == 0) { 195 | return false; 196 | } 197 | } 198 | return true; 199 | } 200 | 201 | 202 | private static class Way4 extends PageBloomFilter { 203 | private static final int WAY = 4; 204 | 205 | public Way4(int pageLevel, int pageNum) { 206 | super(WAY, pageLevel, pageNum); 207 | } 208 | 209 | public Way4(int pageLevel, byte[] data, long uniqueCnt) { 210 | super(WAY, pageLevel, data, uniqueCnt); 211 | } 212 | 213 | @Override 214 | public int getWay() { return WAY; } 215 | 216 | @Override 217 | public boolean set(byte[] key) { 218 | return set(WAY, key); 219 | } 220 | 221 | @Override 222 | public boolean test(byte[] key) { 223 | return test(WAY, key); 224 | } 225 | } 226 | 227 | private static class Way5 extends PageBloomFilter { 228 | private static final int WAY = 5; 229 | 230 | public Way5(int pageLevel, int pageNum) { 231 | super(WAY, pageLevel, pageNum); 232 | } 233 | 234 | public Way5(int pageLevel, byte[] data, long uniqueCnt) { 235 | super(WAY, pageLevel, data, uniqueCnt); 236 | } 237 | 238 | @Override 239 | public int getWay() { return WAY; } 240 | 241 | @Override 242 | public boolean set(byte[] key) { 243 | return set(WAY, key); 244 | } 245 | 246 | @Override 247 | public boolean test(byte[] key) { 248 | return test(WAY, key); 249 | } 250 | } 251 | 252 | private static class Way6 extends PageBloomFilter { 253 | private static final int WAY = 6; 254 | 255 | public Way6(int pageLevel, int pageNum) { 256 | super(WAY, pageLevel, pageNum); 257 | } 258 | 259 | public Way6(int pageLevel, byte[] data, long uniqueCnt) { 260 | super(WAY, pageLevel, data, uniqueCnt); 261 | } 262 | 263 | @Override 264 | public int getWay() { return WAY; } 265 | 266 | @Override 267 | public boolean set(byte[] key) { 268 | return set(WAY, key); 269 | } 270 | 271 | @Override 272 | public boolean test(byte[] key) { 273 | return test(WAY, key); 274 | } 275 | } 276 | 277 | private static class Way7 extends PageBloomFilter { 278 | private static final int WAY = 7; 279 | 280 | public Way7(int pageLevel, int pageNum) { 281 | super(WAY, pageLevel, pageNum); 282 | } 283 | 284 | public Way7(int pageLevel, byte[] data, long uniqueCnt) { 285 | super(WAY, pageLevel, data, uniqueCnt); 286 | } 287 | 288 | @Override 289 | public int getWay() { return WAY; } 290 | 291 | @Override 292 | public boolean set(byte[] key) { 293 | return set(WAY, key); 294 | } 295 | 296 | @Override 297 | public boolean test(byte[] key) { 298 | return test(WAY, key); 299 | } 300 | } 301 | 302 | private static class Way8 extends PageBloomFilter { 303 | private static final int WAY = 8; 304 | 305 | public Way8(int pageLevel, int pageNum) { 306 | super(WAY, pageLevel, pageNum); 307 | } 308 | 309 | public Way8(int pageLevel, byte[] data, long uniqueCnt) { 310 | super(WAY, pageLevel, data, uniqueCnt); 311 | } 312 | 313 | @Override 314 | public int getWay() { return WAY; } 315 | 316 | @Override 317 | public boolean set(byte[] key) { 318 | return set(WAY, key); 319 | } 320 | 321 | @Override 322 | public boolean test(byte[] key) { 323 | return test(WAY, key); 324 | } 325 | } 326 | 327 | } 328 | -------------------------------------------------------------------------------- /csharp/PageBloomFilter/PageBloomFilter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Ruan Kunliang. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | namespace PageBloomFilter { 6 | public abstract class PageBloomFilter { 7 | private readonly int pageLevel = 0; 8 | private readonly uint pageNum = 0; 9 | private long uniqueCnt = 0; 10 | private readonly byte[] data; 11 | 12 | public abstract int Way { get; } 13 | public int PageLevel { get => pageLevel; } 14 | public uint PageNum { get => pageNum; } 15 | 16 | public long UniqueCnt { get => uniqueCnt; } 17 | public byte[] Data { get => data; } 18 | 19 | public long Capacity { 20 | get => data.LongLength * 8 / Way; 21 | } 22 | public long VirtualCapacity(double falsePositiveRate) { 23 | var t = Math.Log(1.0 - Math.Pow(falsePositiveRate, 1.0 / Way)) 24 | / Math.Log(1.0 - 1.0 / (data.LongLength * 8)); 25 | return (long)t / Way; 26 | } 27 | 28 | public abstract bool Set(ReadOnlySpan key); 29 | public abstract bool Test(ReadOnlySpan key); 30 | 31 | public static PageBloomFilter New(long item, double falsePositiveRate) { 32 | if (item < 1) { 33 | item = 1; 34 | } 35 | if (falsePositiveRate > 0.1) { 36 | falsePositiveRate = 0.1; 37 | } else if (falsePositiveRate < 0.0005) { 38 | falsePositiveRate = 0.0005; 39 | } 40 | var w = -Math.Log2(falsePositiveRate); 41 | var bytesPerItem = w / (Math.Log(2) * 8); 42 | if (w > 9) { 43 | var x = w - 7; 44 | bytesPerItem *= 1 + 0.0025*x*x; 45 | } else if (w > 3) { 46 | bytesPerItem *= 1.01; 47 | } 48 | var way = (int)Math.Round(w); 49 | if (way < 4) { 50 | way = 4; 51 | } else if (way > 8) { 52 | way = 8; 53 | } 54 | 55 | var n = (long)(bytesPerItem * item); 56 | int pageLevel = 0; 57 | for (int i = 6; i < 12; i++) { 58 | if (n < (1U << (i + 4))) { 59 | pageLevel = i; 60 | if (pageLevel < (8 - 8 / way)) { 61 | pageLevel++; 62 | } 63 | break; 64 | } 65 | } 66 | if (pageLevel == 0) { 67 | pageLevel = 12; 68 | } 69 | 70 | long pageNum = (n + (1L << pageLevel) - 1L) >> pageLevel; 71 | if (pageNum > Int32.MaxValue) { 72 | throw new ArgumentException("too many items"); 73 | } 74 | return New(way, pageLevel, (uint)pageNum); 75 | } 76 | 77 | public static PageBloomFilter New(int way, int pageLevel, uint pageNum) => 78 | way switch { 79 | 4 => new PageBloomFilter.Way4(pageLevel, pageNum), 80 | 5 => new PageBloomFilter.Way5(pageLevel, pageNum), 81 | 6 => new PageBloomFilter.Way6(pageLevel, pageNum), 82 | 7 => new PageBloomFilter.Way7(pageLevel, pageNum), 83 | 8 => new PageBloomFilter.Way8(pageLevel, pageNum), 84 | _ => throw new ArgumentException("illegal way"), 85 | }; 86 | 87 | protected PageBloomFilter(int way, int pageLevel, uint pageNum) { 88 | if (way < 4 || way > 8) { 89 | throw new ArgumentException("way should be 4-8"); 90 | } 91 | if (pageLevel < (8 - 8 / way) || pageLevel > 13) { 92 | throw new ArgumentException("pageLevel should be 7-13"); 93 | } 94 | if (pageNum <= 0) { 95 | throw new ArgumentException("pageNum should be positive"); 96 | } 97 | 98 | this.pageLevel = pageLevel; 99 | this.pageNum = pageNum; 100 | this.uniqueCnt = 0; 101 | this.data = new byte[pageNum << pageLevel]; 102 | } 103 | 104 | public static PageBloomFilter New(int way, int pageLevel, ReadOnlySpan data, long uniqueCnt) => 105 | way switch { 106 | 4 => new PageBloomFilter.Way4(pageLevel, data, uniqueCnt), 107 | 5 => new PageBloomFilter.Way5(pageLevel, data, uniqueCnt), 108 | 6 => new PageBloomFilter.Way6(pageLevel, data, uniqueCnt), 109 | 7 => new PageBloomFilter.Way7(pageLevel, data, uniqueCnt), 110 | 8 => new PageBloomFilter.Way8(pageLevel, data, uniqueCnt), 111 | _ => throw new ArgumentException("illegal way"), 112 | }; 113 | 114 | protected PageBloomFilter(int way, int pageLevel, ReadOnlySpan data, long uniqueCnt) { 115 | if (way < 4 || way > 8) { 116 | throw new ArgumentException("way should be 4-8"); 117 | } 118 | if (pageLevel < (8 - 8 / way) || pageLevel > 13) { 119 | throw new ArgumentException("pageLevel should be 7-13"); 120 | } 121 | int pageSize = 1 << pageLevel; 122 | if (data.Length == 0 || data.Length % pageSize != 0) { 123 | throw new ArgumentException("illegal data size"); 124 | } 125 | this.pageLevel = pageLevel; 126 | this.pageNum = (uint)(data.Length / pageSize); 127 | this.uniqueCnt = uniqueCnt; 128 | this.data = new byte[data.Length]; 129 | data.CopyTo(this.data); 130 | } 131 | 132 | public void Clear() { 133 | Array.Clear(data, 0, data.Length); 134 | } 135 | 136 | private static uint Rot(uint x, int k) { 137 | return (x << k) | (x >> (32 - k)); 138 | } 139 | 140 | private struct HashResult { 141 | public uint offset; 142 | public uint[] codes; 143 | public HashResult() { 144 | offset = 0; 145 | codes = new uint[8]; 146 | } 147 | } 148 | private HashResult PageHash(ReadOnlySpan key) { 149 | var code = Hash.Hash128(key); 150 | var w0 = (uint)code.low; 151 | var w1 = (uint)(code.low >> 32); 152 | var w2 = (uint)code.high; 153 | var w3 = (uint)(code.high >> 32); 154 | var ret = new HashResult(); 155 | var x = Rot(w0, 8) ^ Rot(w1, 6) ^ Rot(w2, 4) ^ Rot(w3, 2); 156 | ret.offset = x % pageNum; 157 | ret.offset <<= pageLevel; 158 | ret.codes[0] = w0 & 0xffff; 159 | ret.codes[1] = w0 >> 16; 160 | ret.codes[2] = w1 & 0xffff; 161 | ret.codes[3] = w1 >> 16; 162 | ret.codes[4] = w2 & 0xffff; 163 | ret.codes[5] = w2 >> 16; 164 | ret.codes[6] = w3 & 0xffff; 165 | ret.codes[7] = w3 >> 16; 166 | return ret; 167 | } 168 | 169 | protected bool Set(int way, ReadOnlySpan key) { 170 | var h = PageHash(key); 171 | uint mask = (1U << (pageLevel + 3)) - 1U; 172 | int hit = 1; 173 | for (int i = 0; i < way; i++) { 174 | uint idx = h.codes[i] & mask; 175 | byte bit = (byte)(1u << (int)(idx & 7)); 176 | hit &= data[h.offset + (idx >> 3)] >> (int)(idx & 7); 177 | data[h.offset + (idx >> 3)] |= bit; 178 | } 179 | if (hit != 0) { 180 | return false; 181 | } 182 | uniqueCnt++; 183 | return true; 184 | } 185 | 186 | protected bool Test(int way, ReadOnlySpan key) { 187 | var h = PageHash(key); 188 | uint mask = (1U << (pageLevel + 3)) - 1U; 189 | for (int i = 0; i < way; i++) { 190 | uint idx = h.codes[i] & mask; 191 | byte bit = (byte)(1U << (int)(idx & 7)); 192 | if ((data[h.offset + (idx >> 3)] & bit) == 0) { 193 | return false; 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | 200 | private sealed class Way4 : PageBloomFilter { 201 | private const int WAY = 4; 202 | public Way4(int pageLevel, uint pageNum) 203 | : base(WAY, pageLevel, pageNum) {} 204 | public Way4(int pageLevel, ReadOnlySpan data, long uniqueCnt) 205 | : base(WAY, pageLevel, data, uniqueCnt) {} 206 | public override int Way { get => WAY; } 207 | public override bool Set(ReadOnlySpan key) { return Set(WAY, key); } 208 | public override bool Test(ReadOnlySpan key) { return Test(WAY, key); } 209 | } 210 | 211 | private sealed class Way5 : PageBloomFilter { 212 | private const int WAY = 5; 213 | public Way5(int pageLevel, uint pageNum) 214 | : base(WAY, pageLevel, pageNum) {} 215 | public Way5(int pageLevel, ReadOnlySpan data, long uniqueCnt) 216 | : base(WAY, pageLevel, data, uniqueCnt) {} 217 | public override int Way { get => WAY; } 218 | public override bool Set(ReadOnlySpan key) { return Set(WAY, key); } 219 | public override bool Test(ReadOnlySpan key) { return Test(WAY, key); } 220 | } 221 | 222 | private sealed class Way6 : PageBloomFilter { 223 | private const int WAY = 6; 224 | public Way6(int pageLevel, uint pageNum) 225 | : base(WAY, pageLevel, pageNum) {} 226 | public Way6(int pageLevel, ReadOnlySpan data, long uniqueCnt) 227 | : base(WAY, pageLevel, data, uniqueCnt) {} 228 | public override int Way { get => WAY; } 229 | public override bool Set(ReadOnlySpan key) { return Set(WAY, key); } 230 | public override bool Test(ReadOnlySpan key) { return Test(WAY, key); } 231 | } 232 | 233 | private sealed class Way7 : PageBloomFilter { 234 | private const int WAY = 7; 235 | public Way7(int pageLevel, uint pageNum) 236 | : base(WAY, pageLevel, pageNum) {} 237 | public Way7(int pageLevel, ReadOnlySpan data, long uniqueCnt) 238 | : base(WAY, pageLevel, data, uniqueCnt) {} 239 | public override int Way { get => WAY; } 240 | public override bool Set(ReadOnlySpan key) { return Set(WAY, key); } 241 | public override bool Test(ReadOnlySpan key) { return Test(WAY, key); } 242 | } 243 | 244 | private sealed class Way8 : PageBloomFilter { 245 | private const int WAY = 8; 246 | public Way8(int pageLevel, uint pageNum) 247 | : base(WAY, pageLevel, pageNum) {} 248 | public Way8(int pageLevel, ReadOnlySpan data, long uniqueCnt) 249 | : base(WAY, pageLevel, data, uniqueCnt) {} 250 | public override int Way { get => WAY; } 251 | public override bool Set(ReadOnlySpan key) { return Set(WAY, key); } 252 | public override bool Test(ReadOnlySpan key) { return Test(WAY, key); } 253 | } 254 | } 255 | } 256 | --------------------------------------------------------------------------------