Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+5
View File
@@ -8,6 +8,11 @@ publish = false
default-run = "notes_service-cli"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "vendor"
[workspace.dependencies]
loco-rs = { version = "1.0.1" }
+6
View File
@@ -8,6 +8,12 @@ publish = false
name = "migration"
path = "src/lib.rs"
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "../vendor"
[dependencies]
loco-rs = { workspace = true }
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"08071a308400282ad18ca2de0df6d1f2dc270475f69aa483740ce311af3f63e0","CHANGELOG.md":"04fa29ec6eb6b05b706247ecac2cbc7075792dbfcea0bf52715782cf42132e94","Cargo.lock":"3e44ced212a9e7ddc0a5450bcebb48ec67f32a058529856458efa36415554e53","Cargo.toml":"8f30dbd092f3acc475b9d339736cd7b64c6489bc47cd234a7f2232fc52e2d490","Cargo.toml.orig":"077df9094ac86443a4d05305f74782bd237c1f15fa39640463e4c62e9e4a310a","LICENSE-0BSD":"861399f8c21c042b110517e76dc6b63a2b334276c8cf17412fc3c8908ca8dc17","LICENSE-APACHE":"8ada45cd9f843acf64e4722ae262c622a2b3b3007c7310ef36ac1061a30f6adb","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"cd955d5d6a49161e6f7a04df4a5963581b66ed43fd5096b2dedca8e295efe4f9","RELEASE_PROCESS.md":"a86cd10fc70f167f8d00e9e4ce0c6b4ebdfa1865058390dffd1e0ad4d3e68d9d","benches/bench.rs":"d67bef1c7f36ed300a8fbcf9d50b9dfdead1fd340bf87a4d47d99a0c1c042c04","src/algo.rs":"932c2bc591d13fe4470185125617b5aaa660a3898f23b553acc85df0bf49dded","src/lib.rs":"4acd41668fe30daffa37084e7e223f268957b816afc1864ffb3f5d6d7adf0890"},"package":"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "89a031a0f42eeff31c70dc598b398cbf31f1680f"
},
"path_in_vcs": ""
}
+84
View File
@@ -0,0 +1,84 @@
# Changelog
All notable changes to this project will be documented in this file.
---
## [2.0.1](https://github.com/Frommi/miniz_oxide/compare/2.0.0..2.0.1) - 2025-06-09
### Other
- Remove `compiler-builtins` from `rustc-dep-of-std` dependencies - ([7cdbd39](https://github.com/Frommi/miniz_oxide/commit/7cdbd3925a7f61cc075f44367b5d383861571b0a)) - Trevor Gross
---
## [2.0.0](https://github.com/Frommi/miniz_oxide/compare/1.0.2..2.0.0) - 2024-08-04
First release of adler2 - fork of adler crate as the original is unmaintained and archived
##### Changes since last version of Adler:
### Bug Fixes
- **(core)** change to rust 2021 edition, update repository info and links, update author info - ([867b115](https://github.com/Frommi/miniz_oxide/commit/867b115bad79bf62098f2acccc81bf53ec5a125d)) - oyvindln
- **(core)** simplify some code and fix benches - ([128fb9c](https://github.com/Frommi/miniz_oxide/commit/128fb9cb6cad5c3a54fb0b6c68549d80b79a1fe0)) - oyvindln
### Changelog of original adler crate
---
## [1.0.2 - 2021-02-26](https://github.com/jonas-schievink/adler/releases/tag/v1.0.2)
- Fix doctest on big-endian systems ([#9]).
[#9]: https://github.com/jonas-schievink/adler/pull/9
## [1.0.1 - 2020-11-08](https://github.com/jonas-schievink/adler/releases/tag/v1.0.1)
### Fixes
- Fix documentation on docs.rs.
## [1.0.0 - 2020-11-08](https://github.com/jonas-schievink/adler/releases/tag/v1.0.0)
### Fixes
- Fix `cargo test --no-default-features` ([#5]).
### Improvements
- Extended and clarified documentation.
- Added more rustdoc examples.
- Extended CI to test the crate with `--no-default-features`.
### Breaking Changes
- `adler32_reader` now takes its generic argument by value instead of as a `&mut`.
- Renamed `adler32_reader` to `adler32`.
## [0.2.3 - 2020-07-11](https://github.com/jonas-schievink/adler/releases/tag/v0.2.3)
- Process 4 Bytes at a time, improving performance by up to 50% ([#2]).
## [0.2.2 - 2020-06-27](https://github.com/jonas-schievink/adler/releases/tag/v0.2.2)
- Bump MSRV to 1.31.0.
## [0.2.1 - 2020-06-27](https://github.com/jonas-schievink/adler/releases/tag/v0.2.1)
- Add a few `#[inline]` annotations to small functions.
- Fix CI badge.
- Allow integration into libstd.
## [0.2.0 - 2020-06-27](https://github.com/jonas-schievink/adler/releases/tag/v0.2.0)
- Support `#![no_std]` when using `default-features = false`.
- Improve performance by around 7x.
- Support Rust 1.8.0.
- Improve API naming.
## [0.1.0 - 2020-06-26](https://github.com/jonas-schievink/adler/releases/tag/v0.1.0)
Initial release.
[#2]: https://github.com/jonas-schievink/adler/pull/2
[#5]: https://github.com/jonas-schievink/adler/pull/5
+16
View File
@@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
dependencies = [
"rustc-std-workspace-core",
]
[[package]]
name = "rustc-std-workspace-core"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c"
+91
View File
@@ -0,0 +1,91 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "adler2"
version = "2.0.1"
authors = [
"Jonas Schievink <jonasschievink@gmail.com>",
"oyvindln <oyvindln@users.noreply.github.com>",
]
build = false
exclude = [".*"]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A simple clean-room implementation of the Adler-32 checksum"
documentation = "https://docs.rs/adler2/"
readme = "README.md"
keywords = [
"checksum",
"integrity",
"hash",
"adler32",
"zlib",
]
categories = ["algorithms"]
license = "0BSD OR MIT OR Apache-2.0"
repository = "https://github.com/oyvindln/adler2"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg=docsrs"]
[package.metadata.release]
no-dev-version = true
pre-release-commit-message = "Release {{version}}"
tag-message = "{{version}}"
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
replace = """
## Unreleased
No changes.
## [{{version}} - {{date}}](https://github.com/jonas-schievink/adler/releases/tag/v{{version}})
"""
search = """
## Unreleased
"""
[[package.metadata.release.pre-release-replacements]]
file = "README.md"
replace = 'adler = "{{version}}"'
search = 'adler = "[a-z0-9\\.-]+"'
[[package.metadata.release.pre-release-replacements]]
file = "src/lib.rs"
replace = "https://docs.rs/adler/{{version}}"
search = 'https://docs.rs/adler/[a-z0-9\.-]+'
[features]
default = ["std"]
rustc-dep-of-std = ["core"]
std = []
[lib]
name = "adler2"
path = "src/lib.rs"
[[bench]]
name = "bench"
path = "benches/bench.rs"
harness = false
[dependencies.core]
version = "1.0.0"
optional = true
package = "rustc-std-workspace-core"
[dev-dependencies]
+71
View File
@@ -0,0 +1,71 @@
[package]
name = "adler2"
version = "2.0.1"
authors = ["Jonas Schievink <jonasschievink@gmail.com>", "oyvindln <oyvindln@users.noreply.github.com>"]
description = "A simple clean-room implementation of the Adler-32 checksum"
documentation = "https://docs.rs/adler2/"
repository = "https://github.com/oyvindln/adler2"
keywords = ["checksum", "integrity", "hash", "adler32", "zlib"]
categories = ["algorithms"]
readme = "README.md"
license = "0BSD OR MIT OR Apache-2.0"
edition = "2021"
exclude = [".*"]
[[bench]]
name = "bench"
harness = false
[dependencies]
# Internal features, only used when building as part of libstd, not part of the
# stable interface of this crate.
core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' }
[dev-dependencies]
## Messes with minimum rust version and drags in deps just for running tests
## so just comment out for now and enable manually when needed for enabling benches
## criterion = "0.3.2"
[features]
# Disable default features to enable `#![no_std]` support.
default = ["std"]
std = []
# Internal feature, only used when building as part of libstd, not part of the
# stable interface of this crate.
rustc-dep-of-std = ['core']
[package.metadata.docs.rs]
rustdoc-args = [ "--cfg=docsrs" ]
# cargo-release configuration
[package.metadata.release]
tag-message = "{{version}}"
no-dev-version = true
pre-release-commit-message = "Release {{version}}"
# Change the changelog's `Unreleased` section to refer to this release and
# prepend a new `Unreleased` section
[[package.metadata.release.pre-release-replacements]]
file = "CHANGELOG.md"
search = "## Unreleased\n"
replace = """
## Unreleased
No changes.
## [{{version}} - {{date}}](https://github.com/jonas-schievink/adler/releases/tag/v{{version}})
"""
# Bump the version inside the example manifest in `README.md`
[[package.metadata.release.pre-release-replacements]]
file = "README.md"
search = 'adler = "[a-z0-9\\.-]+"'
replace = 'adler = "{{version}}"'
# Bump the version referenced by the `html_root_url` attribute in `lib.rs`
[[package.metadata.release.pre-release-replacements]]
file = "src/lib.rs"
search = "https://docs.rs/adler/[a-z0-9\\.-]+"
replace = "https://docs.rs/adler/{{version}}"
+12
View File
@@ -0,0 +1,12 @@
Copyright (C) Jonas Schievink <jonasschievink@gmail.com>
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/LICENSE-2.0
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+46
View File
@@ -0,0 +1,46 @@
# Adler-32 checksums for Rust
This is a fork of the adler crate as the [original](https://github.com/jonas-schievink/adler) has been archived and is no longer updated by it's author
[![crates.io](https://img.shields.io/crates/v/adler.svg)](https://crates.io/crates/adler)
[![docs.rs](https://docs.rs/adler/badge.svg)](https://docs.rs/adler/)
![CI](https://github.com/jonas-schievink/adler/workflows/CI/badge.svg)
This crate provides a simple implementation of the Adler-32 checksum, used in
the zlib compression format.
Please refer to the [changelog](CHANGELOG.md) to see what changed in the last
releases.
## Features
- Permissively licensed (0BSD) clean-room implementation.
- Zero dependencies.
- Zero `unsafe`.
- Decent performance (3-4 GB/s) (see note).
- Supports `#![no_std]` (with `default-features = false`).
## Usage
Add an entry to your `Cargo.toml`:
```toml
[dependencies]
adler2 = "2.0.0"
```
Check the [API Documentation](https://docs.rs/adler/) for how to use the
crate's functionality.
## Rust version support
Currently, this crate supports all Rust versions starting at Rust 1.56.0.
Bumping the Minimum Supported Rust Version (MSRV) is *not* considered a breaking
change, but will not be done without good reasons. The latest 3 stable Rust
versions will always be supported no matter what.
## Performance
Due to the way the algorithm works this crate and the fact that it's not possible to use explicit simd in safe rust currently, this crate benefits drastically from being compiled with newer cpu instructions enabled (using e.g ```RUSTFLAGS=-C target-feature'+sse4.1``` or ```-C target-cpu=x86-64-v2```/```-C target-cpu=x86-64-v3``` arguments depending on what cpu support is being targeted.)
Judging by the crate benchmarks, on a Ryzen 5600, compiling with SSE 4.1 (enabled in x86-64-v2 feature level) enabled can give a ~50-150% speedup, enabling the LZCNT instruction (enabled in x86-64-v3 feature level) can give a further ~50% speedup,
+13
View File
@@ -0,0 +1,13 @@
# What to do to publish a new release
1. Ensure all notable changes are in the changelog under "Unreleased".
2. Execute `cargo release <level>` to bump version(s), tag and publish
everything. External subcommand, must be installed with `cargo install
cargo-release`.
`<level>` can be one of `major|minor|patch`. If this is the first release
(`0.1.0`), use `minor`, since the version starts out as `0.0.0`.
3. Go to the GitHub releases, edit the just-pushed tag. Copy the release notes
from the changelog.
+109
View File
@@ -0,0 +1,109 @@
extern crate adler2;
extern crate criterion;
use adler2::{adler32_slice, Adler32};
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
fn simple(c: &mut Criterion) {
{
const SIZE: usize = 100;
let mut group = c.benchmark_group("simple-100b");
group.throughput(Throughput::Bytes(SIZE as u64));
group.bench_function("zeroes-100", |bencher| {
bencher.iter(|| {
adler32_slice(&[0; SIZE]);
});
});
group.bench_function("ones-100", |bencher| {
bencher.iter(|| {
adler32_slice(&[0xff; SIZE]);
});
});
}
{
const SIZE: usize = 1024;
let mut group = c.benchmark_group("simple-1k");
group.throughput(Throughput::Bytes(SIZE as u64));
group.bench_function("zeroes-1k", |bencher| {
bencher.iter(|| {
adler32_slice(&[0; SIZE]);
});
});
group.bench_function("ones-1k", |bencher| {
bencher.iter(|| {
adler32_slice(&[0xff; SIZE]);
});
});
}
{
const SIZE: usize = 1024 * 1024;
let mut group = c.benchmark_group("simple-1m");
group.throughput(Throughput::Bytes(SIZE as u64));
group.bench_function("zeroes-1m", |bencher| {
bencher.iter(|| {
adler32_slice(&[0; SIZE]);
});
});
group.bench_function("ones-1m", |bencher| {
bencher.iter(|| {
adler32_slice(&[0xff; SIZE]);
});
});
}
}
fn chunked(c: &mut Criterion) {
const SIZE: usize = 16 * 1024 * 1024;
let data = vec![0xAB; SIZE];
let mut group = c.benchmark_group("chunked-16m");
group.throughput(Throughput::Bytes(SIZE as u64));
group.bench_function("5552", |bencher| {
bencher.iter(|| {
let mut h = Adler32::new();
for chunk in data.chunks(5552) {
h.write_slice(chunk);
}
h.checksum()
});
});
group.bench_function("8k", |bencher| {
bencher.iter(|| {
let mut h = Adler32::new();
for chunk in data.chunks(8 * 1024) {
h.write_slice(chunk);
}
h.checksum()
});
});
group.bench_function("64k", |bencher| {
bencher.iter(|| {
let mut h = Adler32::new();
for chunk in data.chunks(64 * 1024) {
h.write_slice(chunk);
}
h.checksum()
});
});
group.bench_function("1m", |bencher| {
bencher.iter(|| {
let mut h = Adler32::new();
for chunk in data.chunks(1024 * 1024) {
h.write_slice(chunk);
}
h.checksum()
});
});
}
criterion_group!(benches, simple, chunked);
criterion_main!(benches);
+155
View File
@@ -0,0 +1,155 @@
use crate::Adler32;
use std::ops::{AddAssign, MulAssign, RemAssign};
impl Adler32 {
pub(crate) fn compute(&mut self, bytes: &[u8]) {
// The basic algorithm is, for every byte:
// a = (a + byte) % MOD
// b = (b + a) % MOD
// where MOD = 65521.
//
// For efficiency, we can defer the `% MOD` operations as long as neither a nor b overflows:
// - Between calls to `write`, we ensure that a and b are always in range 0..MOD.
// - We use 32-bit arithmetic in this function.
// - Therefore, a and b must not increase by more than 2^32-MOD without performing a `% MOD`
// operation.
//
// According to Wikipedia, b is calculated as follows for non-incremental checksumming:
// b = n×D1 + (n1)×D2 + (n2)×D3 + ... + Dn + n*1 (mod 65521)
// Where n is the number of bytes and Di is the i-th Byte. We need to change this to account
// for the previous values of a and b, as well as treat every input Byte as being 255:
// b_inc = n×255 + (n-1)×255 + ... + 255 + n*65520
// Or in other words:
// b_inc = n*65520 + n(n+1)/2*255
// The max chunk size is thus the largest value of n so that b_inc <= 2^32-65521.
// 2^32-65521 = n*65520 + n(n+1)/2*255
// Plugging this into an equation solver since I can't math gives n = 5552.18..., so 5552.
//
// On top of the optimization outlined above, the algorithm can also be parallelized with a
// bit more work:
//
// Note that b is a linear combination of a vector of input bytes (D1, ..., Dn).
//
// If we fix some value k<N and rewrite indices 1, ..., N as
//
// 1_1, 1_2, ..., 1_k, 2_1, ..., 2_k, ..., (N/k)_k,
//
// then we can express a and b in terms of sums of smaller sequences kb and ka:
//
// ka(j) := D1_j + D2_j + ... + D(N/k)_j where j <= k
// kb(j) := (N/k)*D1_j + (N/k-1)*D2_j + ... + D(N/k)_j where j <= k
//
// a = ka(1) + ka(2) + ... + ka(k) + 1
// b = k*(kb(1) + kb(2) + ... + kb(k)) - 1*ka(2) - ... - (k-1)*ka(k) + N
//
// We use this insight to unroll the main loop and process k=4 bytes at a time.
// The resulting code is highly amenable to SIMD acceleration, although the immediate speedups
// stem from increased pipeline parallelism rather than auto-vectorization.
//
// This technique is described in-depth (here:)[https://software.intel.com/content/www/us/\
// en/develop/articles/fast-computation-of-fletcher-checksums.html]
const MOD: u32 = 65521;
const CHUNK_SIZE: usize = 5552 * 4;
let mut a = u32::from(self.a);
let mut b = u32::from(self.b);
let mut a_vec = U32X4([0; 4]);
let mut b_vec = a_vec;
let (bytes, remainder) = bytes.split_at(bytes.len() - bytes.len() % 4);
// iterate over 4 bytes at a time
let chunk_iter = bytes.chunks_exact(CHUNK_SIZE);
let remainder_chunk = chunk_iter.remainder();
for chunk in chunk_iter {
for byte_vec in chunk.chunks_exact(4) {
let val = U32X4::from(byte_vec);
a_vec += val;
b_vec += a_vec;
}
b += CHUNK_SIZE as u32 * a;
a_vec %= MOD;
b_vec %= MOD;
b %= MOD;
}
// special-case the final chunk because it may be shorter than the rest
for byte_vec in remainder_chunk.chunks_exact(4) {
let val = U32X4::from(byte_vec);
a_vec += val;
b_vec += a_vec;
}
b += remainder_chunk.len() as u32 * a;
a_vec %= MOD;
b_vec %= MOD;
b %= MOD;
// combine the sub-sum results into the main sum
b_vec *= 4;
b_vec.0[1] += MOD - a_vec.0[1];
b_vec.0[2] += (MOD - a_vec.0[2]) * 2;
b_vec.0[3] += (MOD - a_vec.0[3]) * 3;
for &av in a_vec.0.iter() {
a += av;
}
for &bv in b_vec.0.iter() {
b += bv;
}
// iterate over the remaining few bytes in serial
for &byte in remainder.iter() {
a += u32::from(byte);
b += a;
}
self.a = (a % MOD) as u16;
self.b = (b % MOD) as u16;
}
}
#[derive(Copy, Clone)]
struct U32X4([u32; 4]);
impl U32X4 {
#[inline]
fn from(bytes: &[u8]) -> Self {
U32X4([
u32::from(bytes[0]),
u32::from(bytes[1]),
u32::from(bytes[2]),
u32::from(bytes[3]),
])
}
}
impl AddAssign<Self> for U32X4 {
#[inline]
fn add_assign(&mut self, other: Self) {
// Implement this in a primitive manner to help out the compiler a bit.
self.0[0] += other.0[0];
self.0[1] += other.0[1];
self.0[2] += other.0[2];
self.0[3] += other.0[3];
}
}
impl RemAssign<u32> for U32X4 {
#[inline]
fn rem_assign(&mut self, quotient: u32) {
self.0[0] %= quotient;
self.0[1] %= quotient;
self.0[2] %= quotient;
self.0[3] %= quotient;
}
}
impl MulAssign<u32> for U32X4 {
#[inline]
fn mul_assign(&mut self, rhs: u32) {
self.0[0] *= rhs;
self.0[1] *= rhs;
self.0[2] *= rhs;
self.0[3] *= rhs;
}
}
+287
View File
@@ -0,0 +1,287 @@
//! Adler-32 checksum implementation.
//!
//! This implementation features:
//!
//! - Permissively licensed (0BSD) clean-room implementation.
//! - Zero dependencies.
//! - Zero `unsafe`.
//! - Decent performance (3-4 GB/s).
//! - `#![no_std]` support (with `default-features = false`).
#![doc(html_root_url = "https://docs.rs/adler2/2.0.0")]
// Deny a few warnings in doctests, since rustdoc `allow`s many warnings by default
#![doc(test(attr(deny(unused_imports, unused_must_use))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations)]
#![forbid(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate core as std;
mod algo;
use std::hash::Hasher;
#[cfg(feature = "std")]
use std::io::{self, BufRead};
/// Adler-32 checksum calculator.
///
/// An instance of this type is equivalent to an Adler-32 checksum: It can be created in the default
/// state via [`new`] (or the provided `Default` impl), or from a precalculated checksum via
/// [`from_checksum`], and the currently stored checksum can be fetched via [`checksum`].
///
/// This type also implements `Hasher`, which makes it easy to calculate Adler-32 checksums of any
/// type that implements or derives `Hash`. This also allows using Adler-32 in a `HashMap`, although
/// that is not recommended (while every checksum is a hash function, they are not necessarily a
/// good one).
///
/// # Examples
///
/// Basic, piecewise checksum calculation:
///
/// ```
/// use adler2::Adler32;
///
/// let mut adler = Adler32::new();
///
/// adler.write_slice(&[0, 1, 2]);
/// adler.write_slice(&[3, 4, 5]);
///
/// assert_eq!(adler.checksum(), 0x00290010);
/// ```
///
/// Using `Hash` to process structures:
///
/// ```
/// use std::hash::Hash;
/// use adler2::Adler32;
///
/// #[derive(Hash)]
/// struct Data {
/// byte: u8,
/// word: u16,
/// big: u64,
/// }
///
/// let mut adler = Adler32::new();
///
/// let data = Data { byte: 0x1F, word: 0xABCD, big: !0 };
/// data.hash(&mut adler);
///
/// // hash value depends on architecture endianness
/// if cfg!(target_endian = "little") {
/// assert_eq!(adler.checksum(), 0x33410990);
/// }
/// if cfg!(target_endian = "big") {
/// assert_eq!(adler.checksum(), 0x331F0990);
/// }
///
/// ```
///
/// [`new`]: #method.new
/// [`from_checksum`]: #method.from_checksum
/// [`checksum`]: #method.checksum
#[derive(Debug, Copy, Clone)]
pub struct Adler32 {
a: u16,
b: u16,
}
impl Adler32 {
/// Creates a new Adler-32 instance with default state.
#[inline]
pub fn new() -> Self {
Self::default()
}
/// Creates an `Adler32` instance from a precomputed Adler-32 checksum.
///
/// This allows resuming checksum calculation without having to keep the `Adler32` instance
/// around.
///
/// # Example
///
/// ```
/// # use adler2::Adler32;
/// let parts = [
/// "rust",
/// "acean",
/// ];
/// let whole = adler2::adler32_slice(b"rustacean");
///
/// let mut sum = Adler32::new();
/// sum.write_slice(parts[0].as_bytes());
/// let partial = sum.checksum();
///
/// // ...later
///
/// let mut sum = Adler32::from_checksum(partial);
/// sum.write_slice(parts[1].as_bytes());
/// assert_eq!(sum.checksum(), whole);
/// ```
#[inline]
pub const fn from_checksum(sum: u32) -> Self {
Adler32 {
a: sum as u16,
b: (sum >> 16) as u16,
}
}
/// Returns the calculated checksum at this point in time.
#[inline]
pub fn checksum(&self) -> u32 {
(u32::from(self.b) << 16) | u32::from(self.a)
}
/// Adds `bytes` to the checksum calculation.
///
/// If efficiency matters, this should be called with Byte slices that contain at least a few
/// thousand Bytes.
pub fn write_slice(&mut self, bytes: &[u8]) {
self.compute(bytes);
}
}
impl Default for Adler32 {
#[inline]
fn default() -> Self {
Adler32 { a: 1, b: 0 }
}
}
impl Hasher for Adler32 {
#[inline]
fn finish(&self) -> u64 {
u64::from(self.checksum())
}
fn write(&mut self, bytes: &[u8]) {
self.write_slice(bytes);
}
}
/// Calculates the Adler-32 checksum of a byte slice.
///
/// This is a convenience function around the [`Adler32`] type.
///
/// [`Adler32`]: struct.Adler32.html
pub fn adler32_slice(data: &[u8]) -> u32 {
let mut h = Adler32::new();
h.write_slice(data);
h.checksum()
}
/// Calculates the Adler-32 checksum of a `BufRead`'s contents.
///
/// The passed `BufRead` implementor will be read until it reaches EOF (or until it reports an
/// error).
///
/// If you only have a `Read` implementor, you can wrap it in `std::io::BufReader` before calling
/// this function.
///
/// # Errors
///
/// Any error returned by the reader are bubbled up by this function.
///
/// # Examples
///
/// ```no_run
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// use adler2::adler32;
///
/// use std::fs::File;
/// use std::io::BufReader;
///
/// let file = File::open("input.txt")?;
/// let mut file = BufReader::new(file);
///
/// adler32(&mut file)?;
/// # Ok(()) }
/// # fn main() { run().unwrap() }
/// ```
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn adler32<R: BufRead>(mut reader: R) -> io::Result<u32> {
let mut h = Adler32::new();
loop {
let len = {
let buf = reader.fill_buf()?;
if buf.is_empty() {
return Ok(h.checksum());
}
h.write_slice(buf);
buf.len()
};
reader.consume(len);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zeroes() {
assert_eq!(adler32_slice(&[]), 1);
assert_eq!(adler32_slice(&[0]), 1 | 1 << 16);
assert_eq!(adler32_slice(&[0, 0]), 1 | 2 << 16);
assert_eq!(adler32_slice(&[0; 100]), 0x00640001);
assert_eq!(adler32_slice(&[0; 1024]), 0x04000001);
assert_eq!(adler32_slice(&[0; 1024 * 1024]), 0x00f00001);
}
#[test]
fn ones() {
assert_eq!(adler32_slice(&[0xff; 1024]), 0x79a6fc2e);
assert_eq!(adler32_slice(&[0xff; 1024 * 1024]), 0x8e88ef11);
}
#[test]
fn mixed() {
assert_eq!(adler32_slice(&[1]), 2 | 2 << 16);
assert_eq!(adler32_slice(&[40]), 41 | 41 << 16);
assert_eq!(adler32_slice(&[0xA5; 1024 * 1024]), 0xd5009ab1);
}
/// Example calculation from https://en.wikipedia.org/wiki/Adler-32.
#[test]
fn wiki() {
assert_eq!(adler32_slice(b"Wikipedia"), 0x11E60398);
}
#[test]
fn resume() {
let mut adler = Adler32::new();
adler.write_slice(&[0xff; 1024]);
let partial = adler.checksum();
assert_eq!(partial, 0x79a6fc2e); // from above
adler.write_slice(&[0xff; 1024 * 1024 - 1024]);
assert_eq!(adler.checksum(), 0x8e88ef11); // from above
// Make sure that we can resume computing from the partial checksum via `from_checksum`.
let mut adler = Adler32::from_checksum(partial);
adler.write_slice(&[0xff; 1024 * 1024 - 1024]);
assert_eq!(adler.checksum(), 0x8e88ef11); // from above
}
#[cfg(feature = "std")]
#[test]
fn bufread() {
use std::io::BufReader;
fn test(data: &[u8], checksum: u32) {
// `BufReader` uses an 8 KB buffer, so this will test buffer refilling.
let mut buf = BufReader::new(data);
let real_sum = adler32(&mut buf).unwrap();
assert_eq!(checksum, real_sum);
}
test(&[], 1);
test(&[0; 1024], 0x04000001);
test(&[0; 1024 * 1024], 0x00f00001);
test(&[0xA5; 1024 * 1024], 0xd5009ab1);
}
}
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"468b41583b9a0192a65de9dd7d69e90f2a09a219ebe29bc57c3ae75d5888fc12",".github/workflows/rust.yml":"d0e4222971bd7e9385180c29b49593fa85f0f7c2b74f3a42c6aaa21b7c72eef2","Cargo.toml":"a519809f46c9aad28d6c48748f330fe31e05029d234b6d1743522ec403441744","Cargo.toml.orig":"5579a6abb7a783d7896d527abe838dbf413e1f3fb90fd6d6baf6368dc54e0276","FAQ.md":"9eb41898523ee209a0a937f9bcb78afe45ad55ca0556f8a4d4063558098f6d1e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2","README.md":"72185284f100e149998fe5301f70489e617cc4415b51cc77e967c63c6e970a67","build.rs":"f80cb1cdb731a63d16513f1f0b0871d9c077d2c6bf2312af40a202f9c0fefe49","rustfmt.toml":"e090969e99df9360705680cc0097cfaddae10c22dc2e01470592cf3b9787fd36","src/aes_hash.rs":"0b11ce066931396222d2bed7eff678fdd7c8351819485efb721f62a30551866b","src/convert.rs":"f0e78840046493d0679a9ec077c8164cf57cf30d5e852b11bfadfdd996d29bd1","src/fallback_hash.rs":"ec00691bd555c69f7446afe893b6631cb84207cb7b512260dec8ef488e1905f3","src/hash_map.rs":"ed0c79c41c2218ad9591a585670a2b9b983807c9725880b780138a44c126cbfd","src/hash_quality_test.rs":"61695e5cac46ea25021a9d04199fb00c513e0c0c9c0f67aca0c647b9d2f7dd5a","src/hash_set.rs":"dc3d33e290aad62457ab1f5e64d3e33eb79e28c9468bfc8686339f0bbd8b19aa","src/lib.rs":"9fec7d1d412e414231c9b929081b1daa7c3b788a9f91eedd79a55efdf5d0d291","src/operations.rs":"10772e65b8b7106f195428c5eb8dbf6cbd49dd5a2165ac750e54af5995210f88","src/random_state.rs":"ce9689147659efa975887debe1481daddca09386ea8e1d5b4ee90ebeda6c8745","src/specialize.rs":"38d3b56ef4f264d564f48dbcb8ac137928babf90635090c9771c1a62140d1f30","tests/bench.rs":"0851dffebaffd7a437f6f9946ed5e03a957e9a6eb0da7911451af58778c411ec","tests/map_tests.rs":"e0f155f964dd965740b072ee1da110a8c6ef34491c95219f7c89064112c7840f","tests/nopanic.rs":"3363675c4c1a197b86604a0aebbe958fb5ec7c01a414fbfd70e9eb8a29707400"},"package":"891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "a5b990538887e6d793227a152b2aa36c1b29d41d"
},
"path_in_vcs": ""
}
@@ -0,0 +1,151 @@
name: Rust
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install latest stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: clippy
- name: check nostd
uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features
- name: test nostd
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features
- name: check constrandom
uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features --features compile-time-rng
- name: test constrandom
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features compile-time-rng
- name: check
uses: actions-rs/cargo@v1
with:
command: check
- name: test
uses: actions-rs/cargo@v1
with:
command: test
nightly:
name: nightly
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=native
steps:
- uses: actions/checkout@v2
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: clippy
- name: check nightly
uses: actions-rs/cargo@v1
with:
command: check
- name: test nightly
uses: actions-rs/cargo@v1
with:
command: test
- name: check serde
uses: actions-rs/cargo@v1
with:
command: check
args: --features serde
- name: test serde
uses: actions-rs/cargo@v1
with:
command: test
args: --features serde
linux_arm7:
name: Linux ARMv7
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: armv7-unknown-linux-gnueabihf
- uses: actions-rs/cargo@v1
with:
command: check
args: --target armv7-unknown-linux-gnueabihf
i686-unknown-linux-gnu:
name: Linux i686
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: i686-unknown-linux-gnu
- name: Install cross compile tools
run: sudo apt-get install -y gcc-multilib libc6-i386 libc6-dev-i386
- uses: actions-rs/cargo@v1
with:
command: check
args: --target i686-unknown-linux-gnu
- uses: actions-rs/cargo@v1
with:
command: test
args: --target i686-unknown-linux-gnu
x86_64-unknown-linux-gnu:
name: Linux x86_64 - nightly
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=skylake -C target-feature=+aes
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
target: x86_64-unknown-linux-gnu
- uses: actions-rs/cargo@v1
with:
command: check
args: --target x86_64-unknown-linux-gnu
- uses: actions-rs/cargo@v1
with:
command: test
args: --target x86_64-unknown-linux-gnu
thumbv6m:
name: thumbv6m
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: thumbv6m-none-eabi
- uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv6m-none-eabi --no-default-features
wasm32-unknown-unknown:
name: wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: check
args: --target wasm32-unknown-unknown
+149
View File
@@ -0,0 +1,149 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "ahash"
version = "0.7.8"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
build = "./build.rs"
exclude = [
"/smhasher",
"/benchmark_tools",
]
description = "A non-cryptographic hash function using AES-NI for high performance"
documentation = "https://docs.rs/ahash"
readme = "README.md"
keywords = [
"hash",
"hasher",
"hashmap",
"aes",
"no-std",
]
categories = [
"algorithms",
"data-structures",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/tkaitchuck/ahash"
[package.metadata.docs.rs]
features = ["std"]
rustc-args = [
"-C",
"target-feature=+aes",
]
rustdoc-args = [
"-C",
"target-feature=+aes",
]
[profile.bench]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.test]
opt-level = 2
lto = "fat"
[lib]
name = "ahash"
path = "src/lib.rs"
test = true
doctest = true
bench = true
doc = true
[[bench]]
name = "ahash"
path = "tests/bench.rs"
harness = false
[[bench]]
name = "map"
path = "tests/map_tests.rs"
harness = false
[dev-dependencies.criterion]
version = "0.3.2"
[dev-dependencies.fnv]
version = "1.0.5"
[dev-dependencies.fxhash]
version = "0.2.1"
[dev-dependencies.hex]
version = "0.4.2"
[dev-dependencies.no-panic]
version = "0.1.10"
[dev-dependencies.rand]
version = "0.7.3"
[dev-dependencies.seahash]
version = "4.0"
[dev-dependencies.serde_json]
version = "1.0.59"
[build-dependencies.version_check]
version = "0.9"
[features]
atomic-polyfill = [
"dep:atomic-polyfill",
"once_cell/atomic-polyfill",
]
compile-time-rng = ["const-random"]
default = ["std"]
std = []
[target."cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))".dependencies.const-random]
version = "0.1.12"
optional = true
[target."cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))".dependencies.getrandom]
version = "0.2.3"
[target."cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))".dependencies.serde]
version = "1.0.117"
optional = true
[target."cfg(not(all(target_arch = \"arm\", target_os = \"none\")))".dependencies.once_cell]
version = "1.13.1"
features = ["alloc"]
default-features = false
[target."cfg(not(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\")))".dependencies.atomic-polyfill]
version = "1.0.1"
optional = true
[target."cfg(not(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\")))".dependencies.const-random]
version = "0.1.12"
optional = true
[target."cfg(not(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\")))".dependencies.serde]
version = "1.0.117"
optional = true
+96
View File
@@ -0,0 +1,96 @@
[package]
name = "ahash"
version = "0.7.8"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "A non-cryptographic hash function using AES-NI for high performance"
documentation = "https://docs.rs/ahash"
repository = "https://github.com/tkaitchuck/ahash"
keywords = ["hash", "hasher", "hashmap", "aes", "no-std"]
categories = ["algorithms", "data-structures", "no-std"]
edition = "2018"
readme = "README.md"
build = "./build.rs"
exclude = ["/smhasher", "/benchmark_tools"]
[lib]
name = "ahash"
path = "src/lib.rs"
test = true
doctest = true
bench = true
doc = true
[features]
default = ["std"]
# Enabling this will enable `AHashMap` and `AHashSet`.
std = []
# This is an alternitive to runtime key generation which does compile time key generation if getrandom is not available.
# (If getrandom is available this does nothing.)
# If this is on (and getrandom is off) it implies the produced binary will not be identical.
# If this is disabled and gerrandom is unavailable constant keys are used.
compile-time-rng = ["const-random"]
# in case this is being used on an architecture lacking core::sync::atomic::AtomicUsize and friends
atomic-polyfill = [ "dep:atomic-polyfill", "once_cell/atomic-polyfill"]
[[bench]]
name = "ahash"
path = "tests/bench.rs"
harness = false
[[bench]]
name = "map"
path = "tests/map_tests.rs"
harness = false
[profile.test]
opt-level = 2
lto = 'fat'
[profile.release]
opt-level = 3
debug = false
lto = 'fat'
debug-assertions = false
codegen-units = 1
[profile.bench]
opt-level = 3
debug = false
lto = 'fat'
debug-assertions = false
codegen-units = 1
[build-dependencies]
version_check = "0.9"
[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "windows", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "solaris", target_os = "illumos", target_os = "fuchsia", target_os = "redox", target_os = "cloudabi", target_os = "haiku", target_os = "vxworks", target_os = "emscripten", target_os = "wasi"))'.dependencies]
getrandom = { version = "0.2.3" }
const-random = { version = "0.1.12", optional = true }
serde = { version = "1.0.117", optional = true }
[target.'cfg(not(any(target_os = "linux", target_os = "android", target_os = "windows", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "solaris", target_os = "illumos", target_os = "fuchsia", target_os = "redox", target_os = "cloudabi", target_os = "haiku", target_os = "vxworks", target_os = "emscripten", target_os = "wasi")))'.dependencies]
const-random = { version = "0.1.12", optional = true }
serde = { version = "1.0.117", optional = true }
atomic-polyfill = { version="1.0.1", optional=true}
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies]
once_cell = { version = "1.13.1", default-features = false, features = ["alloc"] }
[dev-dependencies]
no-panic = "0.1.10"
criterion = {version = "0.3.2"}
seahash = "4.0"
fnv = "1.0.5"
fxhash = "0.2.1"
hex = "0.4.2"
rand = "0.7.3"
serde_json = "1.0.59"
[package.metadata.docs.rs]
rustc-args = ["-C", "target-feature=+aes"]
rustdoc-args = ["-C", "target-feature=+aes"]
features = ["std"]
+118
View File
@@ -0,0 +1,118 @@
## How does aHash prevent DOS attacks
AHash is designed to [prevent an adversary that does not know the key from being able to create hash collisions or partial collisions.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
If you are a cryptographer and would like to help review aHash's algorithm, please post a comment [here](https://github.com/tkaitchuck/aHash/issues/11).
In short, this is achieved by ensuring that:
* aHash is designed to [resist differential crypto analysis](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks#differential-analysis). Meaning it should not be possible to devise a scheme to "cancel" out a modification of the internal state from a block of input via some corresponding change in a subsequent block of input.
* This is achieved by not performing any "premixing" - This reversible mixing gave previous hashes such as murmurhash confidence in their quality, but could be undone by a deliberate attack.
* Before it is used each chunk of input is "masked" such as by xoring it with an unpredictable value.
* aHash obeys the '[strict avalanche criterion](https://en.wikipedia.org/wiki/Avalanche_effect#Strict_avalanche_criterion)':
Each bit of input has the potential to flip every bit of the output.
* Similarly, each bit in the key can affect every bit in the output.
* Input bits never effect just one, or a very few, bits in intermediate state. This is specifically designed to prevent the sort of
[differential attacks launched by the sipHash authors](https://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/) which cancel previous inputs.
* The `finish` call at the end of the hash is designed to not expose individual bits of the internal state.
* For example in the main algorithm 256bits of state and 256bits of keys are reduced to 64 total bits using 3 rounds of AES encryption.
Reversing this is more than non-trivial. Most of the information is by definition gone, and any given bit of the internal state is fully diffused across the output.
* In both aHash and its fallback the internal state is divided into two halves which are updated by two unrelated techniques using the same input. - This means that if there is a way to attack one of them it likely won't be able to attack both of them at the same time.
* It is deliberately difficult to 'chain' collisions. (This has been the major technique used to weaponize attacks on other hash functions)
More details are available on [the wiki](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks).
## Why not use a cryptographic hash in a hashmap.
Cryptographic hashes are designed to make is nearly impossible to find two items that collide when the attacker has full control
over the input. This has several implications:
* They are very difficult to construct, and have to go to a lot of effort to ensure that collisions are not possible.
* They have no notion of a 'key'. Rather, they are fully deterministic and provide exactly one hash for a given input.
For a HashMap the requirements are different.
* Speed is very important, especially for short inputs. Often the key for a HashMap is a single `u32` or similar, and to be effective
the bucket that it should be hashed to needs to be computed in just a few CPU cycles.
* A hashmap does not need to provide a hard and fast guarantee that no two inputs will ever collide. Hence, hashCodes are not 256bits
but are just 64 or 32 bits in length. Often the first thing done with the hashcode is to truncate it further to compute which among a few buckets should be used for a key.
* Here collisions are expected, and a cheap to deal with provided there is no systematic way to generated huge numbers of values that all
go to the same bucket.
* This also means that unlike a cryptographic hash partial collisions matter. It doesn't do a hashmap any good to produce a unique 256bit hash if
the lower 12 bits are all the same. This means that even a provably irreversible hash would not offer protection from a DOS attack in a hashmap
because an attacker can easily just brute force the bottom N bits.
From a cryptography point of view, a hashmap needs something closer to a block cypher.
Where the input can be quickly mixed in a way that cannot be reversed without knowing a key.
## Why isn't aHash cryptographically secure
It is not designed to be.
Attempting to use aHash as a secure hash will likely fail to hold up for several reasons:
1. aHash relies on random keys which are assumed to not be observable by an attacker. For a cryptographic hash all inputs can be seen and controlled by the attacker.
2. aHash has not yet gone through peer review, which is a pre-requisite for security critical algorithms.
3. Because aHash uses reduced rounds of AES as opposed to the standard of 10. Things like the SQUARE attack apply to part of the internal state.
(These are mitigated by other means to prevent producing collections, but would be a problem in other contexts).
4. Like any cypher based hash, it will show certain statistical deviations from truly random output when comparing a (VERY) large number of hashes.
(By definition cyphers have fewer collisions than truly random data.)
There are efforts to build a secure hash function that uses AES-NI for acceleration, but aHash is not one of them.
## How is aHash so fast
AHash uses a number of tricks.
One trick is taking advantage of specialization. If aHash is compiled on nightly it will take
advantage of specialized hash implementations for strings, slices, and primitives.
Another is taking advantage of hardware instructions.
When it is available aHash uses AES rounds using the AES-NI instruction. AES-NI is very fast (on an intel i7-6700 it
is as fast as a 64 bit multiplication.) and handles 16 bytes of input at a time, while being a very strong permutation.
This is obviously much faster than most standard approaches to hashing, and does a better job of scrambling data than most non-secure hashes.
On an intel i7-6700 compiled on nightly Rust with flags `-C opt-level=3 -C target-cpu=native -C codegen-units=1`:
| Input | SipHash 1-3 time | FnvHash time|FxHash time| aHash time| aHash Fallback* |
|----------------|-----------|-----------|-----------|-----------|---------------|
| u8 | 9.3271 ns | 0.808 ns | **0.594 ns** | 0.7704 ns | 0.7664 ns |
| u16 | 9.5139 ns | 0.803 ns | **0.594 ns** | 0.7653 ns | 0.7704 ns |
| u32 | 9.1196 ns | 1.4424 ns | **0.594 ns** | 0.7637 ns | 0.7712 ns |
| u64 | 10.854 ns | 3.0484 ns | **0.628 ns** | 0.7788 ns | 0.7888 ns |
| u128 | 12.465 ns | 7.0728 ns | 0.799 ns | **0.6174 ns** | 0.6250 ns |
| 1 byte string | 11.745 ns | 2.4743 ns | 2.4000 ns | **1.4921 ns** | 1.5861 ns |
| 3 byte string | 12.066 ns | 3.5221 ns | 2.9253 ns | **1.4745 ns** | 1.8518 ns |
| 4 byte string | 11.634 ns | 4.0770 ns | 1.8818 ns | **1.5206 ns** | 1.8924 ns |
| 7 byte string | 14.762 ns | 5.9780 ns | 3.2282 ns | **1.5207 ns** | 1.8933 ns |
| 8 byte string | 13.442 ns | 4.0535 ns | 2.9422 ns | **1.6262 ns** | 1.8929 ns |
| 15 byte string | 16.880 ns | 8.3434 ns | 4.6070 ns | **1.6265 ns** | 1.7965 ns |
| 16 byte string | 15.155 ns | 7.5796 ns | 3.2619 ns | **1.6262 ns** | 1.8011 ns |
| 24 byte string | 16.521 ns | 12.492 ns | 3.5424 ns | **1.6266 ns** | 2.8311 ns |
| 68 byte string | 24.598 ns | 50.715 ns | 5.8312 ns | **4.8282 ns** | 5.4824 ns |
| 132 byte string| 39.224 ns | 119.96 ns | 11.777 ns | **6.5087 ns** | 9.1459 ns |
|1024 byte string| 254.00 ns | 1087.3 ns | 156.41 ns | **25.402 ns** | 54.566 ns |
* Fallback refers to the algorithm aHash would use if AES instructions are unavailable.
For reference a hash that does nothing (not even reads the input data takes) **0.520 ns**. So that represents the fastest
possible time.
As you can see above aHash like `FxHash` provides a large speedup over `SipHash-1-3` which is already nearly twice as fast as `SipHash-2-4`.
Rust's HashMap by default uses `SipHash-1-3` because faster hash functions such as `FxHash` are predictable and vulnerable to denial of
service attacks. While `aHash` has both very strong scrambling and very high performance.
AHash performs well when dealing with large inputs because aHash reads 8 or 16 bytes at a time. (depending on availability of AES-NI)
Because of this, and its optimized logic, `aHash` is able to outperform `FxHash` with strings.
It also provides especially good performance dealing with unaligned input.
(Notice the big performance gaps between 3 vs 4, 7 vs 8 and 15 vs 16 in `FxHash` above)
### Which CPUs can use the hardware acceleration
Hardware AES instructions are built into Intel processors built after 2010 and AMD processors after 2012.
It is also available on [many other CPUs](https://en.wikipedia.org/wiki/AES_instruction_set) should in eventually
be able to get aHash to work. However, only X86 and X86-64 are the only supported architectures at the moment, as currently
they are the only architectures for which Rust provides an intrinsic.
aHash also uses `sse2` and `sse3` instructions. X86 processors that have `aesni` also have these instruction sets.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2016 Amanieu d'Antras
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+110
View File
@@ -0,0 +1,110 @@
# aHash ![Build Status](https://img.shields.io/github/workflow/status/tkaitchuck/ahash/Rust) ![Licence](https://img.shields.io/crates/l/ahash) ![Downloads](https://img.shields.io/crates/d/ahash)
AHash is the [fastest](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Speed),
[DOS resistant hash](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks) currently available in Rust.
AHash is intended *exclusively* for use in in-memory hashmaps.
AHash's output is of [high quality](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Quality) but aHash is **not** a cryptographically secure hash.
## Design
Because AHash is a keyed hash, each map will produce completely different hashes, which cannot be predicted without knowing the keys.
[This prevents DOS attacks where an attacker sends a large number of items whose hashes collide that get used as keys in a hashmap.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
This also avoids [accidentally quadratic behavior by reading from one map and writing to another.](https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion)
## Goals and Non-Goals
AHash does *not* have a fixed standard for its output. This allows it to improve over time. For example,
if any faster algorithm is found, aHash will be updated to incorporate the technique.
Similarly, should any flaw in aHash's DOS resistance be found, aHash will be changed to correct the flaw.
Because it does not have a fixed standard, different computers or computers on different versions of the code will observe different hash values.
As such, aHash is not recommended for use other than in-memory maps. Specifically, aHash is not intended for network use or in applications which persist hashed values.
(In these cases `HighwayHash` would be a better choice)
Additionally, aHash is not intended to be cryptographically secure and should not be used as a MAC, or anywhere which requires a cryptographically secure hash.
(In these cases `SHA-3` would be a better choice)
## Usage
AHash is a drop in replacement for the default implementation of the `Hasher` trait. To construct a `HashMap` using aHash
as its hasher do the following:
```rust
use ahash::{AHasher, RandomState};
use std::collections::HashMap;
let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
map.insert(12, 34);
```
For convenience, wrappers called `AHashMap` and `AHashSet` are also provided.
These do the same thing with slightly less typing.
```rust
use ahash::AHashMap;
let mut map: AHashMap<i32, i32> = AHashMap::new();
map.insert(12, 34);
map.insert(56, 78);
```
## Flags
The aHash package has the following flags:
* `std`: This enables features which require the standard library. (On by default) This includes providing the utility classes `AHashMap` and `AHashSet`.
* `serde`: Enables `serde` support for the utility classes `AHashMap` and `AHashSet`.
* `compile-time-rng`: Whenever possible aHash will seed hashers with random numbers using the [getrandom](https://github.com/rust-random/getrandom) crate.
This is possible for OS targets which provide a source of randomness. (see the [full list](https://docs.rs/getrandom/0.2.0/getrandom/#supported-targets).)
For OS targets without access to a random number generator, `compile-time-rng` provides an alternative.
If `getrandom` is unavailable and `compile-time-rng` is enabled, aHash will generate random numbers at compile time and embed them in the binary.
This allows for DOS resistance even if there is no random number generator available at runtime (assuming the compiled binary is not public).
This makes the binary non-deterministic, unless `getrandom` is available for the target in which case the flag does nothing.
(If non-determinism is a problem see [constrandom's documentation](https://github.com/tkaitchuck/constrandom#deterministic-builds))
**NOTE:** If `getrandom` is unavailable and `compile-time-rng` is disabled aHash will fall back on using the numeric
value of memory addresses as a source of randomness. This is somewhat strong if ALSR is turned on (it is by default)
but for embedded platforms this will result in weak keys. As a result, it is recommended to use `compile-time-rng` anytime
random numbers will not be available at runtime.
## Comparison with other hashers
A full comparison with other hashing algorithms can be found [here](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md)
![Hasher performance](https://docs.google.com/spreadsheets/d/e/2PACX-1vSK7Li2nS-Bur9arAYF9IfT37MP-ohAe1v19lZu5fd9MajI1fSveLAQZyEie4Ea9k5-SWHTff7nL2DW/pubchart?oid=1323618938&format=image)
For a more representative performance comparison which includes the overhead of using a HashMap, see [HashBrown's benchmarks](https://github.com/rust-lang/hashbrown#performance)
as HashBrown now uses aHash as its hasher by default.
## Hash quality
AHash passes the full [SMHasher test suite](https://github.com/rurban/smhasher).
The code to reproduce the result, and the full output [are checked into the repo](https://github.com/tkaitchuck/aHash/tree/master/smhasher).
## Additional FAQ
A separate FAQ document is maintained [here](https://github.com/tkaitchuck/aHash/blob/master/FAQ.md).
If you have questions not covered there, open an issue [here](https://github.com/tkaitchuck/aHash/issues).
## License
Licensed under either of:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
+47
View File
@@ -0,0 +1,47 @@
#![deny(warnings)]
use std::env;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
if let Some(channel) = version_check::Channel::read() {
if channel.supports_features() {
println!("cargo:rustc-cfg=feature=\"specialize\"");
if version_check::Version::read().map_or(false, |v| v.at_most("1.77.9")) {
println!("cargo:rustc-cfg=feature=\"stdsimd\"");
}
}
}
let os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS was not set");
if os.eq_ignore_ascii_case("linux")
|| os.eq_ignore_ascii_case("android")
|| os.eq_ignore_ascii_case("windows")
|| os.eq_ignore_ascii_case("macos")
|| os.eq_ignore_ascii_case("ios")
|| os.eq_ignore_ascii_case("freebsd")
|| os.eq_ignore_ascii_case("openbsd")
|| os.eq_ignore_ascii_case("dragonfly")
|| os.eq_ignore_ascii_case("solaris")
|| os.eq_ignore_ascii_case("illumos")
|| os.eq_ignore_ascii_case("fuchsia")
|| os.eq_ignore_ascii_case("redox")
|| os.eq_ignore_ascii_case("cloudabi")
|| os.eq_ignore_ascii_case("haiku")
|| os.eq_ignore_ascii_case("vxworks")
|| os.eq_ignore_ascii_case("emscripten")
|| os.eq_ignore_ascii_case("wasi")
{
println!("cargo:rustc-cfg=feature=\"runtime-rng\"");
}
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH was not set");
if arch.eq_ignore_ascii_case("x86_64")
|| arch.eq_ignore_ascii_case("aarch64")
|| arch.eq_ignore_ascii_case("mips64")
|| arch.eq_ignore_ascii_case("powerpc64")
|| arch.eq_ignore_ascii_case("riscv64gc")
|| arch.eq_ignore_ascii_case("s390x")
{
println!("cargo:rustc-cfg=feature=\"folded_multiply\"");
}
}
+1
View File
@@ -0,0 +1 @@
max_width = 120
+440
View File
@@ -0,0 +1,440 @@
use crate::convert::*;
#[cfg(feature = "specialize")]
use crate::fallback_hash::MULTIPLE;
use crate::operations::*;
use crate::RandomState;
use core::hash::Hasher;
use crate::random_state::PI;
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
enc: u128,
sum: u128,
key: u128,
}
impl AHasher {
/// Creates a new hasher keyed to the provided keys.
///
/// Normally hashers are created via `AHasher::default()` for fixed keys or `RandomState::new()` for randomly
/// generated keys and `RandomState::with_seeds(a,b)` for seeds that are set and can be reused. All of these work at
/// map creation time (and hence don't have any overhead on a per-item bais).
///
/// This method directly creates the hasher instance and performs no transformation on the provided seeds. This may
/// be useful where a HashBuilder is not desired, such as for testing purposes.
///
/// # Example
///
/// ```
/// use std::hash::Hasher;
/// use ahash::AHasher;
///
/// let mut hasher = AHasher::new_with_keys(1234, 5678);
///
/// hasher.write_u32(1989);
/// hasher.write_u8(11);
/// hasher.write_u8(9);
/// hasher.write(b"Huh?");
///
/// println!("Hash is {:x}!", hasher.finish());
/// ```
#[inline]
pub fn new_with_keys(key1: u128, key2: u128) -> Self {
let pi: [u128; 2] = PI.convert();
let key1 = key1 ^ pi[0];
let key2 = key2 ^ pi[1];
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline]
pub(crate) fn from_random_state(rand_state: &RandomState) -> Self {
let key1 = [rand_state.k0, rand_state.k1].convert();
let key2 = [rand_state.k2, rand_state.k3].convert();
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline(always)]
fn add_in_length(&mut self, length: u64) {
//This will be scrambled by the next AES round.
let mut enc: [u64; 2] = self.enc.convert();
enc[0] = enc[0].wrapping_add(length);
self.enc = enc.convert();
}
#[inline(always)]
fn hash_in(&mut self, new_value: u128) {
self.enc = aesenc(self.enc, new_value);
self.sum = shuffle_and_add(self.sum, new_value);
}
#[inline(always)]
fn hash_in_2(&mut self, v1: u128, v2: u128) {
self.enc = aesenc(self.enc, v1);
self.sum = shuffle_and_add(self.sum, v1);
self.enc = aesenc(self.enc, v2);
self.sum = shuffle_and_add(self.sum, v2);
}
#[inline]
#[cfg(feature = "specialize")]
fn short_finish(&self) -> u64 {
let combined = aesdec(self.sum, self.enc);
let result: [u64; 2] = aesenc(combined, combined).convert();
result[0]
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.hash_in(i);
}
#[inline]
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32", target_pointer_width = "16"))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len();
self.add_in_length(length as u64);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() <= 8 {
let value = read_small(data);
self.hash_in(value.convert());
} else {
if data.len() > 32 {
if data.len() > 64 {
let tail = data.read_last_u128x4();
let mut current: [u128; 4] = [self.key; 4];
current[0] = aesenc(current[0], tail[0]);
current[1] = aesdec(current[1], tail[1]);
current[2] = aesenc(current[2], tail[2]);
current[3] = aesdec(current[3], tail[3]);
let mut sum: [u128; 2] = [self.key, !self.key];
sum[0] = add_by_64s(sum[0].convert(), tail[0].convert()).convert();
sum[1] = add_by_64s(sum[1].convert(), tail[1].convert()).convert();
sum[0] = shuffle_and_add(sum[0], tail[2]);
sum[1] = shuffle_and_add(sum[1], tail[3]);
while data.len() > 64 {
let (blocks, rest) = data.read_u128x4();
current[0] = aesenc(current[0], blocks[0]);
current[1] = aesenc(current[1], blocks[1]);
current[2] = aesenc(current[2], blocks[2]);
current[3] = aesenc(current[3], blocks[3]);
sum[0] = shuffle_and_add(sum[0], blocks[0]);
sum[1] = shuffle_and_add(sum[1], blocks[1]);
sum[0] = shuffle_and_add(sum[0], blocks[2]);
sum[1] = shuffle_and_add(sum[1], blocks[3]);
data = rest;
}
self.hash_in_2(current[0], current[1]);
self.hash_in_2(current[2], current[3]);
self.hash_in_2(sum[0], sum[1]);
} else {
//len 33-64
let (head, _) = data.read_u128x2();
let tail = data.read_last_u128x2();
self.hash_in_2(head[0], head[1]);
self.hash_in_2(tail[0], tail[1]);
}
} else {
if data.len() > 16 {
//len 17-32
self.hash_in_2(data.read_u128().0, data.read_last_u128());
} else {
//len 9-16
let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
self.hash_in(value.convert());
}
}
}
}
#[inline]
fn finish(&self) -> u64 {
let combined = aesdec(self.sum, self.enc);
let result: [u64; 2] = aesenc(aesenc(combined, self.key), combined).convert();
result[1]
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(feature = "specialize")]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
let rot = (self.pad & 63) as u32;
self.buffer.rotate_left(rot)
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(feature = "specialize")]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for strings
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(feature = "specialize")]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
let result : [u64; 2] = self.0.enc.convert();
result[0]
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes);
self.0.enc = aesdec(self.0.sum, self.0.enc);
self.0.enc = aesenc(aesenc(self.0.enc, self.0.key), self.0.enc);
} else {
self.0.add_in_length(bytes.len() as u64);
let value = read_small(bytes).convert();
self.0.sum = shuffle_and_add(self.0.sum, value);
self.0.enc = aesdec(self.0.sum, self.0.enc);
self.0.enc = aesenc(aesenc(self.0.enc, self.0.key), self.0.enc);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convert::Convert;
use crate::operations::aesenc;
use crate::RandomState;
use std::hash::{BuildHasher, Hasher};
#[test]
fn test_sanity() {
let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
hasher.write_u64(0);
let h1 = hasher.finish();
hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]);
let h2 = hasher.finish();
assert_ne!(h1, h2);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_builder() {
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_default() {
let hasher_a = AHasher::default();
let a_enc: [u64; 2] = hasher_a.enc.convert();
let a_sum: [u64; 2] = hasher_a.sum.convert();
assert_ne!(0, a_enc[0]);
assert_ne!(0, a_enc[1]);
assert_ne!(0, a_sum[0]);
assert_ne!(0, a_sum[1]);
assert_ne!(a_enc[0], a_enc[1]);
assert_ne!(a_sum[0], a_sum[1]);
assert_ne!(a_enc[0], a_sum[0]);
assert_ne!(a_enc[1], a_sum[1]);
let hasher_b = AHasher::default();
let b_enc: [u64; 2] = hasher_b.enc.convert();
let b_sum: [u64; 2] = hasher_b.sum.convert();
assert_eq!(a_enc[0], b_enc[0]);
assert_eq!(a_enc[1], b_enc[1]);
assert_eq!(a_sum[0], b_sum[0]);
assert_eq!(a_sum[1], b_sum[1]);
}
#[test]
fn test_hash() {
let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
result = aesenc(value.convert(), result.convert()).convert();
result = aesenc(result.convert(), result.convert()).convert();
let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
result2 = aesenc(value2.convert(), result2.convert()).convert();
result2 = aesenc(result2.convert(), result.convert()).convert();
let result: [u8; 16] = result.convert();
let result2: [u8; 16] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
#[test]
fn test_conversion() {
let input: &[u8] = "dddddddd".as_bytes();
let bytes: u64 = as_array!(input, 8).convert();
assert_eq!(bytes, 0x6464646464646464);
}
}
+167
View File
@@ -0,0 +1,167 @@
pub(crate) trait Convert<To> {
fn convert(self) -> To;
}
macro_rules! convert {
($a:ty, $b:ty) => {
impl Convert<$b> for $a {
#[inline(always)]
fn convert(self) -> $b {
unsafe {
core::mem::transmute::<$a, $b>(self)
}
}
}
impl Convert<$a> for $b {
#[inline(always)]
fn convert(self) -> $a {
unsafe {
core::mem::transmute::<$b, $a>(self)
}
}
}
};
}
convert!([u128; 4], [u64; 8]);
convert!([u128; 4], [u32; 16]);
convert!([u128; 4], [u16; 32]);
convert!([u128; 4], [u8; 64]);
convert!([u128; 2], [u64; 4]);
convert!([u128; 2], [u32; 8]);
convert!([u128; 2], [u16; 16]);
convert!([u128; 2], [u8; 32]);
convert!(u128, [u64; 2]);
convert!(u128, [u32; 4]);
convert!(u128, [u16; 8]);
convert!(u128, [u8; 16]);
convert!([u64; 8], [u32; 16]);
convert!([u64; 8], [u16; 32]);
convert!([u64; 8], [u8; 64]);
convert!([u64; 4], [u32; 8]);
convert!([u64; 4], [u16; 16]);
convert!([u64; 4], [u8; 32]);
convert!([u64; 2], [u32; 4]);
convert!([u64; 2], [u16; 8]);
convert!([u64; 2], [u8; 16]);
convert!([u32; 4], [u16; 8]);
convert!([u32; 4], [u8; 16]);
convert!([u16; 8], [u8; 16]);
convert!(u64, [u32; 2]);
convert!(u64, [u16; 4]);
convert!(u64, [u8; 8]);
convert!([u32; 2], [u16; 4]);
convert!([u32; 2], [u8; 8]);
convert!(u32, [u16; 2]);
convert!(u32, [u8; 4]);
convert!([u16; 2], [u8; 4]);
convert!(u16, [u8; 2]);
convert!([[u64; 4]; 2], [u8; 64]);
convert!([f64; 2], [u8; 16]);
convert!([f32; 4], [u8; 16]);
convert!(f64, [u8; 8]);
convert!([f32; 2], [u8; 8]);
convert!(f32, [u8; 4]);
macro_rules! as_array {
($input:expr, $len:expr) => {{
{
#[inline(always)]
fn as_array<T>(slice: &[T]) -> &[T; $len] {
assert_eq!(slice.len(), $len);
unsafe { &*(slice.as_ptr() as *const [_; $len]) }
}
as_array($input)
}
}};
}
pub(crate) trait ReadFromSlice {
fn read_u16(&self) -> (u16, &[u8]);
fn read_u32(&self) -> (u32, &[u8]);
fn read_u64(&self) -> (u64, &[u8]);
fn read_u128(&self) -> (u128, &[u8]);
fn read_u128x2(&self) -> ([u128; 2], &[u8]);
fn read_u128x4(&self) -> ([u128; 4], &[u8]);
fn read_last_u16(&self) -> u16;
fn read_last_u32(&self) -> u32;
fn read_last_u64(&self) -> u64;
fn read_last_u128(&self) -> u128;
fn read_last_u128x2(&self) -> [u128; 2];
fn read_last_u128x4(&self) -> [u128; 4];
}
impl ReadFromSlice for [u8] {
#[inline(always)]
fn read_u16(&self) -> (u16, &[u8]) {
let (value, rest) = self.split_at(2);
(as_array!(value, 2).convert(), rest)
}
#[inline(always)]
fn read_u32(&self) -> (u32, &[u8]) {
let (value, rest) = self.split_at(4);
(as_array!(value, 4).convert(), rest)
}
#[inline(always)]
fn read_u64(&self) -> (u64, &[u8]) {
let (value, rest) = self.split_at(8);
(as_array!(value, 8).convert(), rest)
}
#[inline(always)]
fn read_u128(&self) -> (u128, &[u8]) {
let (value, rest) = self.split_at(16);
(as_array!(value, 16).convert(), rest)
}
#[inline(always)]
fn read_u128x2(&self) -> ([u128; 2], &[u8]) {
let (value, rest) = self.split_at(32);
(as_array!(value, 32).convert(), rest)
}
#[inline(always)]
fn read_u128x4(&self) -> ([u128; 4], &[u8]) {
let (value, rest) = self.split_at(64);
(as_array!(value, 64).convert(), rest)
}
#[inline(always)]
fn read_last_u16(&self) -> u16 {
let (_, value) = self.split_at(self.len() - 2);
as_array!(value, 2).convert()
}
#[inline(always)]
fn read_last_u32(&self) -> u32 {
let (_, value) = self.split_at(self.len() - 4);
as_array!(value, 4).convert()
}
#[inline(always)]
fn read_last_u64(&self) -> u64 {
let (_, value) = self.split_at(self.len() - 8);
as_array!(value, 8).convert()
}
#[inline(always)]
fn read_last_u128(&self) -> u128 {
let (_, value) = self.split_at(self.len() - 16);
as_array!(value, 16).convert()
}
#[inline(always)]
fn read_last_u128x2(&self) -> [u128; 2] {
let (_, value) = self.split_at(self.len() - 32);
as_array!(value, 32).convert()
}
#[inline(always)]
fn read_last_u128x4(&self) -> [u128; 4] {
let (_, value) = self.split_at(self.len() - 64);
as_array!(value, 64).convert()
}
}
+392
View File
@@ -0,0 +1,392 @@
use crate::convert::*;
use crate::operations::folded_multiply;
use crate::operations::read_small;
use crate::random_state::PI;
use crate::RandomState;
use core::hash::Hasher;
///This constant come from Kunth's prng (Empirically it works better than those from splitmix32).
pub(crate) const MULTIPLE: u64 = 6364136223846793005;
const ROT: u32 = 23; //17
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
buffer: u64,
pad: u64,
extra_keys: [u64; 2],
}
impl AHasher {
/// Creates a new hasher keyed to the provided key.
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub fn new_with_keys(key1: u128, key2: u128) -> AHasher {
let pi: [u128; 2] = PI.convert();
let key1: [u64; 2] = (key1 ^ pi[0]).convert();
let key2: [u64; 2] = (key2 ^ pi[1]).convert();
AHasher {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
let key1: [u64; 2] = key1.convert();
let key2: [u64; 2] = key2.convert();
Self {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher {
AHasher {
buffer: rand_state.k0,
pad: rand_state.k1,
extra_keys: [rand_state.k2, rand_state.k3],
}
}
/// This update function has the goal of updating the buffer with a single multiply
/// FxHash does this but is vulnerable to attack. To avoid this input needs to be masked to with an
/// unpredictable value. Other hashes such as murmurhash have taken this approach but were found vulnerable
/// to attack. The attack was based on the idea of reversing the pre-mixing (Which is necessarily
/// reversible otherwise bits would be lost) then placing a difference in the highest bit before the
/// multiply used to mix the data. Because a multiply can never affect the bits to the right of it, a
/// subsequent update that also differed in this bit could result in a predictable collision.
///
/// This version avoids this vulnerability while still only using a single multiply. It takes advantage
/// of the fact that when a 64 bit multiply is performed the upper 64 bits are usually computed and thrown
/// away. Instead it creates two 128 bit values where the upper 64 bits are zeros and multiplies them.
/// (The compiler is smart enough to turn this into a 64 bit multiplication in the assembly)
/// Then the upper bits are xored with the lower bits to produce a single 64 bit result.
///
/// To understand why this is a good scrambling function it helps to understand multiply-with-carry PRNGs:
/// https://en.wikipedia.org/wiki/Multiply-with-carry_pseudorandom_number_generator
/// If the multiple is chosen well, this creates a long period, decent quality PRNG.
/// Notice that this function is equivalent to this except the `buffer`/`state` is being xored with each
/// new block of data. In the event that data is all zeros, it is exactly equivalent to a MWC PRNG.
///
/// This is impervious to attack because every bit buffer at the end is dependent on every bit in
/// `new_data ^ buffer`. For example suppose two inputs differed in only the 5th bit. Then when the
/// multiplication is performed the `result` will differ in bits 5-69. More specifically it will differ by
/// 2^5 * MULTIPLE. However in the next step bits 65-128 are turned into a separate 64 bit value. So the
/// differing bits will be in the lower 6 bits of this value. The two intermediate values that differ in
/// bits 5-63 and in bits 0-5 respectively get added together. Producing an output that differs in every
/// bit. The addition carries in the multiplication and at the end additionally mean that the even if an
/// attacker somehow knew part of (but not all) the contents of the buffer before hand,
/// they would not be able to predict any of the bits in the buffer at the end.
#[inline(always)]
#[cfg(feature = "folded_multiply")]
fn update(&mut self, new_data: u64) {
self.buffer = folded_multiply(new_data ^ self.buffer, MULTIPLE);
}
#[inline(always)]
#[cfg(not(feature = "folded_multiply"))]
fn update(&mut self, new_data: u64) {
let d1 = (new_data ^ self.buffer).wrapping_mul(MULTIPLE);
self.pad = (self.pad ^ d1).rotate_left(8).wrapping_mul(MULTIPLE);
self.buffer = (self.buffer ^ self.pad).rotate_left(24);
}
/// Similar to the above this function performs an update using a "folded multiply".
/// However it takes in 128 bits of data instead of 64. Both halves must be masked.
///
/// This makes it impossible for an attacker to place a single bit difference between
/// two blocks so as to cancel each other.
///
/// However this is not sufficient. to prevent (a,b) from hashing the same as (b,a) the buffer itself must
/// be updated between calls in a way that does not commute. To achieve this XOR and Rotate are used.
/// Add followed by xor is not the same as xor followed by add, and rotate ensures that the same out bits
/// can't be changed by the same set of input bits. To cancel this sequence with subsequent input would require
/// knowing the keys.
#[inline(always)]
#[cfg(feature = "folded_multiply")]
fn large_update(&mut self, new_data: u128) {
let block: [u64; 2] = new_data.convert();
let combined = folded_multiply(block[0] ^ self.extra_keys[0], block[1] ^ self.extra_keys[1]);
self.buffer = (self.buffer.wrapping_add(self.pad) ^ combined).rotate_left(ROT);
}
#[inline(always)]
#[cfg(not(feature = "folded_multiply"))]
fn large_update(&mut self, new_data: u128) {
let block: [u64; 2] = new_data.convert();
self.update(block[0] ^ self.extra_keys[0]);
self.update(block[1] ^ self.extra_keys[1]);
}
#[inline]
#[cfg(feature = "specialize")]
fn short_finish(&self) -> u64 {
self.buffer.wrapping_add(self.pad)
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.update(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.update(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.update(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.update(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.large_update(i);
}
#[inline]
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32", target_pointer_width = "16"))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len() as u64;
//Needs to be an add rather than an xor because otherwise it could be canceled with carefully formed input.
self.buffer = self.buffer.wrapping_add(length).wrapping_mul(MULTIPLE);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() > 8 {
if data.len() > 16 {
let tail = data.read_last_u128();
self.large_update(tail);
while data.len() > 16 {
let (block, rest) = data.read_u128();
self.large_update(block);
data = rest;
}
} else {
self.large_update([data.read_u64().0, data.read_last_u64()].convert());
}
} else {
let value = read_small(data);
self.large_update(value.convert());
}
}
#[inline]
#[cfg(feature = "folded_multiply")]
fn finish(&self) -> u64 {
let rot = (self.buffer & 63) as u32;
folded_multiply(self.buffer, self.pad).rotate_left(rot)
}
#[inline]
#[cfg(not(feature = "folded_multiply"))]
fn finish(&self) -> u64 {
let rot = (self.buffer & 63) as u32;
(self.buffer.wrapping_mul(MULTIPLE) ^ self.pad).rotate_left(rot)
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(feature = "specialize")]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
let rot = (self.pad & 63) as u32;
self.buffer.rotate_left(rot)
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(feature = "specialize")]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(feature = "specialize")]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for a single string
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(feature = "specialize")]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
self.0.finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes)
} else {
let value = read_small(bytes);
self.0.buffer = folded_multiply(value[0] ^ self.0.buffer,
value[1] ^ self.0.extra_keys[1]);
self.0.pad = self.0.pad.wrapping_add(bytes.len() as u64);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use crate::convert::Convert;
use crate::fallback_hash::*;
#[test]
fn test_hash() {
let mut hasher = AHasher::new_with_keys(0, 0);
let value: u64 = 1 << 32;
hasher.update(value);
let result = hasher.buffer;
let mut hasher = AHasher::new_with_keys(0, 0);
let value2: u64 = 1;
hasher.update(value2);
let result2 = hasher.buffer;
let result: [u8; 8] = result.convert();
let result2: [u8; 8] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
#[test]
fn test_conversion() {
let input: &[u8] = "dddddddd".as_bytes();
let bytes: u64 = as_array!(input, 8).convert();
assert_eq!(bytes, 0x6464646464646464);
}
}
+371
View File
@@ -0,0 +1,371 @@
use std::borrow::Borrow;
use std::collections::{hash_map, HashMap};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut, Index};
use std::panic::UnwindSafe;
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use crate::RandomState;
/// A [`HashMap`](std::collections::HashMap) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashMap<K, V, S = crate::RandomState>(HashMap<K, V, S>);
impl<K, V> From<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn from(item: HashMap<K, V, crate::RandomState>) -> Self {
AHashMap(item)
}
}
impl<K, V> Into<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn into(self) -> HashMap<K, V, crate::RandomState> {
self.0
}
}
impl<K, V> AHashMap<K, V, RandomState> {
pub fn new() -> Self {
AHashMap(HashMap::with_hasher(RandomState::default()))
}
pub fn with_capacity(capacity: usize) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, RandomState::default()))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashMap(HashMap::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
/// Returns a reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get(&1), Some(&"a"));
/// assert_eq!(map.get(&2), None);
/// ```
#[inline]
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get(k)
}
/// Returns the key-value pair corresponding to the supplied key.
///
/// The supplied key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get_key_value(&1), Some((&1, &"a")));
/// assert_eq!(map.get_key_value(&2), None);
/// ```
#[inline]
pub fn get_key_value<Q: ?Sized>(&self, k: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_key_value(k)
}
/// Returns a mutable reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// if let Some(x) = map.get_mut(&1) {
/// *x = "b";
/// }
/// assert_eq!(map[&1], "b");
/// ```
#[inline]
pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_mut(k)
}
/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, [`None`] is returned.
///
/// If the map did have this key present, the value is updated, and the old
/// value is returned. The key is not updated, though; this matters for
/// types that can be `==` without being identical. See the [module-level
/// documentation] for more.
///
/// [module-level documentation]: crate::collections#insert-and-complex-keys
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// assert_eq!(map.insert(37, "a"), None);
/// assert_eq!(map.is_empty(), false);
///
/// map.insert(37, "b");
/// assert_eq!(map.insert(37, "c"), Some("b"));
/// assert_eq!(map[&37], "c");
/// ```
#[inline]
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
self.0.insert(k, v)
}
/// Removes a key from the map, returning the value at the key if the key
/// was previously in the map.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.remove(&1), Some("a"));
/// assert_eq!(map.remove(&1), None);
/// ```
#[inline]
pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.remove(k)
}
}
impl<K, V, S> Deref for AHashMap<K, V, S> {
type Target = HashMap<K, V, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V, S> DerefMut for AHashMap<K, V, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K, V, S> UnwindSafe for AHashMap<K, V, S>
where
K: UnwindSafe,
V: UnwindSafe,
{
}
impl<K, V, S> PartialEq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: PartialEq,
S: BuildHasher,
{
fn eq(&self, other: &AHashMap<K, V, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<K, V, S> Eq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: Eq,
S: BuildHasher,
{
}
impl<K, Q: ?Sized, V, S> Index<&Q> for AHashMap<K, V, S>
where
K: Eq + Hash + Borrow<Q>,
Q: Eq + Hash,
S: BuildHasher,
{
type Output = V;
/// Returns a reference to the value corresponding to the supplied key.
///
/// # Panics
///
/// Panics if the key is not present in the `HashMap`.
#[inline]
fn index(&self, key: &Q) -> &V {
self.0.index(key)
}
}
impl<K, V, S> Debug for AHashMap<K, V, S>
where
K: Debug,
V: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<K, V, S> FromIterator<(K, V)> for AHashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher + Default,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
AHashMap(HashMap::from_iter(iter))
}
}
impl<'a, K, V, S> IntoIterator for &'a AHashMap<K, V, S> {
type Item = (&'a K, &'a V);
type IntoIter = hash_map::Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<'a, K, V, S> IntoIterator for &'a mut AHashMap<K, V, S> {
type Item = (&'a K, &'a mut V);
type IntoIter = hash_map::IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&mut self.0).iter_mut()
}
}
impl<K, V, S> IntoIterator for AHashMap<K, V, S> {
type Item = (K, V);
type IntoIter = hash_map::IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<K, V, S> Extend<(K, V)> for AHashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
impl<'a, K, V, S> Extend<(&'a K, &'a V)> for AHashMap<K, V, S>
where
K: Eq + Hash + Copy + 'a,
V: Copy + 'a,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (&'a K, &'a V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
impl<K, V> Default for AHashMap<K, V, RandomState> {
#[inline]
fn default() -> AHashMap<K, V, RandomState> {
AHashMap::new()
}
}
#[cfg(feature = "serde")]
impl<K, V> Serialize for AHashMap<K, V>
where
K: Serialize + Eq + Hash,
V: Serialize,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, K, V> Deserialize<'de> for AHashMap<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_map = HashMap::deserialize(deserializer);
hash_map.map(|hash_map| Self(hash_map))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_borrow() {
let mut map: AHashMap<String, String> = AHashMap::new();
map.insert("foo".to_string(), "Bar".to_string());
map.insert("Bar".to_string(), map.get("foo").unwrap().to_owned());
}
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
let mut map = AHashMap::new();
map.insert("for".to_string(), 0);
map.insert("bar".to_string(), 1);
let serialization = serde_json::to_string(&map).unwrap();
let deserialization: AHashMap<String, u64> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, map);
}
}
@@ -0,0 +1,517 @@
use core::hash::{Hash, Hasher};
use std::collections::{HashMap};
fn assert_sufficiently_different(a: u64, b: u64, tolerance: i32) {
let (same_byte_count, same_nibble_count) = count_same_bytes_and_nibbles(a, b);
assert!(same_byte_count <= tolerance, "{:x} vs {:x}: {:}", a, b, same_byte_count);
assert!(
same_nibble_count <= tolerance * 3,
"{:x} vs {:x}: {:}",
a,
b,
same_nibble_count
);
let flipped_bits = (a ^ b).count_ones();
assert!(
flipped_bits > 12 && flipped_bits < 52,
"{:x} and {:x}: {:}",
a,
b,
flipped_bits
);
for rotate in 0..64 {
let flipped_bits2 = (a ^ (b.rotate_left(rotate))).count_ones();
assert!(
flipped_bits2 > 10 && flipped_bits2 < 54,
"{:x} and {:x}: {:}",
a,
b.rotate_left(rotate),
flipped_bits2
);
}
}
fn count_same_bytes_and_nibbles(a: u64, b: u64) -> (i32, i32) {
let mut same_byte_count = 0;
let mut same_nibble_count = 0;
for byte in 0..8 {
let ba = (a >> (8 * byte)) as u8;
let bb = (b >> (8 * byte)) as u8;
if ba == bb {
same_byte_count += 1;
}
if ba & 0xF0u8 == bb & 0xF0u8 {
same_nibble_count += 1;
}
if ba & 0x0Fu8 == bb & 0x0Fu8 {
same_nibble_count += 1;
}
}
(same_byte_count, same_nibble_count)
}
fn gen_combinations(options: &[u32; 8], depth: u32, so_far: Vec<u32>, combinations: &mut Vec<Vec<u32>>) {
if depth == 0 {
return;
}
for option in options {
let mut next = so_far.clone();
next.push(*option);
combinations.push(next.clone());
gen_combinations(options, depth - 1, next, combinations);
}
}
fn test_no_full_collisions<T: Hasher>(gen_hash: impl Fn() -> T) {
let options: [u32; 8] = [
0x00000000, 0x20000000, 0x40000000, 0x60000000, 0x80000000, 0xA0000000, 0xC0000000, 0xE0000000,
];
let mut combinations = Vec::new();
gen_combinations(&options, 7, Vec::new(), &mut combinations);
let mut map: HashMap<u64, Vec<u8>> = HashMap::new();
for combination in combinations {
let array = unsafe {
let (begin, middle, end) = combination.align_to::<u8>();
assert_eq!(0, begin.len());
assert_eq!(0, end.len());
middle.to_vec()
};
let mut hasher = gen_hash();
hasher.write(&array);
let hash = hasher.finish();
if let Some(value) = map.get(&hash) {
assert_eq!(
value, &array,
"Found a collision between {:x?} and {:x?}. Hash: {:x?}",
value, &array, &hash
);
} else {
map.insert(hash, array);
}
}
assert_eq!(2396744, map.len());
}
fn test_keys_change_output<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut a = constructor(1, 1);
let mut b = constructor(1, 2);
let mut c = constructor(2, 1);
let mut d = constructor(2, 2);
"test".hash(&mut a);
"test".hash(&mut b);
"test".hash(&mut c);
"test".hash(&mut d);
assert_sufficiently_different(a.finish(), b.finish(), 1);
assert_sufficiently_different(a.finish(), c.finish(), 1);
assert_sufficiently_different(a.finish(), d.finish(), 1);
assert_sufficiently_different(b.finish(), c.finish(), 1);
assert_sufficiently_different(b.finish(), d.finish(), 1);
assert_sufficiently_different(c.finish(), d.finish(), 1);
}
fn test_input_affect_every_byte<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&0, constructor(0, 0));
for shift in 0..16 {
let mut alternitives = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher = constructor(0, 0);
alternitives.push(hash_with(&input, hasher));
}
assert_each_byte_differs(shift, base, alternitives);
}
}
///Ensures that for every bit in the output there is some value for each byte in the key that flips it.
fn test_keys_affect_every_byte<H: Hash, T: Hasher>(item: H, constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&item, constructor(0, 0));
for shift in 0..16 {
let mut alternitives1 = vec![];
let mut alternitives2 = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher1 = constructor(input, 0);
let hasher2 = constructor(0, input);
let h1 = hash_with(&item, hasher1);
let h2 = hash_with(&item, hasher2);
alternitives1.push(h1);
alternitives2.push(h2);
}
assert_each_byte_differs(shift, base, alternitives1);
assert_each_byte_differs(shift, base, alternitives2);
}
}
fn assert_each_byte_differs(num: u64, base: u64, alternitives: Vec<u64>) {
let mut changed_bits = 0_u64;
for alternitive in alternitives {
changed_bits |= base ^ alternitive
}
assert_eq!(core::u64::MAX, changed_bits, "Bits changed: {:x} on num: {:?}", changed_bits, num);
}
fn test_finish_is_consistent<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut hasher = constructor(1, 2);
"Foo".hash(&mut hasher);
let a = hasher.finish();
let b = hasher.finish();
assert_eq!(a, b);
}
fn test_single_key_bit_flip<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
for bit in 0..128 {
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234".hash(&mut a);
"1234".hash(&mut b);
"1234".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"12345678".hash(&mut a);
"12345678".hash(&mut b);
"12345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234567812345678".hash(&mut a);
"1234567812345678".hash(&mut b);
"1234567812345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
}
}
fn test_all_bytes_matter<T: Hasher>(hasher: impl Fn() -> T) {
let mut item = vec![0; 256];
let base_hash = hash(&item, &hasher);
for pos in 0..256 {
item[pos] = 255;
let hash = hash(&item, &hasher);
assert_ne!(base_hash, hash, "Position {} did not affect output", pos);
item[pos] = 0;
}
}
fn test_no_pair_collisions<T: Hasher>(hasher: impl Fn() -> T) {
let base = [0_u64, 0_u64];
let base_hash = hash(&base, &hasher);
for bitpos1 in 0..64 {
let a = 1_u64 << bitpos1;
for bitpos2 in 0..bitpos1 {
let b = 1_u64 << bitpos2;
let aa = hash(&[a, a], &hasher);
let ab = hash(&[a, b], &hasher);
let ba = hash(&[b, a], &hasher);
let bb = hash(&[b, b], &hasher);
assert_sufficiently_different(base_hash, aa, 3);
assert_sufficiently_different(base_hash, ab, 3);
assert_sufficiently_different(base_hash, ba, 3);
assert_sufficiently_different(base_hash, bb, 3);
assert_sufficiently_different(aa, ab, 3);
assert_sufficiently_different(ab, ba, 3);
assert_sufficiently_different(ba, bb, 3);
assert_sufficiently_different(aa, ba, 3);
assert_sufficiently_different(ab, bb, 3);
assert_sufficiently_different(aa, bb, 3);
}
}
}
fn hash<H: Hash, T: Hasher>(b: &H, hash_builder: &dyn Fn() -> T) -> u64 {
let mut hasher = hash_builder();
b.hash(&mut hasher);
hasher.finish()
}
fn hash_with<H: Hash, T: Hasher>(b: &H, mut hasher: T) -> u64 {
b.hash(&mut hasher);
hasher.finish()
}
fn test_single_bit_flip<T: Hasher>(hasher: impl Fn() -> T) {
let size = 32;
let compare_value = hash(&0u32, &hasher);
for pos in 0..size {
let test_value = hash(&(1u32 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 64;
let compare_value = hash(&0u64, &hasher);
for pos in 0..size {
let test_value = hash(&(1u64 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 128;
let compare_value = hash(&0u128, &hasher);
for pos in 0..size {
let test_value = hash(&(1u128 << pos), &hasher);
dbg!(compare_value, test_value);
assert_sufficiently_different(compare_value, test_value, 2);
}
}
fn test_padding_doesnot_collide<T: Hasher>(hasher: impl Fn() -> T) {
for c in 0..128u8 {
for string in ["", "\0", "\x01", "1234", "12345678", "1234567812345678"].iter() {
let mut short = hasher();
string.hash(&mut short);
let value = short.finish();
let mut padded = string.to_string();
for num in 1..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"{} bytes of {} -> {:x} vs {:x}", num, c, value, long.finish()
);
assert!(
same_nibbles <= 8,
"{} bytes of {} -> {:x} vs {:x}", num, c, value, long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
if string.len() > 0 {
let mut padded = string[1..].to_string();
padded.push(c as char);
for num in 2..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
assert!(
same_nibbles <= 8,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
}
}
}
}
fn test_length_extension<T: Hasher>(hasher: impl Fn(u128, u128) -> T) {
for key in 0..256 {
let h1 = hasher(key, key);
let v1 = hash_with(&[0_u8, 0, 0, 0, 0, 0, 0, 0], h1);
let h2 = hasher(key, key);
let v2 = hash_with(&[1_u8, 0, 0, 0, 0, 0, 0, 0, 0], h2);
assert_ne!(v1, v2);
}
}
fn test_sparse<T: Hasher>(hasher: impl Fn() -> T) {
let mut buf = [0u8; 256];
let mut hashes = HashMap::new();
for idx_1 in 0..256 {
for idx_2 in idx_1+1..256 {
for value_1 in [1, 2, 4, 8, 16, 32, 64, 128] {
for value_2 in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17, 18, 20, 24, 31, 32, 33, 48, 64, 96, 127, 128, 129, 192, 254, 255] {
buf[idx_1] = value_1;
buf[idx_2] = value_2;
let hash_value = hash_with(&buf, &mut hasher());
let keys = hashes.entry(hash_value).or_insert(Vec::new());
keys.push((idx_1, value_1, idx_2, value_2));
buf[idx_1] = 0;
buf[idx_2] = 0;
}
}
}
}
hashes.retain(|_key, value| value.len() != 1);
assert_eq!(0, hashes.len(), "Collision with: {:?}", hashes);
}
#[cfg(test)]
mod fallback_tests {
use crate::fallback_hash::*;
use crate::hash_quality_test::*;
#[test]
fn fallback_single_bit_flip() {
test_single_bit_flip(|| AHasher::new_with_keys(0, 0))
}
#[test]
fn fallback_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::new_with_keys)
}
#[test]
fn fallback_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_keys_change_output() {
test_keys_change_output(AHasher::new_with_keys);
}
#[test]
fn fallback_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::new_with_keys);
}
#[test]
fn fallback_keys_affect_every_byte() {
//For fallback second key is not used in every hash.
#[cfg(all(not(feature = "specialize"), feature = "folded_multiply"))]
test_keys_affect_every_byte(0, |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte("", |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte((0, 0), |a, b| AHasher::new_with_keys(a ^ b, a));
}
#[test]
fn fallback_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn fallback_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 2));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 2));
}
#[test]
fn fallback_length_extension() {
test_length_extension(|a, b| AHasher::new_with_keys(a, b));
}
#[test]
fn test_no_sparse_collisions() {
test_sparse(|| AHasher::new_with_keys(0, 0));
test_sparse(|| AHasher::new_with_keys(1, 2));
}
}
///Basic sanity tests of the cypto properties of aHash.
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
#[cfg(test)]
mod aes_tests {
use crate::aes_hash::*;
use crate::hash_quality_test::*;
use std::hash::{Hash, Hasher};
//This encrypts to 0.
const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363;
//This decrypts to 0.
const BAD_KEY: u128 = 0x5252_5252_5252_5252_5252_5252_5252_5252;
#[test]
fn test_single_bit_in_byte() {
let mut hasher1 = AHasher::test_with_keys(0, 0);
8_u32.hash(&mut hasher1);
let mut hasher2 = AHasher::test_with_keys(0, 0);
0_u32.hash(&mut hasher2);
assert_sufficiently_different(hasher1.finish(), hasher2.finish(), 1);
}
#[test]
fn aes_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::test_with_keys)
}
#[test]
fn aes_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn ase_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
}
#[test]
fn aes_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
}
#[test]
fn aes_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
}
#[test]
fn aes_keys_affect_every_byte() {
#[cfg(not(feature = "specialize"))]
test_keys_affect_every_byte(0, AHasher::test_with_keys);
test_keys_affect_every_byte("", AHasher::test_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::test_with_keys);
}
#[test]
fn aes_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn aes_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_length_extension() {
test_length_extension(|a, b| AHasher::test_with_keys(a, b));
}
#[test]
fn aes_no_sparse_collisions() {
test_sparse(|| AHasher::test_with_keys(0, 0));
test_sparse(|| AHasher::test_with_keys(1, 2));
}
}
+313
View File
@@ -0,0 +1,313 @@
use crate::RandomState;
use std::collections::{hash_set, HashSet};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{BitAnd, BitOr, BitXor, Deref, DerefMut, Sub};
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
/// A [`HashSet`](std::collections::HashSet) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashSet<T, S = crate::RandomState>(HashSet<T, S>);
impl<T> From<HashSet<T, crate::RandomState>> for AHashSet<T> {
fn from(item: HashSet<T, crate::RandomState>) -> Self {
AHashSet(item)
}
}
impl<T> Into<HashSet<T, crate::RandomState>> for AHashSet<T> {
fn into(self) -> HashSet<T, crate::RandomState> {
self.0
}
}
impl<T> AHashSet<T, RandomState> {
pub fn new() -> Self {
AHashSet(HashSet::with_hasher(RandomState::default()))
}
pub fn with_capacity(capacity: usize) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, RandomState::default()))
}
}
impl<T, S> AHashSet<T, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashSet(HashSet::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<T, S> Deref for AHashSet<T, S> {
type Target = HashSet<T, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, S> DerefMut for AHashSet<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T, S> PartialEq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
fn eq(&self, other: &AHashSet<T, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<T, S> Eq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
}
impl<T, S> BitOr<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the union of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a | &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 3, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitor(&rhs.0))
}
}
impl<T, S> BitAnd<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the intersection of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![2, 3, 4].into_iter().collect();
///
/// let set = &a & &b;
///
/// let mut i = 0;
/// let expected = [2, 3];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitand(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitand(&rhs.0))
}
}
impl<T, S> BitXor<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the symmetric difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a ^ &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitxor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitxor(&rhs.0))
}
}
impl<T, S> Sub<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a - &b;
///
/// let mut i = 0;
/// let expected = [1, 2];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn sub(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.sub(&rhs.0))
}
}
impl<T, S> Debug for AHashSet<T, S>
where
T: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<T, S> FromIterator<T> for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher + Default,
{
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> AHashSet<T, S> {
AHashSet(HashSet::from_iter(iter))
}
}
impl<'a, T, S> IntoIterator for &'a AHashSet<T, S> {
type Item = &'a T;
type IntoIter = hash_set::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<T, S> IntoIterator for AHashSet<T, S> {
type Item = T;
type IntoIter = hash_set::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T, S> Extend<T> for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
impl<'a, T, S> Extend<&'a T> for AHashSet<T, S>
where
T: 'a + Eq + Hash + Copy,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
impl<T> Default for AHashSet<T, RandomState> {
/// Creates an empty `AHashSet<T, S>` with the `Default` value for the hasher.
#[inline]
fn default() -> AHashSet<T, RandomState> {
AHashSet(HashSet::default())
}
}
#[cfg(feature = "serde")]
impl<T> Serialize for AHashSet<T>
where
T: Serialize + Eq + Hash,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for AHashSet<T>
where
T: Deserialize<'de> + Eq + Hash,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_set = HashSet::deserialize(deserializer);
hash_set.map(|hash_set| Self(hash_set))
}
}
#[cfg(all(test, feature = "serde"))]
mod test {
use super::*;
#[test]
fn test_serde() {
let mut set = AHashSet::new();
set.insert("for".to_string());
set.insert("bar".to_string());
let serialization = serde_json::to_string(&set).unwrap();
let deserialization: AHashSet<String> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, set);
}
}
+263
View File
@@ -0,0 +1,263 @@
//! AHash is a hashing algorithm is intended to be a high performance, (hardware specific), keyed hash function.
//! This can be seen as a DOS resistant alternative to `FxHash`, or a fast equivalent to `SipHash`.
//! It provides a high speed hash algorithm, but where the result is not predictable without knowing a Key.
//! This allows it to be used in a `HashMap` without allowing for the possibility that an malicious user can
//! induce a collision.
//!
//! # How aHash works
//!
//! aHash uses the hardware AES instruction on x86 processors to provide a keyed hash function.
//! aHash is not a cryptographically secure hash.
//!
//! # Example
//! ```
//! use ahash::{AHasher, RandomState};
//! use std::collections::HashMap;
//!
//! let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
//! map.insert(12, 34);
//! ```
//! For convinence wrappers called `AHashMap` and `AHashSet` are also provided.
//! These to the same thing with slightly less typing.
//! ```ignore
//! use ahash::AHashMap;
//!
//! let mut map: AHashMap<i32, i32> = AHashMap::with_capacity(4);
//! map.insert(12, 34);
//! map.insert(56, 78);
//! ```
#![deny(clippy::correctness, clippy::complexity, clippy::perf)]
#![allow(clippy::pedantic, clippy::cast_lossless, clippy::unreadable_literal)]
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
#![cfg_attr(feature = "specialize", feature(min_specialization))]
#![cfg_attr(feature = "stdsimd", feature(stdsimd))]
#[macro_use]
mod convert;
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
mod aes_hash;
mod fallback_hash;
#[cfg(test)]
mod hash_quality_test;
#[cfg(feature = "std")]
mod hash_map;
#[cfg(feature = "std")]
mod hash_set;
mod operations;
mod random_state;
mod specialize;
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
pub use crate::aes_hash::AHasher;
#[cfg(not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
)))]
pub use crate::fallback_hash::AHasher;
pub use crate::random_state::RandomState;
pub use crate::specialize::CallHasher;
#[cfg(feature = "std")]
pub use crate::hash_map::AHashMap;
#[cfg(feature = "std")]
pub use crate::hash_set::AHashSet;
use core::hash::BuildHasher;
use core::hash::Hash;
use core::hash::Hasher;
/// Provides a default [Hasher] with fixed keys.
/// This is typically used in conjunction with [BuildHasherDefault] to create
/// [AHasher]s in order to hash the keys of the map.
///
/// Generally it is preferable to use [RandomState] instead, so that different
/// hashmaps will have different keys. However if fixed keys are desireable this
/// may be used instead.
///
/// # Example
/// ```
/// use std::hash::BuildHasherDefault;
/// use ahash::{AHasher, RandomState};
/// use std::collections::HashMap;
///
/// let mut map: HashMap<i32, i32, BuildHasherDefault<AHasher>> = HashMap::default();
/// map.insert(12, 34);
/// ```
///
/// [BuildHasherDefault]: std::hash::BuildHasherDefault
/// [Hasher]: std::hash::Hasher
/// [HashMap]: std::collections::HashMap
impl Default for AHasher {
/// Constructs a new [AHasher] with fixed keys.
/// If `std` is enabled these will be generated upon first invocation.
/// Otherwise if the `compile-time-rng`feature is enabled these will be generated at compile time.
/// If neither of these features are available, hardcoded constants will be used.
///
/// Because the values are fixed, different hashers will all hash elements the same way.
/// This could make hash values predictable, if DOS attacks are a concern. If this behaviour is
/// not required, it may be preferable to use [RandomState] instead.
///
/// # Examples
///
/// ```
/// use ahash::AHasher;
/// use std::hash::Hasher;
///
/// let mut hasher_1 = AHasher::default();
/// let mut hasher_2 = AHasher::default();
///
/// hasher_1.write_u32(1234);
/// hasher_2.write_u32(1234);
///
/// assert_eq!(hasher_1.finish(), hasher_2.finish());
/// ```
#[inline]
fn default() -> AHasher {
RandomState::with_fixed_keys().build_hasher()
}
}
/// Used for specialization. (Sealed)
pub(crate) trait BuildHasherExt: BuildHasher {
#[doc(hidden)]
fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64;
#[doc(hidden)]
fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64;
#[doc(hidden)]
fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64;
}
impl<B: BuildHasher> BuildHasherExt for B {
#[inline]
#[cfg(feature = "specialize")]
default fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
#[cfg(not(feature = "specialize"))]
fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
#[cfg(feature = "specialize")]
default fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
#[cfg(not(feature = "specialize"))]
fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
#[cfg(feature = "specialize")]
default fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
#[cfg(not(feature = "specialize"))]
fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = self.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
// #[inline(never)]
// #[doc(hidden)]
// pub fn hash_test(input: &[u8]) -> u64 {
// let a = RandomState::with_seeds(11, 22, 33, 44);
// <[u8]>::get_hash(input, &a)
// }
#[cfg(feature = "std")]
#[cfg(test)]
mod test {
use crate::convert::Convert;
use crate::*;
use std::collections::HashMap;
use std::hash::Hash;
#[test]
fn test_default_builder() {
use core::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[test]
fn test_builder() {
let mut map = HashMap::<u32, u64, RandomState>::default();
map.insert(1, 3);
}
#[test]
fn test_conversion() {
let input: &[u8] = b"dddddddd";
let bytes: u64 = as_array!(input, 8).convert();
assert_eq!(bytes, 0x6464646464646464);
}
#[test]
fn test_non_zero() {
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
"foo".hash(&mut hasher1);
"bar".hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
3_u64.hash(&mut hasher1);
4_u64.hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_non_zero_specialized() {
let hasher_build = RandomState::with_seeds(0,0,0,0);
let h1 = str::get_hash("foo", &hasher_build);
let h2 = str::get_hash("bar", &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
let h1 = u64::get_hash(&3_u64, &hasher_build);
let h2 = u64::get_hash(&4_u64, &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
}
#[test]
fn test_ahasher_construction() {
let _ = AHasher::new_with_keys(1234, 5678);
}
}
+330
View File
@@ -0,0 +1,330 @@
use crate::convert::*;
/// This is a constant with a lot of special properties found by automated search.
/// See the unit tests below. (Below are alternative values)
#[cfg(all(target_feature = "ssse3", not(miri)))]
const SHUFFLE_MASK: u128 = 0x020a0700_0c01030e_050f0d08_06090b04_u128;
//const SHUFFLE_MASK: u128 = 0x000d0702_0a040301_05080f0c_0e0b0609_u128;
//const SHUFFLE_MASK: u128 = 0x040A0700_030E0106_0D050F08_020B0C09_u128;
#[inline(always)]
pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 {
let result = (s as u128).wrapping_mul(by as u128);
((result & 0xffff_ffff_ffff_ffff) as u64) ^ ((result >> 64) as u64)
}
/// Given a small (less than 8 byte slice) returns the same data stored in two u32s.
/// (order of and non-duplication of bytes is NOT guaranteed)
#[inline(always)]
pub(crate) fn read_small(data: &[u8]) -> [u64; 2] {
debug_assert!(data.len() <= 8);
if data.len() >= 2 {
if data.len() >= 4 {
//len 4-8
[data.read_u32().0 as u64, data.read_last_u32() as u64]
} else {
//len 2-3
[data.read_u16().0 as u64, data[data.len() - 1] as u64]
}
} else {
if data.len() > 0 {
[data[0] as u64, data[0] as u64]
} else {
[0, 0]
}
}
}
#[inline(always)]
pub(crate) fn shuffle(a: u128) -> u128 {
#[cfg(all(target_feature = "ssse3", not(miri)))]
{
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use core::mem::transmute;
unsafe { transmute(_mm_shuffle_epi8(transmute(a), transmute(SHUFFLE_MASK))) }
}
#[cfg(not(all(target_feature = "ssse3", not(miri))))]
{
a.swap_bytes()
}
}
#[allow(unused)] //not used by fallback
#[inline(always)]
pub(crate) fn add_and_shuffle(a: u128, b: u128) -> u128 {
let sum = add_by_64s(a.convert(), b.convert());
shuffle(sum.convert())
}
#[allow(unused)] //not used by fallbac
#[inline(always)]
pub(crate) fn shuffle_and_add(base: u128, to_add: u128) -> u128 {
let shuffled: [u64; 2] = shuffle(base).convert();
add_by_64s(shuffled, to_add.convert()).convert()
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri)))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
use core::mem::transmute;
unsafe {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
transmute(_mm_add_epi64(transmute(a), transmute(b)))
}
}
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri))))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
[a[0].wrapping_add(b[0]), a[1].wrapping_add(b[1])]
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use core::mem::transmute;
unsafe {
let value = transmute(value);
transmute(_mm_aesenc_si128(value, transmute(xor)))
}
}
#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd"))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
use core::mem::transmute;
unsafe {
let value = transmute(value);
transmute(vaesmcq_u8(vaeseq_u8(value, transmute(xor))))
}
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use core::mem::transmute;
unsafe {
let value = transmute(value);
transmute(_mm_aesdec_si128(value, transmute(xor)))
}
}
#[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd"))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
use core::mem::transmute;
unsafe {
let value = transmute(value);
transmute(vaesimcq_u8(vaesdq_u8(value, transmute(xor))))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::convert::Convert;
// This is code to search for the shuffle constant
//
//thread_local! { static MASK: Cell<u128> = Cell::new(0); }
//
// fn shuffle(a: u128) -> u128 {
// use std::intrinsics::transmute;
// #[cfg(target_arch = "x86")]
// use core::arch::x86::*;
// #[cfg(target_arch = "x86_64")]
// use core::arch::x86_64::*;
// MASK.with(|mask| {
// unsafe { transmute(_mm_shuffle_epi8(transmute(a), transmute(mask.get()))) }
// })
// }
//
// #[test]
// fn find_shuffle() {
// use rand::prelude::*;
// use SliceRandom;
// use std::panic;
// use std::io::Write;
//
// let mut value: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15];
// let mut rand = thread_rng();
// let mut successful_list = HashMap::new();
// for _attempt in 0..10000000 {
// rand.shuffle(&mut value);
// let test_val = value.convert();
// MASK.with(|mask| {
// mask.set(test_val);
// });
// if let Ok(successful) = panic::catch_unwind(|| {
// test_shuffle_does_not_collide_with_aes();
// test_shuffle_moves_high_bits();
// test_shuffle_moves_every_value();
// //test_shuffle_does_not_loop();
// value
// }) {
// let successful: u128 = successful.convert();
// successful_list.insert(successful, iters_before_loop());
// }
// }
// let write_file = File::create("/tmp/output").unwrap();
// let mut writer = BufWriter::new(&write_file);
//
// for success in successful_list {
// writeln!(writer, "Found successful: {:x?} - {:?}", success.0, success.1);
// }
// }
//
// fn iters_before_loop() -> u32 {
// let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
// let mut shuffled = shuffle(numbered);
// let mut count = 0;
// loop {
// // println!("{:>16x}", shuffled);
// if numbered == shuffled {
// break;
// }
// count += 1;
// shuffled = shuffle(shuffled);
// }
// count
// }
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
target_feature = "aes",
not(miri)
))]
#[test]
fn test_shuffle_does_not_collide_with_aes() {
let mut value: [u8; 16] = [0; 16];
let zero_mask_enc = aesenc(0, 0);
let zero_mask_dec = aesdec(0, 0);
for index in 0..16 {
value[index] = 1;
let excluded_positions_enc: [u8; 16] = aesenc(value.convert(), zero_mask_enc).convert();
let excluded_positions_dec: [u8; 16] = aesdec(value.convert(), zero_mask_dec).convert();
let actual_location: [u8; 16] = shuffle(value.convert()).convert();
for pos in 0..16 {
if actual_location[pos] != 0 {
assert_eq!(
0, excluded_positions_enc[pos],
"Forward Overlap between {:?} and {:?} at {}",
excluded_positions_enc, actual_location, index
);
assert_eq!(
0, excluded_positions_dec[pos],
"Reverse Overlap between {:?} and {:?} at {}",
excluded_positions_dec, actual_location, index
);
}
}
value[index] = 0;
}
}
#[test]
fn test_shuffle_contains_each_value() {
let value: [u8; 16] = 0x00010203_04050607_08090A0B_0C0D0E0F_u128.convert();
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
for index in 0..16_u8 {
assert!(shuffled.contains(&index), "Value is missing {}", index);
}
}
#[test]
fn test_shuffle_moves_every_value() {
let mut value: [u8; 16] = [0; 16];
for index in 0..16 {
value[index] = 1;
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
assert_eq!(0, shuffled[index], "Value is not moved {}", index);
value[index] = 0;
}
}
#[test]
fn test_shuffle_moves_high_bits() {
assert!(
shuffle(1) > (1_u128 << 80),
"Low bits must be moved to other half {:?} -> {:?}",
0,
shuffle(1)
);
assert!(
shuffle(1_u128 << 58) >= (1_u128 << 64),
"High bits must be moved to other half {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 58) < (1_u128 << 112),
"High bits must not remain high {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 64) < (1_u128 << 64),
"Low bits must be moved to other half {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 64) >= (1_u128 << 16),
"Low bits must not remain low {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 120) < (1_u128 << 50),
"High bits must be moved to low half {:?} -> {:?}",
15,
shuffle(1_u128 << 120)
);
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
not(miri)
))]
#[test]
fn test_shuffle_does_not_loop() {
let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
let mut shuffled = shuffle(numbered);
for count in 0..100 {
// println!("{:>16x}", shuffled);
assert_ne!(numbered, shuffled, "Equal after {} vs {:x}", count, shuffled);
shuffled = shuffle(shuffled);
}
}
}
+363
View File
@@ -0,0 +1,363 @@
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
use crate::convert::Convert;
#[cfg(feature = "specialize")]
use crate::BuildHasherExt;
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
pub use crate::aes_hash::*;
#[cfg(not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
)))]
pub use crate::fallback_hash::*;
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
use const_random::const_random;
use core::any::{Any, TypeId};
use core::fmt;
use core::hash::BuildHasher;
#[cfg(feature = "specialize")]
use core::hash::Hash;
use core::hash::Hasher;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(feature = "atomic-polyfill")]
use atomic_polyfill as atomic;
#[cfg(not(feature = "atomic-polyfill"))]
use core::sync::atomic;
use alloc::boxed::Box;
use atomic::{AtomicUsize, Ordering};
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
use once_cell::race::OnceBox;
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
use crate::aes_hash::*;
#[cfg(not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
)))]
use crate::fallback_hash::*;
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
static RAND_SOURCE: OnceBox<Box<dyn RandomSource + Send + Sync>> = OnceBox::new();
/// A supplier of Randomness used for different hashers.
/// See [RandomState.set_random_source].
pub trait RandomSource {
fn get_fixed_seeds(&self) -> &'static [[u64; 4]; 2];
fn gen_hasher_seed(&self) -> usize;
}
pub(crate) const PI: [u64; 4] = [
0x243f_6a88_85a3_08d3,
0x1319_8a2e_0370_7344,
0xa409_3822_299f_31d0,
0x082e_fa98_ec4e_6c89,
];
pub(crate) const PI2: [u64; 4] = [
0x4528_21e6_38d0_1377,
0xbe54_66cf_34e9_0c6c,
0xc0ac_29b7_c97c_50dd,
0x3f84_d5b5_b547_0917,
];
struct DefaultRandomSource {
counter: AtomicUsize,
}
impl DefaultRandomSource {
fn new() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(&PI as *const _ as usize),
}
}
const fn default() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(PI[3] as usize),
}
}
}
impl RandomSource for DefaultRandomSource {
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
fn get_fixed_seeds(&self) -> &'static [[u64; 4]; 2] {
static SEEDS: OnceBox<[[u64; 4]; 2]> = OnceBox::new();
SEEDS.get_or_init(|| {
let mut result: [u8; 64] = [0; 64];
getrandom::getrandom(&mut result).expect("getrandom::getrandom() failed.");
Box::new(result.convert())
})
}
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
fn get_fixed_seeds(&self) -> &'static [[u64; 4]; 2] {
const RAND: [[u64; 4]; 2] = [
[
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
], [
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
]
];
&RAND
}
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
fn get_fixed_seeds(&self) -> &'static [[u64; 4]; 2] {
&[PI, PI2]
}
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
self.counter.fetch_add(stack, Ordering::Relaxed)
}
#[cfg(all(target_arch = "arm", target_os = "none"))]
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
let previous = self.counter.load(Ordering::Relaxed);
let new = previous.wrapping_add(stack);
self.counter.store(new, Ordering::Relaxed);
new
}
}
/// Provides a [Hasher] factory. This is typically used (e.g. by [HashMap]) to create
/// [AHasher]s in order to hash the keys of the map. See `build_hasher` below.
///
/// [build_hasher]: ahash::
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
#[derive(Clone)]
pub struct RandomState {
pub(crate) k0: u64,
pub(crate) k1: u64,
pub(crate) k2: u64,
pub(crate) k3: u64,
}
impl fmt::Debug for RandomState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("RandomState { .. }")
}
}
impl RandomState {
/// Provides an optional way to manually supply a source of randomness for Hasher keys.
///
/// The provided [RandomSource] will be used to be used as a source of randomness by [RandomState] to generate new states.
/// If this method is not invoked the standard source of randomness is used as described in the Readme.
///
/// The source of randomness can only be set once, and must be set before the first RandomState is created.
/// If the source has already been specified `Err` is returned with a `bool` indicating if the set failed because
/// method was previously invoked (true) or if the default source is already being used (false).
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub fn set_random_source(source: impl RandomSource + Send + Sync + 'static) -> Result<(), bool> {
RAND_SOURCE.set(Box::new(Box::new(source))).map_err(|s| s.as_ref().type_id() != TypeId::of::<&DefaultRandomSource>())
}
#[inline]
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
fn get_src() -> &'static dyn RandomSource {
RAND_SOURCE.get_or_init(|| Box::new(Box::new(DefaultRandomSource::new()))).as_ref()
}
#[inline]
#[cfg(all(target_arch = "arm", target_os = "none"))]
fn get_src() -> &'static dyn RandomSource {
static RAND_SOURCE: DefaultRandomSource = DefaultRandomSource::default();
&RAND_SOURCE
}
/// Use randomly generated keys
#[inline]
pub fn new() -> RandomState {
let src = Self::get_src();
let fixed = src.get_fixed_seeds();
Self::from_keys(&fixed[0], &fixed[1], src.gen_hasher_seed())
}
/// Allows for supplying seeds, but each time it is called the resulting state will be different.
/// This is done using a static counter, so it can safely be used with a fixed keys.
#[inline]
pub fn generate_with(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
let src = Self::get_src();
let fixed = src.get_fixed_seeds();
RandomState::from_keys(&fixed[0], &[k0, k1, k2, k3], src.gen_hasher_seed())
}
fn from_keys(a: &[u64; 4], b: &[u64; 4], c: usize) -> RandomState {
let &[k0, k1, k2, k3] = a;
let mut hasher = AHasher::from_random_state(&RandomState { k0, k1, k2, k3 });
hasher.write_usize(c);
let mix = |k: u64| {
let mut h = hasher.clone();
h.write_u64(k);
h.finish()
};
RandomState {
k0: mix(b[0]),
k1: mix(b[1]),
k2: mix(b[2]),
k3: mix(b[3]),
}
}
/// Internal. Used by Default.
#[inline]
pub(crate) fn with_fixed_keys() -> RandomState {
let [k0, k1, k2, k3] = Self::get_src().get_fixed_seeds()[0];
RandomState { k0, k1, k2, k3 }
}
/// Allows for explicitly setting a seed to used.
///
/// Note: This method does not require the provided seed to be strong.
#[inline]
pub fn with_seed(key: usize) -> RandomState {
let fixed = Self::get_src().get_fixed_seeds();
RandomState::from_keys(&fixed[0], &fixed[1], key)
}
/// Allows for explicitly setting the seeds to used.
///
/// Note: This method is robust against 0s being passed for one or more of the parameters
/// or the same value being passed for more than one parameter.
#[inline]
pub const fn with_seeds(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
RandomState { k0: k0 ^ PI2[0], k1: k1 ^ PI2[1], k2: k2 ^ PI2[2], k3: k3 ^ PI2[3] }
}
}
impl Default for RandomState {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl BuildHasher for RandomState {
type Hasher = AHasher;
/// Constructs a new [AHasher] with keys based on this [RandomState] object.
/// This means that two different [RandomState]s will will generate
/// [AHasher]s that will return different hashcodes, but [Hasher]s created from the same [BuildHasher]
/// will generate the same hashes for the same input data.
///
/// # Examples
///
/// ```
/// use ahash::{AHasher, RandomState};
/// use std::hash::{Hasher, BuildHasher};
///
/// let build_hasher = RandomState::new();
/// let mut hasher_1 = build_hasher.build_hasher();
/// let mut hasher_2 = build_hasher.build_hasher();
///
/// hasher_1.write_u32(1234);
/// hasher_2.write_u32(1234);
///
/// assert_eq!(hasher_1.finish(), hasher_2.finish());
///
/// let other_build_hasher = RandomState::new();
/// let mut different_hasher = other_build_hasher.build_hasher();
/// different_hasher.write_u32(1234);
/// assert_ne!(different_hasher.finish(), hasher_1.finish());
/// ```
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
#[inline]
fn build_hasher(&self) -> AHasher {
AHasher::from_random_state(self)
}
}
#[cfg(feature = "specialize")]
impl BuildHasherExt for RandomState {
#[inline]
fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherU64 {
buffer: self.k0,
pad: self.k1,
};
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherFixed(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherStr(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_unique() {
let a = RandomState::new();
let b = RandomState::new();
assert_ne!(a.build_hasher().finish(), b.build_hasher().finish());
}
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
#[test]
fn test_not_pi() {
assert_ne!(PI, RandomState::get_src().get_fixed_seeds()[0]);
}
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
#[test]
fn test_not_pi_const() {
assert_ne!(PI, RandomState::get_src().get_fixed_seeds()[0]);
}
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
#[test]
fn test_pi() {
assert_eq!(PI, RandomState::get_src().get_fixed_seeds()[0]);
}
#[test]
fn test_with_seeds_const() {
const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19, 21, 23);
}
}
+239
View File
@@ -0,0 +1,239 @@
use core::hash::BuildHasher;
use core::hash::Hash;
use core::hash::Hasher;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(feature = "specialize")]
use crate::BuildHasherExt;
#[cfg(feature = "specialize")]
use alloc::string::String;
#[cfg(feature = "specialize")]
use alloc::vec::Vec;
/// Provides a way to get an optimized hasher for a given data type.
/// Rather than using a Hasher generically which can hash any value, this provides a way to get a specialized hash
/// for a specific type. So this may be faster for primitive types.
/// # Example
/// ```
/// use std::hash::BuildHasher;
/// use ahash::RandomState;
/// use ahash::CallHasher;
///
/// let hash_builder = RandomState::new();
/// //...
/// let value: u32 = 17;
/// let hash = u32::get_hash(&value, &hash_builder);
/// ```
/// Note that the type used to invoke `get_hash` must be the same a the type of value passed.
/// For example get a hasher specialized on `[u8]` can invoke:
/// ```
/// /// use std::hash::BuildHasher;
/// # use ahash::RandomState;
/// # use ahash::CallHasher;
/// # let hash_builder = RandomState::new();
/// let bytes: [u8; 4] = [1, 2, 3, 4];
/// let hash = <[u8]>::get_hash(&bytes, &hash_builder);
/// ```
pub trait CallHasher {
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64;
}
#[cfg(not(feature = "specialize"))]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
let mut hasher = build_hasher.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(feature = "specialize")]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
default fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
let mut hasher = build_hasher.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
macro_rules! call_hasher_impl {
($typ:ty) => {
#[cfg(feature = "specialize")]
impl CallHasher for $typ {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_u64(value)
}
}
};
}
call_hasher_impl!(u8);
call_hasher_impl!(u16);
call_hasher_impl!(u32);
call_hasher_impl!(u64);
call_hasher_impl!(i8);
call_hasher_impl!(i16);
call_hasher_impl!(i32);
call_hasher_impl!(i64);
#[cfg(feature = "specialize")]
impl CallHasher for u128 {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_fixed_length(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for i128 {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_fixed_length(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for usize {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_fixed_length(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for isize {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_fixed_length(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for [u8] {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_str(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for Vec<u8> {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_str(value)
}
}
#[cfg(feature = "specialize")]
impl CallHasher for str {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_str(value)
}
}
#[cfg(all(feature = "specialize"))]
impl CallHasher for String {
#[inline]
fn get_hash<H: Hash + ?Sized, B: BuildHasher>(value: &H, build_hasher: &B) -> u64 {
build_hasher.hash_as_str(value)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
#[cfg(feature = "specialize")]
pub fn test_specialized_invoked() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
let shortened = u64::get_hash(&0, &build_hasher);
let mut hasher = AHasher::new_with_keys(1, 2);
0_u64.hash(&mut hasher);
assert_ne!(hasher.finish(), shortened);
}
/// Tests that some non-trivial transformation takes place.
#[test]
pub fn test_input_processed() {
let build_hasher = RandomState::with_seeds(2, 2, 2, 2);
assert_ne!(0, u64::get_hash(&0, &build_hasher));
assert_ne!(1, u64::get_hash(&0, &build_hasher));
assert_ne!(2, u64::get_hash(&0, &build_hasher));
assert_ne!(3, u64::get_hash(&0, &build_hasher));
assert_ne!(4, u64::get_hash(&0, &build_hasher));
assert_ne!(5, u64::get_hash(&0, &build_hasher));
assert_ne!(0, u64::get_hash(&1, &build_hasher));
assert_ne!(1, u64::get_hash(&1, &build_hasher));
assert_ne!(2, u64::get_hash(&1, &build_hasher));
assert_ne!(3, u64::get_hash(&1, &build_hasher));
assert_ne!(4, u64::get_hash(&1, &build_hasher));
assert_ne!(5, u64::get_hash(&1, &build_hasher));
let xored = u64::get_hash(&0, &build_hasher) ^ u64::get_hash(&1, &build_hasher);
assert_ne!(0, xored);
assert_ne!(1, xored);
assert_ne!(2, xored);
assert_ne!(3, xored);
assert_ne!(4, xored);
assert_ne!(5, xored);
}
#[test]
pub fn test_ref_independent() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
assert_eq!(u8::get_hash(&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(feature = "specialize")]
assert_eq!(
str::get_hash(&"test", &build_hasher),
<[u8]>::get_hash("test".as_bytes(), &build_hasher)
);
let build_hasher = RandomState::with_seeds(10, 20, 30, 40);
assert_eq!(u8::get_hash(&&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(feature = "specialize")]
assert_eq!(
str::get_hash(&&"test", &build_hasher),
<[u8]>::get_hash(&"test".to_string().into_bytes(), &build_hasher)
);
}
}
+154
View File
@@ -0,0 +1,154 @@
use ahash::{CallHasher, RandomState};
use criterion::*;
use fxhash::FxHasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
fn aeshash<H: Hash>(b: &H) -> u64 {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
H::get_hash(b, &build_hasher)
}
#[cfg(not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
)))]
fn aeshash<H: Hash>(_b: &H) -> u64 {
panic!("aes must be enabled")
}
#[cfg(not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
)))]
fn fallbackhash<H: Hash>(b: &H) -> u64 {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
H::get_hash(b, &build_hasher)
}
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd")
))]
fn fallbackhash<H: Hash>(_b: &H) -> u64 {
panic!("aes must be disabled")
}
fn fnvhash<H: Hash>(b: &H) -> u64 {
let mut hasher = fnv::FnvHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn siphash<H: Hash>(b: &H) -> u64 {
let mut hasher = DefaultHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn fxhash<H: Hash>(b: &H) -> u64 {
let mut hasher = FxHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn seahash<H: Hash>(b: &H) -> u64 {
let mut hasher = seahash::SeaHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
const STRING_LENGTHS: [u32; 12] = [1, 3, 4, 7, 8, 15, 16, 24, 33, 68, 132, 1024];
fn gen_strings() -> Vec<String> {
STRING_LENGTHS
.iter()
.map(|len| {
let mut string = String::default();
for pos in 1..=*len {
let c = (48 + (pos % 10) as u8) as char;
string.push(c);
}
string
})
.collect()
}
const U8_VALUE: u8 = 123;
const U16_VALUE: u16 = 1234;
const U32_VALUE: u32 = 12345678;
const U64_VALUE: u64 = 1234567890123456;
const U128_VALUE: u128 = 12345678901234567890123456789012;
fn bench_ahash(c: &mut Criterion) {
let mut group = c.benchmark_group("aeshash");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(aeshash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(aeshash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(aeshash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(aeshash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(aeshash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(aeshash(s))));
}
fn bench_fallback(c: &mut Criterion) {
let mut group = c.benchmark_group("fallback");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(fallbackhash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(fallbackhash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(fallbackhash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(fallbackhash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(fallbackhash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(fallbackhash(s))));
}
fn bench_fx(c: &mut Criterion) {
let mut group = c.benchmark_group("fx");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(fxhash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(fxhash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(fxhash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(fxhash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(fxhash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(fxhash(s))));
}
fn bench_fnv(c: &mut Criterion) {
let mut group = c.benchmark_group("fnv");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(fnvhash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(fnvhash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(fnvhash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(fnvhash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(fnvhash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(fnvhash(s))));
}
fn bench_sea(c: &mut Criterion) {
let mut group = c.benchmark_group("sea");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(seahash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(seahash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(seahash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(seahash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(seahash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(seahash(s))));
}
fn bench_sip(c: &mut Criterion) {
let mut group = c.benchmark_group("sip");
group.bench_with_input("u8", &U8_VALUE, |b, s| b.iter(|| black_box(siphash(s))));
group.bench_with_input("u16", &U16_VALUE, |b, s| b.iter(|| black_box(siphash(s))));
group.bench_with_input("u32", &U32_VALUE, |b, s| b.iter(|| black_box(siphash(s))));
group.bench_with_input("u64", &U64_VALUE, |b, s| b.iter(|| black_box(siphash(s))));
group.bench_with_input("u128", &U128_VALUE, |b, s| b.iter(|| black_box(siphash(s))));
group.bench_with_input("string", &gen_strings(), |b, s| b.iter(|| black_box(siphash(s))));
}
criterion_main!(benches);
criterion_group!(
benches,
bench_ahash,
bench_fallback,
bench_fx,
bench_fnv,
bench_sea,
bench_sip
);
+203
View File
@@ -0,0 +1,203 @@
use std::hash::{BuildHasher, Hash, Hasher};
use criterion::*;
use fxhash::FxHasher;
use ahash::{AHasher, CallHasher, RandomState};
fn gen_word_pairs() -> Vec<String> {
let words: Vec<_> = r#"
a, ability, able, about, above, accept, according, account, across, act, action,
activity, actually, add, address, administration, admit, adult, affect, after,
again, against, age, agency, agent, ago, agree, agreement, ahead, air, all,
allow, almost, alone, along, already, also, although, always, American, among,
amount, analysis, and, animal, another, answer, any, anyone, anything, appear,
apply, approach, area, argue, arm, around, arrive, art, article, artist, as,
ask, assume, at, attack, attention, attorney, audience, author, authority,
available, avoid, away, baby, back, bad, bag, ball, bank, bar, base, be, beat,
beautiful, because, become, bed, before, begin, behavior, behind, believe,
benefit, best, better, between, beyond, big, bill, billion, bit, black, blood,
blue, board, body, book, born, both, box, boy, break, bring, brother, budget,
build, building, business, but, buy, by, call, camera, campaign, can, cancer,
candidate, capital, car, card, care, career, carry, case, catch, cause, cell,
center, central, century, certain, certainly, chair, challenge, chance, change,
character, charge, check, child, choice, choose, church, citizen, city, civil,
claim, class, clear, clearly, close, coach, cold, collection, college, color,
come, commercial, common, community, company, compare, computer, concern,
condition, conference, Congress, consider, consumer, contain, continue, control,
cost, could, country, couple, course, court, cover, create, crime, cultural,
culture, cup, current, customer, cut, dark, data, daughter, day, dead, deal,
death, debate, decade, decide, decision, deep, defense, degree, Democrat,
democratic, describe, design, despite, detail, determine, develop, development,
die, difference, different, difficult, dinner, direction, director, discover,
discuss, discussion, disease, do, doctor, dog, door, down, draw, dream, drive,
drop, drug, during, each, early, east, easy, eat, economic, economy, edge,
education, effect, effort, eight, either, election, else, employee, end, energy,
enjoy, enough, enter, entire, environment, environmental, especially, establish,
even, evening, event, ever, every, everybody, everyone, everything, evidence,
exactly, example, executive, exist, expect, experience, expert, explain, eye,
face, fact, factor, fail, fall, family, far, fast, father, fear, federal, feel,
feeling, few, field, fight, figure, fill, film, final, finally, financial, find,
fine, finger, finish, fire, firm, first, fish, five, floor, fly, focus, follow,
food, foot, for, force, foreign, forget, form, former, forward, four, free,
friend, from, front, full, fund, future, game, garden, gas, general, generation,
get, girl, give, glass, go, goal, good, government, great, green, ground, group,
grow, growth, guess, gun, guy, hair, half, hand, hang, happen, happy, hard,
have, he, head, health, hear, heart, heat, heavy, help, her, here, herself,
high, him, himself, his, history, hit, hold, home, hope, hospital, hot, hotel,
hour, house, how, however, huge, human, hundred, husband, I, idea, identify, if,
image, imagine, impact, important, improve, in, include, including, increase,
indeed, indicate, individual, industry, information, inside, instead,
institution, interest, interesting, international, interview, into, investment,
involve, issue, it, item, its, itself, job, join, just, keep, key, kid, kill,
kind, kitchen, know, knowledge, land, language, large, last, late, later, laugh,
law, lawyer, lay, lead, leader, learn, least, leave, left, leg, legal, less,
let, letter, level, lie, life, light, like, likely, line, list, listen, little,
live, local, long, look, lose, loss, lot, love, low, machine, magazine, main,
maintain, major, majority, make, man, manage, management, manager, many, market,
marriage, material, matter, may, maybe, me, mean, measure, media, medical, meet,
meeting, member, memory, mention, message, method, middle, might, military,
million, mind, minute, miss, mission, model, modern, moment, money, month, more,
morning, most, mother, mouth, move, movement, movie, Mr, Mrs, much, music, must,
my, myself, name, nation, national, natural, nature, near, nearly, necessary,
need, network, never, new, news, newspaper, next, nice, night, no, none, nor,
north, not, note, nothing, notice, now, n't, number, occur, of, off, offer,
office, officer, official, often, oh, oil, ok, old, on, once, one, only, onto,
open, operation, opportunity, option, or, order, organization, other, others,
our, out, outside, over, own, owner, page, pain, painting, paper, parent, part,
participant, particular, particularly, partner, party, pass, past, patient,
pattern, pay, peace, people, per, perform, performance, perhaps, period, person,
personal, phone, physical, pick, picture, piece, place, plan, plant, play,
player, PM, point, police, policy, political, politics, poor, popular,
population, position, positive, possible, power, practice, prepare, present,
president, pressure, pretty, prevent, price, private, probably, problem,
process, produce, product, production, professional, professor, program,
project, property, protect, prove, provide, public, pull, purpose, push, put,
quality, question, quickly, quite, race, radio, raise, range, rate, rather,
reach, read, ready, real, reality, realize, really, reason, receive, recent,
recently, recognize, record, red, reduce, reflect, region, relate, relationship,
religious, remain, remember, remove, report, represent, Republican, require,
research, resource, respond, response, responsibility, rest, result, return,
reveal, rich, right, rise, risk, road, rock, role, room, rule, run, safe, same,
save, say, scene, school, science, scientist, score, sea, season, seat, second,
section, security, see, seek, seem, sell, send, senior, sense, series, serious,
serve, service, set, seven, several, sex, sexual, shake, share, she, shoot,
short, shot, should, shoulder, show, side, sign, significant, similar, simple,
simply, since, sing, single, sister, sit, site, situation, six, size, skill,
skin, small, smile, so, social, society, soldier, some, somebody, someone,
something, sometimes, son, song, soon, sort, sound, source, south, southern,
space, speak, special, specific, speech, spend, sport, spring, staff, stage,
stand, standard, star, start, state, statement, station, stay, step, still,
stock, stop, store, story, strategy, street, strong, structure, student, study,
stuff, style, subject, success, successful, such, suddenly, suffer, suggest,
summer, support, sure, surface, system, table, take, talk, task, tax, teach,
teacher, team, technology, television, tell, ten, tend, term, test, than, thank,
that, the, their, them, themselves, then, theory, there, these, they, thing,
think, third, this, those, though, thought, thousand, threat, three, through,
throughout, throw, thus, time, to, today, together, tonight, too, top, total,
tough, toward, town, trade, traditional, training, travel, treat, treatment,
tree, trial, trip, trouble, true, truth, try, turn, TV, two, type, under,
understand, unit, until, up, upon, us, use, usually, value, various, very,
victim, view, violence, visit, voice, vote, wait, walk, wall, want, war, watch,
water, way, we, weapon, wear, week, weight, well, west, western, what, whatever,
when, where, whether, which, while, white, who, whole, whom, whose, why, wide,
wife, will, win, wind, window, wish, with, within, without, woman, wonder, word,
work, worker, world, worry, would, write, writer, wrong, yard, yeah, year, yes,
yet, you, young, your, yourself"#
.split(',')
.map(|word| word.trim())
.collect();
let mut word_pairs: Vec<_> = Vec::new();
for word in &words {
for other_word in &words {
word_pairs.push(word.to_string() + " " + other_word);
}
}
assert_eq!(1_000_000, word_pairs.len());
word_pairs
}
#[allow(unused)] // False positive
fn test_hash_common_words<B: BuildHasher>(build_hasher: &B) {
let word_pairs: Vec<_> = gen_word_pairs();
check_for_collisions(build_hasher, &word_pairs, 32);
}
#[allow(unused)] // False positive
fn check_for_collisions<H: Hash, B: BuildHasher>(build_hasher: &B, items: &[H], bucket_count: usize) {
let mut buckets = vec![0; bucket_count];
for item in items {
let value = hash(item, build_hasher) as usize;
buckets[value % bucket_count] += 1;
}
let mean = items.len() / bucket_count;
let max = *buckets.iter().max().unwrap();
let min = *buckets.iter().min().unwrap();
assert!(
(min as f64) > (mean as f64) * 0.95,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
assert!(
(max as f64) < (mean as f64) * 1.05,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
}
#[allow(unused)] // False positive
fn hash<H: Hash, B: BuildHasher>(b: &H, build_hasher: &B) -> u64 {
H::get_hash(b, build_hasher)
}
#[test]
fn test_bucket_distribution() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
test_hash_common_words(&build_hasher);
let sequence: Vec<_> = (0..320000).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000).collect();
check_for_collisions(&build_hasher, &sequence, 256);
let sequence: Vec<_> = (0..320000).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000_u64).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 256);
}
fn ahash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = AHasher::new_with_keys(1234, 5678);
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn fxhash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = FxHasher::default();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn bench_ahash_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("aes_words", |b| b.iter(|| black_box(ahash_vec(&words))));
}
fn bench_fx_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("fx_words", |b| b.iter(|| black_box(fxhash_vec(&words))));
}
criterion_main!(benches);
criterion_group!(benches, bench_ahash_words, bench_fx_words,);
+72
View File
@@ -0,0 +1,72 @@
use ahash::{AHasher, CallHasher, RandomState};
use std::hash::BuildHasher;
#[macro_use]
extern crate no_panic;
#[inline(never)]
#[no_panic]
fn hash_test_final(num: i32, string: &str) -> (u64, u64) {
use core::hash::Hasher;
let mut hasher1 = AHasher::new_with_keys(1, 2);
let mut hasher2 = AHasher::new_with_keys(3, 4);
hasher1.write_i32(num);
hasher2.write(string.as_bytes());
(hasher1.finish(), hasher2.finish())
}
#[inline(never)]
fn hash_test_final_wrapper(num: i32, string: &str) {
hash_test_final(num, string);
}
struct SimpleBuildHasher {
hasher: AHasher,
}
impl BuildHasher for SimpleBuildHasher {
type Hasher = AHasher;
fn build_hasher(&self) -> Self::Hasher {
self.hasher.clone()
}
}
#[inline(never)]
#[no_panic]
fn hash_test_specialize(num: i32, string: &str) -> (u64, u64) {
let hasher1 = AHasher::new_with_keys(1, 2);
let hasher2 = AHasher::new_with_keys(1, 2);
(
i32::get_hash(&num, &SimpleBuildHasher { hasher: hasher1 }),
<[u8]>::get_hash(string.as_bytes(), &SimpleBuildHasher { hasher: hasher2 }),
)
}
#[inline(never)]
fn hash_test_random_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[inline(never)]
#[no_panic]
fn hash_test_random(num: i32, string: &str) -> (u64, u64) {
let build_hasher1 = RandomState::with_seeds(1, 2, 3, 4);
let build_hasher2 = RandomState::with_seeds(1, 2, 3, 4);
(
i32::get_hash(&num, &build_hasher1),
<[u8]>::get_hash(string.as_bytes(), &build_hasher2),
)
}
#[inline(never)]
fn hash_test_specialize_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[test]
fn test_no_panic() {
hash_test_final_wrapper(2, "Foo");
hash_test_specialize_wrapper(2, "Bar");
hash_test_random_wrapper(2, "Baz");
}
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"a9e489a9cb5309b1be3502e513cdf8b6d65d7f4dbd8d1b6273717e5f46d16370",".github/workflows/rust.yml":"0d0bc3c9d0fda45f18bc7dc169d90b78d2757a01f3e789fea5011c4e4d3ebd8f","Cargo.toml":"4ddbacdfae8fa9049a0f47b8f479a5e09536687917415f5c993c9c5438cbdbcd","Cargo.toml.orig":"84591ea3bef6c64be7d7b22aa3d12e3cdd723ec3afb6236c103bb497a8097dde","FAQ.md":"9eb41898523ee209a0a937f9bcb78afe45ad55ca0556f8a4d4063558098f6d1e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0444c6991eead6822f7b9102e654448d51624431119546492e8b231db42c48bb","README.md":"d7f74d616a751bcca23d5d3b58a6daf556356a526c5f0b6aa0504715d176549a","build.rs":"d7dd5428c78b80bb3c99068561641ec661f0f94defbda17f85b443e358ab6396","rustfmt.toml":"e090969e99df9360705680cc0097cfaddae10c22dc2e01470592cf3b9787fd36","src/aes_hash.rs":"c29c24588676f669c9c5d928100d5743325a8a7aa19dae9e899b7108c2bf5309","src/convert.rs":"85789aaa5da2a1d37d91570ba6fa14ef261ff264dac40a624a88178f41c5494b","src/fallback_hash.rs":"6061ff5c42423f9028f487e4b042449424da6a5de5fd57fdb5f2b8063124312d","src/hash_map.rs":"1df153463e3c1f818bebf09938bfe63f18dc34cfeaf3041a67e2a604135e3f84","src/hash_quality_test.rs":"61a5d8a24b1882479ef72590f9165a2cfd5af3a26e1aca3e172061798d93f35e","src/hash_set.rs":"222d4563ea3749bc63ab7ded5c4a4239a0a1515b6a64301c0e04cf095386776d","src/lib.rs":"de037bb4e0535fdf4c432f87f0e0d0e5ba9a4ba25033f97c9a76ded33574aa9e","src/operations.rs":"dae1c602718e29e5839fad52e512a8cf168c1d888cfa9cd074302db7dbabb3d4","src/random_state.rs":"3a28f818165289071339299c49d8deb93fb27bf31ab1416349132d3af5ec2ad4","src/specialize.rs":"f2fe0de5d79851ee8ef6d977f418db8d5df5469eaa18a5173fa16cd61df8312e","tests/bench.rs":"e84993bb03089b11b8df0dccce273eab4d7aab966a71bc609d1e220ca783f023","tests/map_tests.rs":"fd6028cc96e1892b7d27cb5f55bf290b8e331305515598b35a54a3f349c5d7a7","tests/nopanic.rs":"0d28a46248d77283941db1d9fd154c68b965c81a0e3db1fe4a43e06fc448da8f"},"package":"5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "9aa1ba20f05ed582eda04ea625d5658c92195a57"
},
"path_in_vcs": ""
}
+156
View File
@@ -0,0 +1,156 @@
name: Rust
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install latest stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- name: check nostd
run: cargo check --no-default-features
- name: test nostd
run: cargo test --no-default-features
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng
- name: test constrandom
run: cargo test --no-default-features --features compile-time-rng
- name: check fixed-seed
run: cargo check --no-default-features --features std
- name: check
run: cargo check
- name: test
run: cargo test
nightly:
name: nightly
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=native
steps:
- uses: actions/checkout@v4
- name: Install latest nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: clippy
- name: check nightly
run: cargo check -Z msrv-policy
- name: test nightly
run: cargo test
- name: check serde
run: cargo check --features serde
- name: test serde
run: cargo test --features serde
linux_arm7:
name: Linux ARMv7
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: armv7-unknown-linux-gnueabihf
- run: cargo check --target armv7-unknown-linux-gnueabihf
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: armv7-unknown-linux-gnueabihf
- run: cargo +1.72.0 check --target armv7-unknown-linux-gnueabihf
aarch64-apple-darwin:
name: Aarch64 Apple Darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: aarch64-apple-darwin
- run: cargo check --target aarch64-apple-darwin
- run: cargo test
- run: cargo test --no-default-features --features compile-time-rng
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: aarch64-apple-darwin
- run: cargo +1.72.0 check --target aarch64-apple-darwin
i686-unknown-linux-gnu:
name: Linux i686
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: i686-unknown-linux-gnu
- name: Install cross compile tools
run: sudo apt-get install -y gcc-multilib libc6-i386 libc6-dev-i386
- run: cargo check --target i686-unknown-linux-gnu
- run: cargo test --target i686-unknown-linux-gnu
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng --target i686-unknown-linux-gnu
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
targets: i686-unknown-linux-gnu
- run: cargo +1.72.0 check --target i686-unknown-linux-gnu
- name: check constrandom
run: cargo +1.72.0 check --no-default-features --features compile-time-rng --target i686-unknown-linux-gnu
x86_64-unknown-linux-gnu:
name: Linux x86_64
runs-on: ubuntu-latest
env:
RUSTFLAGS: -C target-cpu=skylake -C target-feature=+aes
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
targets: x86_64-unknown-linux-gnu
- run: cargo check --target x86_64-unknown-linux-gnu
- run: cargo test --target x86_64-unknown-linux-gnu
- name: check constrandom
run: cargo check --no-default-features --features compile-time-rng --target x86_64-unknown-linux-gnu
- name: Install 1.72.0
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.72.0
- run: cargo +1.72.0 check --target x86_64-unknown-linux-gnu
- name: check constrandom
run: cargo +1.72.0 check --no-default-features --features compile-time-rng --target x86_64-unknown-linux-gnu
thumbv6m:
name: thumbv6m
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: thumbv6m-none-eabi
- run: cargo check --target thumbv6m-none-eabi --no-default-features
wasm32-unknown-unknown:
name: wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: wasm32-unknown-unknown
- run: cargo check --target wasm32-unknown-unknown --no-default-features
no_std:
name: no-std build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- run: cargo build --manifest-path=no_std_test/Cargo.toml
+183
View File
@@ -0,0 +1,183 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.60.0"
name = "ahash"
version = "0.8.12"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
build = "build.rs"
exclude = [
"/smhasher",
"/benchmark_tools",
]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A non-cryptographic hash function using AES-NI for high performance"
documentation = "https://docs.rs/ahash"
readme = "README.md"
keywords = [
"hash",
"hasher",
"hashmap",
"aes",
"no-std",
]
categories = [
"algorithms",
"data-structures",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/tkaitchuck/ahash"
[package.metadata.docs.rs]
features = ["std"]
rustc-args = [
"-C",
"target-feature=+aes",
]
rustdoc-args = [
"-C",
"target-feature=+aes",
]
[profile.bench]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
debug = 0
debug-assertions = false
[profile.test]
opt-level = 2
lto = "fat"
[lib]
name = "ahash"
path = "src/lib.rs"
test = true
doctest = true
bench = true
doc = true
[[test]]
name = "bench"
path = "tests/bench.rs"
[[test]]
name = "map_tests"
path = "tests/map_tests.rs"
[[test]]
name = "nopanic"
path = "tests/nopanic.rs"
[[bench]]
name = "ahash"
path = "tests/bench.rs"
harness = false
[[bench]]
name = "map"
path = "tests/map_tests.rs"
harness = false
[dependencies.cfg-if]
version = "1.0"
[dependencies.const-random]
version = "0.1.17"
optional = true
[dependencies.getrandom]
version = "0.3.1"
optional = true
[dependencies.portable-atomic]
version = "1.0.0"
optional = true
[dependencies.serde]
version = "1.0.117"
optional = true
[dependencies.zerocopy]
version = "0.8.24"
features = ["simd"]
default-features = false
[dev-dependencies.criterion]
version = "0.3.2"
features = ["html_reports"]
[dev-dependencies.fnv]
version = "1.0.5"
[dev-dependencies.fxhash]
version = "0.2.1"
[dev-dependencies.hashbrown]
version = "0.14.3"
[dev-dependencies.hex]
version = "0.4.2"
[dev-dependencies.no-panic]
version = "0.1.10"
[dev-dependencies.pcg-mwc]
version = "0.2.1"
[dev-dependencies.rand]
version = "0.8.5"
[dev-dependencies.seahash]
version = "4.0"
[dev-dependencies.serde_json]
version = "1.0.59"
[dev-dependencies.smallvec]
version = "1.13.1"
[build-dependencies.version_check]
version = "0.9.4"
[features]
atomic-polyfill = [
"dep:portable-atomic",
"once_cell/critical-section",
]
compile-time-rng = ["const-random"]
default = [
"std",
"runtime-rng",
]
nightly-arm-aes = []
no-rng = []
runtime-rng = ["getrandom"]
std = []
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies.once_cell]
version = "1.18.0"
features = ["alloc"]
default-features = false
+108
View File
@@ -0,0 +1,108 @@
[package]
name = "ahash"
version = "0.8.12"
authors = ["Tom Kaitchuck <Tom.Kaitchuck@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "A non-cryptographic hash function using AES-NI for high performance"
documentation = "https://docs.rs/ahash"
repository = "https://github.com/tkaitchuck/ahash"
keywords = ["hash", "hasher", "hashmap", "aes", "no-std"]
categories = ["algorithms", "data-structures", "no-std"]
edition = "2018"
readme = "README.md"
build = "./build.rs"
exclude = ["/smhasher", "/benchmark_tools"]
rust-version = "1.60.0"
[lib]
name = "ahash"
path = "src/lib.rs"
test = true
doctest = true
bench = true
doc = true
[features]
default = ["std", "runtime-rng"]
# Enabling this will enable `AHashMap` and `AHashSet`.
std = []
# Runtime random key generation using getrandom.
runtime-rng = ["getrandom"]
# This is an alternative to runtime key generation which does compile time key generation if runtime-rng is not available.
# (If runtime-rng is enabled this does nothing.)
# If this is on (and runtime-rng is off) it implies the produced binary will not be identical.
# If this is disabled and runtime-rng is unavailable constant keys are used.
compile-time-rng = ["const-random"]
# Do not use any random number generator (either at compile time or runtime)
# If either runtime-rng or compile-time-rng are enabled this does nothing.
no-rng = []
# in case this is being used on an architecture lacking core::sync::atomic::AtomicUsize and friends
atomic-polyfill = [ "dep:portable-atomic", "once_cell/critical-section"]
# Nightly-only support for AES intrinsics on 32-bit ARM
nightly-arm-aes = []
[[bench]]
name = "ahash"
path = "tests/bench.rs"
harness = false
[[bench]]
name = "map"
path = "tests/map_tests.rs"
harness = false
[profile.test]
opt-level = 2
lto = 'fat'
[profile.release]
opt-level = 3
debug = false
lto = 'fat'
debug-assertions = false
codegen-units = 1
[profile.bench]
opt-level = 3
debug = false
lto = 'fat'
debug-assertions = false
codegen-units = 1
[build-dependencies]
version_check = "0.9.4"
[dependencies]
const-random = { version = "0.1.17", optional = true }
serde = { version = "1.0.117", optional = true }
cfg-if = "1.0"
portable-atomic = { version = "1.0.0", optional = true }
getrandom = { version = "0.3.1", optional = true }
zerocopy = { version = "0.8.24", default-features = false, features = ["simd"] }
[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies]
once_cell = { version = "1.18.0", default-features = false, features = ["alloc"] }
[dev-dependencies]
no-panic = "0.1.10"
criterion = {version = "0.3.2", features = ["html_reports"] }
seahash = "4.0"
fnv = "1.0.5"
fxhash = "0.2.1"
hex = "0.4.2"
rand = "0.8.5"
pcg-mwc = "0.2.1"
serde_json = "1.0.59"
hashbrown = "0.14.3"
smallvec = "1.13.1"
[package.metadata.docs.rs]
rustc-args = ["-C", "target-feature=+aes"]
rustdoc-args = ["-C", "target-feature=+aes"]
features = ["std"]
+118
View File
@@ -0,0 +1,118 @@
## How does aHash prevent DOS attacks
AHash is designed to [prevent an adversary that does not know the key from being able to create hash collisions or partial collisions.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
If you are a cryptographer and would like to help review aHash's algorithm, please post a comment [here](https://github.com/tkaitchuck/aHash/issues/11).
In short, this is achieved by ensuring that:
* aHash is designed to [resist differential crypto analysis](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks#differential-analysis). Meaning it should not be possible to devise a scheme to "cancel" out a modification of the internal state from a block of input via some corresponding change in a subsequent block of input.
* This is achieved by not performing any "premixing" - This reversible mixing gave previous hashes such as murmurhash confidence in their quality, but could be undone by a deliberate attack.
* Before it is used each chunk of input is "masked" such as by xoring it with an unpredictable value.
* aHash obeys the '[strict avalanche criterion](https://en.wikipedia.org/wiki/Avalanche_effect#Strict_avalanche_criterion)':
Each bit of input has the potential to flip every bit of the output.
* Similarly, each bit in the key can affect every bit in the output.
* Input bits never effect just one, or a very few, bits in intermediate state. This is specifically designed to prevent the sort of
[differential attacks launched by the sipHash authors](https://emboss.github.io/blog/2012/12/14/breaking-murmur-hash-flooding-dos-reloaded/) which cancel previous inputs.
* The `finish` call at the end of the hash is designed to not expose individual bits of the internal state.
* For example in the main algorithm 256bits of state and 256bits of keys are reduced to 64 total bits using 3 rounds of AES encryption.
Reversing this is more than non-trivial. Most of the information is by definition gone, and any given bit of the internal state is fully diffused across the output.
* In both aHash and its fallback the internal state is divided into two halves which are updated by two unrelated techniques using the same input. - This means that if there is a way to attack one of them it likely won't be able to attack both of them at the same time.
* It is deliberately difficult to 'chain' collisions. (This has been the major technique used to weaponize attacks on other hash functions)
More details are available on [the wiki](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks).
## Why not use a cryptographic hash in a hashmap.
Cryptographic hashes are designed to make is nearly impossible to find two items that collide when the attacker has full control
over the input. This has several implications:
* They are very difficult to construct, and have to go to a lot of effort to ensure that collisions are not possible.
* They have no notion of a 'key'. Rather, they are fully deterministic and provide exactly one hash for a given input.
For a HashMap the requirements are different.
* Speed is very important, especially for short inputs. Often the key for a HashMap is a single `u32` or similar, and to be effective
the bucket that it should be hashed to needs to be computed in just a few CPU cycles.
* A hashmap does not need to provide a hard and fast guarantee that no two inputs will ever collide. Hence, hashCodes are not 256bits
but are just 64 or 32 bits in length. Often the first thing done with the hashcode is to truncate it further to compute which among a few buckets should be used for a key.
* Here collisions are expected, and a cheap to deal with provided there is no systematic way to generated huge numbers of values that all
go to the same bucket.
* This also means that unlike a cryptographic hash partial collisions matter. It doesn't do a hashmap any good to produce a unique 256bit hash if
the lower 12 bits are all the same. This means that even a provably irreversible hash would not offer protection from a DOS attack in a hashmap
because an attacker can easily just brute force the bottom N bits.
From a cryptography point of view, a hashmap needs something closer to a block cypher.
Where the input can be quickly mixed in a way that cannot be reversed without knowing a key.
## Why isn't aHash cryptographically secure
It is not designed to be.
Attempting to use aHash as a secure hash will likely fail to hold up for several reasons:
1. aHash relies on random keys which are assumed to not be observable by an attacker. For a cryptographic hash all inputs can be seen and controlled by the attacker.
2. aHash has not yet gone through peer review, which is a pre-requisite for security critical algorithms.
3. Because aHash uses reduced rounds of AES as opposed to the standard of 10. Things like the SQUARE attack apply to part of the internal state.
(These are mitigated by other means to prevent producing collections, but would be a problem in other contexts).
4. Like any cypher based hash, it will show certain statistical deviations from truly random output when comparing a (VERY) large number of hashes.
(By definition cyphers have fewer collisions than truly random data.)
There are efforts to build a secure hash function that uses AES-NI for acceleration, but aHash is not one of them.
## How is aHash so fast
AHash uses a number of tricks.
One trick is taking advantage of specialization. If aHash is compiled on nightly it will take
advantage of specialized hash implementations for strings, slices, and primitives.
Another is taking advantage of hardware instructions.
When it is available aHash uses AES rounds using the AES-NI instruction. AES-NI is very fast (on an intel i7-6700 it
is as fast as a 64 bit multiplication.) and handles 16 bytes of input at a time, while being a very strong permutation.
This is obviously much faster than most standard approaches to hashing, and does a better job of scrambling data than most non-secure hashes.
On an intel i7-6700 compiled on nightly Rust with flags `-C opt-level=3 -C target-cpu=native -C codegen-units=1`:
| Input | SipHash 1-3 time | FnvHash time|FxHash time| aHash time| aHash Fallback* |
|----------------|-----------|-----------|-----------|-----------|---------------|
| u8 | 9.3271 ns | 0.808 ns | **0.594 ns** | 0.7704 ns | 0.7664 ns |
| u16 | 9.5139 ns | 0.803 ns | **0.594 ns** | 0.7653 ns | 0.7704 ns |
| u32 | 9.1196 ns | 1.4424 ns | **0.594 ns** | 0.7637 ns | 0.7712 ns |
| u64 | 10.854 ns | 3.0484 ns | **0.628 ns** | 0.7788 ns | 0.7888 ns |
| u128 | 12.465 ns | 7.0728 ns | 0.799 ns | **0.6174 ns** | 0.6250 ns |
| 1 byte string | 11.745 ns | 2.4743 ns | 2.4000 ns | **1.4921 ns** | 1.5861 ns |
| 3 byte string | 12.066 ns | 3.5221 ns | 2.9253 ns | **1.4745 ns** | 1.8518 ns |
| 4 byte string | 11.634 ns | 4.0770 ns | 1.8818 ns | **1.5206 ns** | 1.8924 ns |
| 7 byte string | 14.762 ns | 5.9780 ns | 3.2282 ns | **1.5207 ns** | 1.8933 ns |
| 8 byte string | 13.442 ns | 4.0535 ns | 2.9422 ns | **1.6262 ns** | 1.8929 ns |
| 15 byte string | 16.880 ns | 8.3434 ns | 4.6070 ns | **1.6265 ns** | 1.7965 ns |
| 16 byte string | 15.155 ns | 7.5796 ns | 3.2619 ns | **1.6262 ns** | 1.8011 ns |
| 24 byte string | 16.521 ns | 12.492 ns | 3.5424 ns | **1.6266 ns** | 2.8311 ns |
| 68 byte string | 24.598 ns | 50.715 ns | 5.8312 ns | **4.8282 ns** | 5.4824 ns |
| 132 byte string| 39.224 ns | 119.96 ns | 11.777 ns | **6.5087 ns** | 9.1459 ns |
|1024 byte string| 254.00 ns | 1087.3 ns | 156.41 ns | **25.402 ns** | 54.566 ns |
* Fallback refers to the algorithm aHash would use if AES instructions are unavailable.
For reference a hash that does nothing (not even reads the input data takes) **0.520 ns**. So that represents the fastest
possible time.
As you can see above aHash like `FxHash` provides a large speedup over `SipHash-1-3` which is already nearly twice as fast as `SipHash-2-4`.
Rust's HashMap by default uses `SipHash-1-3` because faster hash functions such as `FxHash` are predictable and vulnerable to denial of
service attacks. While `aHash` has both very strong scrambling and very high performance.
AHash performs well when dealing with large inputs because aHash reads 8 or 16 bytes at a time. (depending on availability of AES-NI)
Because of this, and its optimized logic, `aHash` is able to outperform `FxHash` with strings.
It also provides especially good performance dealing with unaligned input.
(Notice the big performance gaps between 3 vs 4, 7 vs 8 and 15 vs 16 in `FxHash` above)
### Which CPUs can use the hardware acceleration
Hardware AES instructions are built into Intel processors built after 2010 and AMD processors after 2012.
It is also available on [many other CPUs](https://en.wikipedia.org/wiki/AES_instruction_set) should in eventually
be able to get aHash to work. However, only X86 and X86-64 are the only supported architectures at the moment, as currently
they are the only architectures for which Rust provides an intrinsic.
aHash also uses `sse2` and `sse3` instructions. X86 processors that have `aesni` also have these instruction sets.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2018 Tom Kaitchuck
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+109
View File
@@ -0,0 +1,109 @@
# aHash ![Build Status](https://img.shields.io/github/actions/workflow/status/tkaitchuck/aHash/rust.yml?branch=master) ![Licence](https://img.shields.io/crates/l/ahash) ![Downloads](https://img.shields.io/crates/d/ahash)
AHash is the [fastest](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Speed),
[DOS resistant hash](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks) currently available in Rust.
AHash is intended *exclusively* for use in in-memory hashmaps.
AHash's output is of [high quality](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md#Quality) but aHash is **not** a cryptographically secure hash.
## Design
Because AHash is a keyed hash, each map will produce completely different hashes, which cannot be predicted without knowing the keys.
[This prevents DOS attacks where an attacker sends a large number of items whose hashes collide that get used as keys in a hashmap.](https://github.com/tkaitchuck/aHash/wiki/How-aHash-is-resists-DOS-attacks)
This also avoids [accidentally quadratic behavior by reading from one map and writing to another.](https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion)
## Goals and Non-Goals
AHash does *not* have a fixed standard for its output. This allows it to improve over time. For example,
if any faster algorithm is found, aHash will be updated to incorporate the technique.
Similarly, should any flaw in aHash's DOS resistance be found, aHash will be changed to correct the flaw.
Because it does not have a fixed standard, different computers or computers on different versions of the code will observe different hash values.
As such, aHash is not recommended for use other than in-memory maps. Specifically, aHash is not intended for network use or in applications which persist hashed values.
(In these cases `HighwayHash` would be a better choice)
Additionally, aHash is not intended to be cryptographically secure and should not be used as a MAC, or anywhere which requires a cryptographically secure hash.
(In these cases `SHA-3` would be a better choice)
## Usage
AHash is a drop in replacement for the default implementation of the `Hasher` trait. To construct a `HashMap` using aHash
as its hasher do the following:
```rust
use ahash::{AHasher, RandomState};
use std::collections::HashMap;
let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
map.insert(12, 34);
```
For convenience, wrappers called `AHashMap` and `AHashSet` are also provided.
These do the same thing with slightly less typing.
```rust
use ahash::AHashMap;
let mut map: AHashMap<i32, i32> = AHashMap::new();
map.insert(12, 34);
map.insert(56, 78);
```
## Flags
The aHash package has the following flags:
* `std`: This enables features which require the standard library. (On by default) This includes providing the utility classes `AHashMap` and `AHashSet`.
* `serde`: Enables `serde` support for the utility classes `AHashMap` and `AHashSet`.
* `runtime-rng`: To obtain a seed for Hashers will obtain randomness from the operating system. (On by default)
This is done using the [getrandom](https://github.com/rust-random/getrandom) crate.
* `compile-time-rng`: For OS targets without access to a random number generator, `compile-time-rng` provides an alternative.
If `getrandom` is unavailable and `compile-time-rng` is enabled, aHash will generate random numbers at compile time and embed them in the binary.
* `nightly-arm-aes`: To use AES instructions on 32-bit ARM, which requires nightly. This is not needed on AArch64.
This allows for DOS resistance even if there is no random number generator available at runtime (assuming the compiled binary is not public).
This makes the binary non-deterministic. (If non-determinism is a problem see [constrandom's documentation](https://github.com/tkaitchuck/constrandom#deterministic-builds))
If both `runtime-rng` and `compile-time-rng` are enabled the `runtime-rng` will take precedence and `compile-time-rng` will do nothing.
If neither flag is set, seeds can be supplied by the application. [Multiple apis](https://docs.rs/ahash/latest/ahash/random_state/struct.RandomState.html)
are available to do this.
## Comparison with other hashers
A full comparison with other hashing algorithms can be found [here](https://github.com/tkaitchuck/aHash/blob/master/compare/readme.md)
![Hasher performance](https://docs.google.com/spreadsheets/d/e/2PACX-1vSK7Li2nS-Bur9arAYF9IfT37MP-ohAe1v19lZu5fd9MajI1fSveLAQZyEie4Ea9k5-SWHTff7nL2DW/pubchart?oid=1323618938&format=image)
For a more representative performance comparison which includes the overhead of using a HashMap, see [HashBrown's benchmarks](https://github.com/rust-lang/hashbrown#performance)
as HashBrown now uses aHash as its hasher by default.
## Hash quality
AHash passes the full [SMHasher test suite](https://github.com/rurban/smhasher).
The code to reproduce the result, and the full output [are checked into the repo](https://github.com/tkaitchuck/aHash/tree/master/smhasher).
## Additional FAQ
A separate FAQ document is maintained [here](https://github.com/tkaitchuck/aHash/blob/master/FAQ.md).
If you have questions not covered there, open an issue [here](https://github.com/tkaitchuck/aHash/issues).
## License
Licensed under either of:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
+22
View File
@@ -0,0 +1,22 @@
#![deny(warnings)]
use std::env;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-check-cfg=cfg(specialize)");
if let Some(true) = version_check::supports_feature("specialize") {
println!("cargo:rustc-cfg=specialize");
}
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH was not set");
println!("cargo:rustc-check-cfg=cfg(folded_multiply)");
if arch.eq_ignore_ascii_case("x86_64")
|| arch.eq_ignore_ascii_case("aarch64")
|| arch.eq_ignore_ascii_case("mips64")
|| arch.eq_ignore_ascii_case("powerpc64")
|| arch.eq_ignore_ascii_case("riscv64gc")
|| arch.eq_ignore_ascii_case("s390x")
{
println!("cargo:rustc-cfg=folded_multiply");
}
}
+1
View File
@@ -0,0 +1 @@
max_width = 120
+426
View File
@@ -0,0 +1,426 @@
use crate::convert::*;
use crate::operations::*;
use crate::random_state::PI;
use crate::RandomState;
use core::hash::Hasher;
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
enc: u128,
sum: u128,
key: u128,
}
impl AHasher {
/// Creates a new hasher keyed to the provided keys.
///
/// Normally hashers are created via `AHasher::default()` for fixed keys or `RandomState::new()` for randomly
/// generated keys and `RandomState::with_seeds(a,b)` for seeds that are set and can be reused. All of these work at
/// map creation time (and hence don't have any overhead on a per-item bais).
///
/// This method directly creates the hasher instance and performs no transformation on the provided seeds. This may
/// be useful where a HashBuilder is not desired, such as for testing purposes.
///
/// # Example
///
/// ```no_build
/// use std::hash::Hasher;
/// use ahash::AHasher;
///
/// let mut hasher = AHasher::new_with_keys(1234, 5678);
///
/// hasher.write_u32(1989);
/// hasher.write_u8(11);
/// hasher.write_u8(9);
/// hasher.write(b"Huh?");
///
/// println!("Hash is {:x}!", hasher.finish());
/// ```
#[inline]
pub(crate) fn new_with_keys(key1: u128, key2: u128) -> Self {
let pi: [u128; 2] = PI.convert();
let key1 = key1 ^ pi[0];
let key2 = key2 ^ pi[1];
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline]
pub(crate) fn from_random_state(rand_state: &RandomState) -> Self {
let key1 = [rand_state.k0, rand_state.k1].convert();
let key2 = [rand_state.k2, rand_state.k3].convert();
Self {
enc: key1,
sum: key2,
key: key1 ^ key2,
}
}
#[inline(always)]
fn hash_in(&mut self, new_value: u128) {
self.enc = aesdec(self.enc, new_value);
self.sum = shuffle_and_add(self.sum, new_value);
}
#[inline(always)]
fn hash_in_2(&mut self, v1: u128, v2: u128) {
self.enc = aesdec(self.enc, v1);
self.sum = shuffle_and_add(self.sum, v1);
self.enc = aesdec(self.enc, v2);
self.sum = shuffle_and_add(self.sum, v2);
}
#[inline]
#[cfg(specialize)]
fn short_finish(&self) -> u64 {
let combined = aesenc(self.sum, self.enc);
let result: [u64; 2] = aesdec(combined, combined).convert();
result[0]
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.hash_in(i);
}
#[inline]
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16"
))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len();
add_in_length(&mut self.enc, length as u64);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() <= 8 {
let value = read_small(data);
self.hash_in(value.convert());
} else {
if data.len() > 32 {
if data.len() > 64 {
let tail = data.read_last_u128x4();
let mut current: [u128; 4] = [self.key; 4];
current[0] = aesenc(current[0], tail[0]);
current[1] = aesdec(current[1], tail[1]);
current[2] = aesenc(current[2], tail[2]);
current[3] = aesdec(current[3], tail[3]);
let mut sum: [u128; 2] = [self.key, !self.key];
sum[0] = add_by_64s(sum[0].convert(), tail[0].convert()).convert();
sum[1] = add_by_64s(sum[1].convert(), tail[1].convert()).convert();
sum[0] = shuffle_and_add(sum[0], tail[2]);
sum[1] = shuffle_and_add(sum[1], tail[3]);
while data.len() > 64 {
let (blocks, rest) = data.read_u128x4();
current[0] = aesdec(current[0], blocks[0]);
current[1] = aesdec(current[1], blocks[1]);
current[2] = aesdec(current[2], blocks[2]);
current[3] = aesdec(current[3], blocks[3]);
sum[0] = shuffle_and_add(sum[0], blocks[0]);
sum[1] = shuffle_and_add(sum[1], blocks[1]);
sum[0] = shuffle_and_add(sum[0], blocks[2]);
sum[1] = shuffle_and_add(sum[1], blocks[3]);
data = rest;
}
self.hash_in_2(current[0], current[1]);
self.hash_in_2(current[2], current[3]);
self.hash_in_2(sum[0], sum[1]);
} else {
//len 33-64
let (head, _) = data.read_u128x2();
let tail = data.read_last_u128x2();
self.hash_in_2(head[0], head[1]);
self.hash_in_2(tail[0], tail[1]);
}
} else {
if data.len() > 16 {
//len 17-32
self.hash_in_2(data.read_u128().0, data.read_last_u128());
} else {
//len 9-16
let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
self.hash_in(value.convert());
}
}
}
}
#[inline]
fn finish(&self) -> u64 {
let combined = aesenc(self.sum, self.enc);
let result: [u64; 2] = aesdec(aesdec(combined, self.key), combined).convert();
result[0]
}
}
#[cfg(specialize)]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(specialize)]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(specialize)]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for strings
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(specialize)]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
let result: [u64; 2] = self.0.enc.convert();
result[0]
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes);
self.0.enc = aesenc(self.0.sum, self.0.enc);
self.0.enc = aesdec(aesdec(self.0.enc, self.0.key), self.0.enc);
} else {
add_in_length(&mut self.0.enc, bytes.len() as u64);
let value = read_small(bytes).convert();
self.0.sum = shuffle_and_add(self.0.sum, value);
self.0.enc = aesenc(self.0.sum, self.0.enc);
self.0.enc = aesdec(aesdec(self.0.enc, self.0.key), self.0.enc);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convert::Convert;
use crate::operations::aesenc;
use crate::RandomState;
use std::hash::{BuildHasher, Hasher};
#[test]
fn test_sanity() {
let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
hasher.write_u64(0);
let h1 = hasher.finish();
hasher.write(&[1, 0, 0, 0, 0, 0, 0, 0]);
let h2 = hasher.finish();
assert_ne!(h1, h2);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_builder() {
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[cfg(feature = "compile-time-rng")]
#[test]
fn test_default() {
let hasher_a = AHasher::default();
let a_enc: [u64; 2] = hasher_a.enc.convert();
let a_sum: [u64; 2] = hasher_a.sum.convert();
assert_ne!(0, a_enc[0]);
assert_ne!(0, a_enc[1]);
assert_ne!(0, a_sum[0]);
assert_ne!(0, a_sum[1]);
assert_ne!(a_enc[0], a_enc[1]);
assert_ne!(a_sum[0], a_sum[1]);
assert_ne!(a_enc[0], a_sum[0]);
assert_ne!(a_enc[1], a_sum[1]);
let hasher_b = AHasher::default();
let b_enc: [u64; 2] = hasher_b.enc.convert();
let b_sum: [u64; 2] = hasher_b.sum.convert();
assert_eq!(a_enc[0], b_enc[0]);
assert_eq!(a_enc[1], b_enc[1]);
assert_eq!(a_sum[0], b_sum[0]);
assert_eq!(a_sum[1], b_sum[1]);
}
#[test]
fn test_hash() {
let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
result = aesenc(value.convert(), result.convert()).convert();
result = aesenc(result.convert(), result.convert()).convert();
let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
result2 = aesenc(value2.convert(), result2.convert()).convert();
result2 = aesenc(result2.convert(), result.convert()).convert();
let result: [u8; 16] = result.convert();
let result2: [u8; 16] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
}
+151
View File
@@ -0,0 +1,151 @@
pub(crate) trait Convert<To> {
fn convert(self) -> To;
}
macro_rules! convert {
($a:ty, $b:ty) => {
impl Convert<$b> for $a {
#[inline(always)]
fn convert(self) -> $b {
zerocopy::transmute!(self)
}
}
impl Convert<$a> for $b {
#[inline(always)]
fn convert(self) -> $a {
zerocopy::transmute!(self)
}
}
};
}
macro_rules! convert_primitive_bytes {
($a:ty, $b:ty) => {
impl Convert<$b> for $a {
#[inline(always)]
fn convert(self) -> $b {
self.to_ne_bytes()
}
}
impl Convert<$a> for $b {
#[inline(always)]
fn convert(self) -> $a {
<$a>::from_ne_bytes(self)
}
}
};
}
convert!([u128; 4], [u8; 64]);
convert!([u128; 2], [u64; 4]);
convert!([u128; 2], [u8; 32]);
convert!(u128, [u64; 2]);
convert_primitive_bytes!(u128, [u8; 16]);
convert!([u64; 2], [u32; 4]);
#[cfg(test)]
convert!([u64; 2], [u8; 16]);
convert_primitive_bytes!(u64, [u8; 8]);
convert_primitive_bytes!(u32, [u8; 4]);
convert_primitive_bytes!(u16, [u8; 2]);
convert!([[u64; 4]; 2], [u8; 64]);
macro_rules! as_array {
($input:expr, $len:expr) => {{
{
#[inline(always)]
fn as_array<T>(slice: &[T]) -> &[T; $len] {
core::convert::TryFrom::try_from(slice).unwrap()
}
as_array($input)
}
}};
}
pub(crate) trait ReadFromSlice {
fn read_u16(&self) -> (u16, &[u8]);
fn read_u32(&self) -> (u32, &[u8]);
fn read_u64(&self) -> (u64, &[u8]);
fn read_u128(&self) -> (u128, &[u8]);
fn read_u128x2(&self) -> ([u128; 2], &[u8]);
fn read_u128x4(&self) -> ([u128; 4], &[u8]);
fn read_last_u16(&self) -> u16;
fn read_last_u32(&self) -> u32;
fn read_last_u64(&self) -> u64;
fn read_last_u128(&self) -> u128;
fn read_last_u128x2(&self) -> [u128; 2];
fn read_last_u128x4(&self) -> [u128; 4];
}
impl ReadFromSlice for [u8] {
#[inline(always)]
fn read_u16(&self) -> (u16, &[u8]) {
let (value, rest) = self.split_at(2);
(as_array!(value, 2).convert(), rest)
}
#[inline(always)]
fn read_u32(&self) -> (u32, &[u8]) {
let (value, rest) = self.split_at(4);
(as_array!(value, 4).convert(), rest)
}
#[inline(always)]
fn read_u64(&self) -> (u64, &[u8]) {
let (value, rest) = self.split_at(8);
(as_array!(value, 8).convert(), rest)
}
#[inline(always)]
fn read_u128(&self) -> (u128, &[u8]) {
let (value, rest) = self.split_at(16);
(as_array!(value, 16).convert(), rest)
}
#[inline(always)]
fn read_u128x2(&self) -> ([u128; 2], &[u8]) {
let (value, rest) = self.split_at(32);
(as_array!(value, 32).convert(), rest)
}
#[inline(always)]
fn read_u128x4(&self) -> ([u128; 4], &[u8]) {
let (value, rest) = self.split_at(64);
(as_array!(value, 64).convert(), rest)
}
#[inline(always)]
fn read_last_u16(&self) -> u16 {
let (_, value) = self.split_at(self.len() - 2);
as_array!(value, 2).convert()
}
#[inline(always)]
fn read_last_u32(&self) -> u32 {
let (_, value) = self.split_at(self.len() - 4);
as_array!(value, 4).convert()
}
#[inline(always)]
fn read_last_u64(&self) -> u64 {
let (_, value) = self.split_at(self.len() - 8);
as_array!(value, 8).convert()
}
#[inline(always)]
fn read_last_u128(&self) -> u128 {
let (_, value) = self.split_at(self.len() - 16);
as_array!(value, 16).convert()
}
#[inline(always)]
fn read_last_u128x2(&self) -> [u128; 2] {
let (_, value) = self.split_at(self.len() - 32);
as_array!(value, 32).convert()
}
#[inline(always)]
fn read_last_u128x4(&self) -> [u128; 4] {
let (_, value) = self.split_at(self.len() - 64);
as_array!(value, 64).convert()
}
}
+360
View File
@@ -0,0 +1,360 @@
use crate::convert::*;
use crate::operations::folded_multiply;
use crate::operations::read_small;
use crate::operations::MULTIPLE;
use crate::random_state::PI;
use crate::RandomState;
use core::hash::Hasher;
const ROT: u32 = 23; //17
/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
buffer: u64,
pad: u64,
extra_keys: [u64; 2],
}
impl AHasher {
/// Creates a new hasher keyed to the provided key.
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn new_with_keys(key1: u128, key2: u128) -> AHasher {
let pi: [u128; 2] = PI.convert();
let key1: [u64; 2] = (key1 ^ pi[0]).convert();
let key2: [u64; 2] = (key2 ^ pi[1]).convert();
AHasher {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[allow(unused)] // False positive
pub(crate) fn test_with_keys(key1: u128, key2: u128) -> Self {
let key1: [u64; 2] = key1.convert();
let key2: [u64; 2] = key2.convert();
Self {
buffer: key1[0],
pad: key1[1],
extra_keys: key2,
}
}
#[inline]
#[allow(dead_code)] // Is not called if non-fallback hash is used.
pub(crate) fn from_random_state(rand_state: &RandomState) -> AHasher {
AHasher {
buffer: rand_state.k1,
pad: rand_state.k0,
extra_keys: [rand_state.k2, rand_state.k3],
}
}
/// This update function has the goal of updating the buffer with a single multiply
/// FxHash does this but is vulnerable to attack. To avoid this input needs to be masked to with an
/// unpredictable value. Other hashes such as murmurhash have taken this approach but were found vulnerable
/// to attack. The attack was based on the idea of reversing the pre-mixing (Which is necessarily
/// reversible otherwise bits would be lost) then placing a difference in the highest bit before the
/// multiply used to mix the data. Because a multiply can never affect the bits to the right of it, a
/// subsequent update that also differed in this bit could result in a predictable collision.
///
/// This version avoids this vulnerability while still only using a single multiply. It takes advantage
/// of the fact that when a 64 bit multiply is performed the upper 64 bits are usually computed and thrown
/// away. Instead it creates two 128 bit values where the upper 64 bits are zeros and multiplies them.
/// (The compiler is smart enough to turn this into a 64 bit multiplication in the assembly)
/// Then the upper bits are xored with the lower bits to produce a single 64 bit result.
///
/// To understand why this is a good scrambling function it helps to understand multiply-with-carry PRNGs:
/// https://en.wikipedia.org/wiki/Multiply-with-carry_pseudorandom_number_generator
/// If the multiple is chosen well, this creates a long period, decent quality PRNG.
/// Notice that this function is equivalent to this except the `buffer`/`state` is being xored with each
/// new block of data. In the event that data is all zeros, it is exactly equivalent to a MWC PRNG.
///
/// This is impervious to attack because every bit buffer at the end is dependent on every bit in
/// `new_data ^ buffer`. For example suppose two inputs differed in only the 5th bit. Then when the
/// multiplication is performed the `result` will differ in bits 5-69. More specifically it will differ by
/// 2^5 * MULTIPLE. However in the next step bits 65-128 are turned into a separate 64 bit value. So the
/// differing bits will be in the lower 6 bits of this value. The two intermediate values that differ in
/// bits 5-63 and in bits 0-5 respectively get added together. Producing an output that differs in every
/// bit. The addition carries in the multiplication and at the end additionally mean that the even if an
/// attacker somehow knew part of (but not all) the contents of the buffer before hand,
/// they would not be able to predict any of the bits in the buffer at the end.
#[inline(always)]
fn update(&mut self, new_data: u64) {
self.buffer = folded_multiply(new_data ^ self.buffer, MULTIPLE);
}
/// Similar to the above this function performs an update using a "folded multiply".
/// However it takes in 128 bits of data instead of 64. Both halves must be masked.
///
/// This makes it impossible for an attacker to place a single bit difference between
/// two blocks so as to cancel each other.
///
/// However this is not sufficient. to prevent (a,b) from hashing the same as (b,a) the buffer itself must
/// be updated between calls in a way that does not commute. To achieve this XOR and Rotate are used.
/// Add followed by xor is not the same as xor followed by add, and rotate ensures that the same out bits
/// can't be changed by the same set of input bits. To cancel this sequence with subsequent input would require
/// knowing the keys.
#[inline(always)]
fn large_update(&mut self, new_data: u128) {
let block: [u64; 2] = new_data.convert();
let combined = folded_multiply(block[0] ^ self.extra_keys[0], block[1] ^ self.extra_keys[1]);
self.buffer = (self.buffer.wrapping_add(self.pad) ^ combined).rotate_left(ROT);
}
#[inline]
#[cfg(specialize)]
fn short_finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
}
}
/// Provides [Hasher] methods to hash all of the primitive types.
///
/// [Hasher]: core::hash::Hasher
impl Hasher for AHasher {
#[inline]
fn write_u8(&mut self, i: u8) {
self.update(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.update(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.update(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.update(i as u64);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.large_update(i);
}
#[inline]
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16"
))]
fn write_usize(&mut self, i: usize) {
self.write_u64(i as u64);
}
#[inline]
#[cfg(target_pointer_width = "128")]
fn write_usize(&mut self, i: usize) {
self.write_u128(i as u128);
}
#[inline]
#[allow(clippy::collapsible_if)]
fn write(&mut self, input: &[u8]) {
let mut data = input;
let length = data.len() as u64;
//Needs to be an add rather than an xor because otherwise it could be canceled with carefully formed input.
self.buffer = self.buffer.wrapping_add(length).wrapping_mul(MULTIPLE);
//A 'binary search' on sizes reduces the number of comparisons.
if data.len() > 8 {
if data.len() > 16 {
let tail = data.read_last_u128();
self.large_update(tail);
while data.len() > 16 {
let (block, rest) = data.read_u128();
self.large_update(block);
data = rest;
}
} else {
self.large_update([data.read_u64().0, data.read_last_u64()].convert());
}
} else {
let value = read_small(data);
self.large_update(value.convert());
}
}
#[inline]
fn finish(&self) -> u64 {
let rot = (self.buffer & 63) as u32;
folded_multiply(self.buffer, self.pad).rotate_left(rot)
}
}
#[cfg(specialize)]
pub(crate) struct AHasherU64 {
pub(crate) buffer: u64,
pub(crate) pad: u64,
}
/// A specialized hasher for only primitives under 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherU64 {
#[inline]
fn finish(&self) -> u64 {
folded_multiply(self.buffer, self.pad)
//self.buffer
}
#[inline]
fn write(&mut self, _bytes: &[u8]) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.buffer = folded_multiply(i ^ self.buffer, MULTIPLE);
}
#[inline]
fn write_u128(&mut self, _i: u128) {
unreachable!("Specialized hasher was called with a different type of object")
}
#[inline]
fn write_usize(&mut self, _i: usize) {
unreachable!("Specialized hasher was called with a different type of object")
}
}
#[cfg(specialize)]
pub(crate) struct AHasherFixed(pub AHasher);
/// A specialized hasher for fixed size primitives larger than 64 bits.
#[cfg(specialize)]
impl Hasher for AHasherFixed {
#[inline]
fn finish(&self) -> u64 {
self.0.short_finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
self.0.write(bytes)
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.write_u64(i as u64);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.write_u64(i as u64);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.write_u64(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.0.write_u64(i);
}
#[inline]
fn write_u128(&mut self, i: u128) {
self.0.write_u128(i);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.0.write_usize(i);
}
}
#[cfg(specialize)]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for a single string
/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec)
#[cfg(specialize)]
impl Hasher for AHasherStr {
#[inline]
fn finish(&self) -> u64 {
self.0.finish()
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
if bytes.len() > 8 {
self.0.write(bytes)
} else {
let value = read_small(bytes);
self.0.buffer = folded_multiply(value[0] ^ self.0.buffer, value[1] ^ self.0.extra_keys[1]);
self.0.pad = self.0.pad.wrapping_add(bytes.len() as u64);
}
}
#[inline]
fn write_u8(&mut self, _i: u8) {}
#[inline]
fn write_u16(&mut self, _i: u16) {}
#[inline]
fn write_u32(&mut self, _i: u32) {}
#[inline]
fn write_u64(&mut self, _i: u64) {}
#[inline]
fn write_u128(&mut self, _i: u128) {}
#[inline]
fn write_usize(&mut self, _i: usize) {}
}
#[cfg(test)]
mod tests {
use crate::fallback_hash::*;
#[test]
fn test_hash() {
let mut hasher = AHasher::new_with_keys(0, 0);
let value: u64 = 1 << 32;
hasher.update(value);
let result = hasher.buffer;
let mut hasher = AHasher::new_with_keys(0, 0);
let value2: u64 = 1;
hasher.update(value2);
let result2 = hasher.buffer;
let result: [u8; 8] = result.convert();
let result2: [u8; 8] = result2.convert();
assert_ne!(hex::encode(result), hex::encode(result2));
}
}
+501
View File
@@ -0,0 +1,501 @@
use std::borrow::Borrow;
use std::collections::hash_map::{IntoKeys, IntoValues};
use std::collections::{hash_map, HashMap};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut, Index};
use std::panic::UnwindSafe;
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use crate::RandomState;
/// A [`HashMap`](std::collections::HashMap) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashMap<K, V, S = crate::RandomState>(HashMap<K, V, S>);
impl<K, V> From<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn from(item: HashMap<K, V, crate::RandomState>) -> Self {
AHashMap(item)
}
}
impl<K, V, const N: usize> From<[(K, V); N]> for AHashMap<K, V>
where
K: Eq + Hash,
{
/// # Examples
///
/// ```
/// use ahash::AHashMap;
///
/// let map1 = AHashMap::from([(1, 2), (3, 4)]);
/// let map2: AHashMap<_, _> = [(1, 2), (3, 4)].into();
/// assert_eq!(map1, map2);
/// ```
fn from(arr: [(K, V); N]) -> Self {
Self::from_iter(arr)
}
}
impl<K, V> Into<HashMap<K, V, crate::RandomState>> for AHashMap<K, V> {
fn into(self) -> HashMap<K, V, crate::RandomState> {
self.0
}
}
impl<K, V> AHashMap<K, V, RandomState> {
/// This creates a hashmap using [RandomState::new] which obtains its keys from [RandomSource].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn new() -> Self {
AHashMap(HashMap::with_hasher(RandomState::new()))
}
/// This creates a hashmap with the specified capacity using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn with_capacity(capacity: usize) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, RandomState::new()))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashMap(HashMap::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashMap(HashMap::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<K, V, S> AHashMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher,
{
/// Returns a reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get(&1), Some(&"a"));
/// assert_eq!(map.get(&2), None);
/// ```
#[inline]
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get(k)
}
/// Returns the key-value pair corresponding to the supplied key.
///
/// The supplied key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.get_key_value(&1), Some((&1, &"a")));
/// assert_eq!(map.get_key_value(&2), None);
/// ```
#[inline]
pub fn get_key_value<Q: ?Sized>(&self, k: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_key_value(k)
}
/// Returns a mutable reference to the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// if let Some(x) = map.get_mut(&1) {
/// *x = "b";
/// }
/// assert_eq!(map[&1], "b");
/// ```
#[inline]
pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.get_mut(k)
}
/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, [`None`] is returned.
///
/// If the map did have this key present, the value is updated, and the old
/// value is returned. The key is not updated, though; this matters for
/// types that can be `==` without being identical. See the [module-level
/// documentation] for more.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// assert_eq!(map.insert(37, "a"), None);
/// assert_eq!(map.is_empty(), false);
///
/// map.insert(37, "b");
/// assert_eq!(map.insert(37, "c"), Some("b"));
/// assert_eq!(map[&37], "c");
/// ```
#[inline]
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
self.0.insert(k, v)
}
/// Creates a consuming iterator visiting all the keys in arbitrary order.
/// The map cannot be used after calling this.
/// The iterator element type is `K`.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let map = HashMap::from([
/// ("a", 1),
/// ("b", 2),
/// ("c", 3),
/// ]);
///
/// let mut vec: Vec<&str> = map.into_keys().collect();
/// // The `IntoKeys` iterator produces keys in arbitrary order, so the
/// // keys must be sorted to test them against a sorted array.
/// vec.sort_unstable();
/// assert_eq!(vec, ["a", "b", "c"]);
/// ```
///
/// # Performance
///
/// In the current implementation, iterating over keys takes O(capacity) time
/// instead of O(len) because it internally visits empty buckets too.
#[inline]
pub fn into_keys(self) -> IntoKeys<K, V> {
self.0.into_keys()
}
/// Creates a consuming iterator visiting all the values in arbitrary order.
/// The map cannot be used after calling this.
/// The iterator element type is `V`.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let map = HashMap::from([
/// ("a", 1),
/// ("b", 2),
/// ("c", 3),
/// ]);
///
/// let mut vec: Vec<i32> = map.into_values().collect();
/// // The `IntoValues` iterator produces values in arbitrary order, so
/// // the values must be sorted to test them against a sorted array.
/// vec.sort_unstable();
/// assert_eq!(vec, [1, 2, 3]);
/// ```
///
/// # Performance
///
/// In the current implementation, iterating over values takes O(capacity) time
/// instead of O(len) because it internally visits empty buckets too.
#[inline]
pub fn into_values(self) -> IntoValues<K, V> {
self.0.into_values()
}
/// Removes a key from the map, returning the value at the key if the key
/// was previously in the map.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
/// the key type.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
///
/// let mut map = HashMap::new();
/// map.insert(1, "a");
/// assert_eq!(map.remove(&1), Some("a"));
/// assert_eq!(map.remove(&1), None);
/// ```
#[inline]
pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.0.remove(k)
}
}
impl<K, V, S> Deref for AHashMap<K, V, S> {
type Target = HashMap<K, V, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V, S> DerefMut for AHashMap<K, V, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<K, V, S> UnwindSafe for AHashMap<K, V, S>
where
K: UnwindSafe,
V: UnwindSafe,
{
}
impl<K, V, S> PartialEq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: PartialEq,
S: BuildHasher,
{
fn eq(&self, other: &AHashMap<K, V, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<K, V, S> Eq for AHashMap<K, V, S>
where
K: Eq + Hash,
V: Eq,
S: BuildHasher,
{
}
impl<K, Q: ?Sized, V, S> Index<&Q> for AHashMap<K, V, S>
where
K: Eq + Hash + Borrow<Q>,
Q: Eq + Hash,
S: BuildHasher,
{
type Output = V;
/// Returns a reference to the value corresponding to the supplied key.
///
/// # Panics
///
/// Panics if the key is not present in the `HashMap`.
#[inline]
fn index(&self, key: &Q) -> &V {
self.0.index(key)
}
}
impl<K, V, S> Debug for AHashMap<K, V, S>
where
K: Debug,
V: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<K, V> FromIterator<(K, V)> for AHashMap<K, V, RandomState>
where
K: Eq + Hash,
{
/// This creates a hashmap from the provided iterator using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut inner = HashMap::with_hasher(RandomState::new());
inner.extend(iter);
AHashMap(inner)
}
}
impl<'a, K, V, S> IntoIterator for &'a AHashMap<K, V, S> {
type Item = (&'a K, &'a V);
type IntoIter = hash_map::Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<'a, K, V, S> IntoIterator for &'a mut AHashMap<K, V, S> {
type Item = (&'a K, &'a mut V);
type IntoIter = hash_map::IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
(&mut self.0).iter_mut()
}
}
impl<K, V, S> IntoIterator for AHashMap<K, V, S> {
type Item = (K, V);
type IntoIter = hash_map::IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<K, V, S> Extend<(K, V)> for AHashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
impl<'a, K, V, S> Extend<(&'a K, &'a V)> for AHashMap<K, V, S>
where
K: Eq + Hash + Copy + 'a,
V: Copy + 'a,
S: BuildHasher,
{
#[inline]
fn extend<T: IntoIterator<Item = (&'a K, &'a V)>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
/// NOTE: For safety this trait impl is only available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl<K, V> Default for AHashMap<K, V, RandomState> {
#[inline]
fn default() -> AHashMap<K, V, RandomState> {
AHashMap(HashMap::default())
}
}
#[cfg(feature = "serde")]
impl<K, V> Serialize for AHashMap<K, V>
where
K: Serialize + Eq + Hash,
V: Serialize,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, K, V> Deserialize<'de> for AHashMap<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_map = HashMap::deserialize(deserializer);
hash_map.map(|hash_map| Self(hash_map))
}
fn deserialize_in_place<D: Deserializer<'de>>(deserializer: D, place: &mut Self) -> Result<(), D::Error> {
use serde::de::{MapAccess, Visitor};
struct MapInPlaceVisitor<'a, K: 'a, V: 'a>(&'a mut AHashMap<K, V>);
impl<'a, 'de, K, V> Visitor<'de> for MapInPlaceVisitor<'a, K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
self.0.clear();
self.0.reserve(map.size_hint().unwrap_or(0).min(4096));
while let Some((key, value)) = map.next_entry()? {
self.0.insert(key, value);
}
Ok(())
}
}
deserializer.deserialize_map(MapInPlaceVisitor(place))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_borrow() {
let mut map: AHashMap<String, String> = AHashMap::new();
map.insert("foo".to_string(), "Bar".to_string());
map.insert("Bar".to_string(), map.get("foo").unwrap().to_owned());
}
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
let mut map = AHashMap::new();
map.insert("for".to_string(), 0);
map.insert("bar".to_string(), 1);
let mut serialization = serde_json::to_string(&map).unwrap();
let mut deserialization: AHashMap<String, u64> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, map);
map.insert("baz".to_string(), 2);
serialization = serde_json::to_string(&map).unwrap();
let mut deserializer = serde_json::Deserializer::from_str(&serialization);
AHashMap::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap();
assert_eq!(deserialization, map);
}
}
+534
View File
@@ -0,0 +1,534 @@
use core::hash::{Hash, Hasher};
use std::collections::HashMap;
fn assert_sufficiently_different(a: u64, b: u64, tolerance: i32) {
let (same_byte_count, same_nibble_count) = count_same_bytes_and_nibbles(a, b);
assert!(same_byte_count <= tolerance, "{:x} vs {:x}: {:}", a, b, same_byte_count);
assert!(
same_nibble_count <= tolerance * 3,
"{:x} vs {:x}: {:}",
a,
b,
same_nibble_count
);
let flipped_bits = (a ^ b).count_ones();
assert!(
flipped_bits > 12 && flipped_bits < 52,
"{:x} and {:x}: {:}",
a,
b,
flipped_bits
);
for rotate in 0..64 {
let flipped_bits2 = (a ^ (b.rotate_left(rotate))).count_ones();
assert!(
flipped_bits2 > 10 && flipped_bits2 < 54,
"{:x} and {:x}: {:}",
a,
b.rotate_left(rotate),
flipped_bits2
);
}
}
fn count_same_bytes_and_nibbles(a: u64, b: u64) -> (i32, i32) {
let mut same_byte_count = 0;
let mut same_nibble_count = 0;
for byte in 0..8 {
let ba = (a >> (8 * byte)) as u8;
let bb = (b >> (8 * byte)) as u8;
if ba == bb {
same_byte_count += 1;
}
if ba & 0xF0u8 == bb & 0xF0u8 {
same_nibble_count += 1;
}
if ba & 0x0Fu8 == bb & 0x0Fu8 {
same_nibble_count += 1;
}
}
(same_byte_count, same_nibble_count)
}
fn gen_combinations(options: &[u32; 11], depth: u32, so_far: Vec<u32>, combinations: &mut Vec<Vec<u32>>) {
if depth == 0 {
return;
}
for option in options {
let mut next = so_far.clone();
next.push(*option);
combinations.push(next.clone());
gen_combinations(options, depth - 1, next, combinations);
}
}
fn test_no_full_collisions<T: Hasher>(gen_hash: impl Fn() -> T) {
let options: [u32; 11] = [
0x00000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0xF0000000, 1, 2, 4, 8, 15,
];
let mut combinations = Vec::new();
gen_combinations(&options, 7, Vec::new(), &mut combinations);
let mut map: HashMap<u64, Vec<u8>> = HashMap::new();
for combination in combinations {
use zerocopy::IntoBytes;
let array = combination.as_bytes().to_vec();
let mut hasher = gen_hash();
hasher.write(&array);
let hash = hasher.finish();
if let Some(value) = map.get(&hash) {
assert_eq!(
value, &array,
"Found a collision between {:x?} and {:x?}. Hash: {:x?}",
value, &array, &hash
);
} else {
map.insert(hash, array);
}
}
assert_eq!(21435887, map.len()); //11^7 + 11^6 ...
}
fn test_keys_change_output<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut a = constructor(1, 1);
let mut b = constructor(1, 2);
let mut c = constructor(2, 1);
let mut d = constructor(2, 2);
"test".hash(&mut a);
"test".hash(&mut b);
"test".hash(&mut c);
"test".hash(&mut d);
assert_sufficiently_different(a.finish(), b.finish(), 1);
assert_sufficiently_different(a.finish(), c.finish(), 1);
assert_sufficiently_different(a.finish(), d.finish(), 1);
assert_sufficiently_different(b.finish(), c.finish(), 1);
assert_sufficiently_different(b.finish(), d.finish(), 1);
assert_sufficiently_different(c.finish(), d.finish(), 1);
}
fn test_input_affect_every_byte<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&0, constructor(0, 0));
for shift in 0..16 {
let mut alternatives = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher = constructor(0, 0);
alternatives.push(hash_with(&input, hasher));
}
assert_each_byte_differs(shift, base, alternatives);
}
}
///Ensures that for every bit in the output there is some value for each byte in the key that flips it.
fn test_keys_affect_every_byte<H: Hash, T: Hasher>(item: H, constructor: impl Fn(u128, u128) -> T) {
let base = hash_with(&item, constructor(0, 0));
for shift in 0..16 {
let mut alternatives1 = vec![];
let mut alternatives2 = vec![];
for v in 0..256 {
let input = (v as u128) << (shift * 8);
let hasher1 = constructor(input, 0);
let hasher2 = constructor(0, input);
let h1 = hash_with(&item, hasher1);
let h2 = hash_with(&item, hasher2);
alternatives1.push(h1);
alternatives2.push(h2);
}
assert_each_byte_differs(shift, base, alternatives1);
assert_each_byte_differs(shift, base, alternatives2);
}
}
fn assert_each_byte_differs(num: u64, base: u64, alternatives: Vec<u64>) {
let mut changed_bits = 0_u64;
for alternative in alternatives {
changed_bits |= base ^ alternative
}
assert_eq!(
core::u64::MAX,
changed_bits,
"Bits changed: {:x} on num: {:?}. base {:x}",
changed_bits,
num,
base
);
}
fn test_finish_is_consistent<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
let mut hasher = constructor(1, 2);
"Foo".hash(&mut hasher);
let a = hasher.finish();
let b = hasher.finish();
assert_eq!(a, b);
}
fn test_single_key_bit_flip<T: Hasher>(constructor: impl Fn(u128, u128) -> T) {
for bit in 0..128 {
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234".hash(&mut a);
"1234".hash(&mut b);
"1234".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"12345678".hash(&mut a);
"12345678".hash(&mut b);
"12345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
let mut a = constructor(0, 0);
let mut b = constructor(0, 1 << bit);
let mut c = constructor(1 << bit, 0);
"1234567812345678".hash(&mut a);
"1234567812345678".hash(&mut b);
"1234567812345678".hash(&mut c);
assert_sufficiently_different(a.finish(), b.finish(), 2);
assert_sufficiently_different(a.finish(), c.finish(), 2);
assert_sufficiently_different(b.finish(), c.finish(), 2);
}
}
fn test_all_bytes_matter<T: Hasher>(hasher: impl Fn() -> T) {
let mut item = vec![0; 256];
let base_hash = hash(&item, &hasher);
for pos in 0..256 {
item[pos] = 255;
let hash = hash(&item, &hasher);
assert_ne!(base_hash, hash, "Position {} did not affect output", pos);
item[pos] = 0;
}
}
fn test_no_pair_collisions<T: Hasher>(hasher: impl Fn() -> T) {
let base = [0_u64, 0_u64];
let base_hash = hash(&base, &hasher);
for bitpos1 in 0..64 {
let a = 1_u64 << bitpos1;
for bitpos2 in 0..bitpos1 {
let b = 1_u64 << bitpos2;
let aa = hash(&[a, a], &hasher);
let ab = hash(&[a, b], &hasher);
let ba = hash(&[b, a], &hasher);
let bb = hash(&[b, b], &hasher);
assert_sufficiently_different(base_hash, aa, 3);
assert_sufficiently_different(base_hash, ab, 3);
assert_sufficiently_different(base_hash, ba, 3);
assert_sufficiently_different(base_hash, bb, 3);
assert_sufficiently_different(aa, ab, 3);
assert_sufficiently_different(ab, ba, 3);
assert_sufficiently_different(ba, bb, 3);
assert_sufficiently_different(aa, ba, 3);
assert_sufficiently_different(ab, bb, 3);
assert_sufficiently_different(aa, bb, 3);
}
}
}
fn hash<H: Hash, T: Hasher>(b: &H, hash_builder: &dyn Fn() -> T) -> u64 {
let mut hasher = hash_builder();
b.hash(&mut hasher);
hasher.finish()
}
fn hash_with<H: Hash, T: Hasher>(b: &H, mut hasher: T) -> u64 {
b.hash(&mut hasher);
hasher.finish()
}
fn test_single_bit_flip<T: Hasher>(hasher: impl Fn() -> T) {
let size = 32;
let compare_value = hash(&0u32, &hasher);
for pos in 0..size {
let test_value = hash(&(1u32 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 64;
let compare_value = hash(&0u64, &hasher);
for pos in 0..size {
let test_value = hash(&(1u64 << pos), &hasher);
assert_sufficiently_different(compare_value, test_value, 2);
}
let size = 128;
let compare_value = hash(&0u128, &hasher);
for pos in 0..size {
let test_value = hash(&(1u128 << pos), &hasher);
dbg!(compare_value, test_value);
assert_sufficiently_different(compare_value, test_value, 2);
}
}
fn test_padding_doesnot_collide<T: Hasher>(hasher: impl Fn() -> T) {
for c in 0..128u8 {
for string in ["", "\0", "\x01", "1234", "12345678", "1234567812345678"].iter() {
let mut short = hasher();
string.hash(&mut short);
let value = short.finish();
let mut padded = string.to_string();
for num in 1..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"{} bytes of {} -> {:x} vs {:x}",
num,
c,
value,
long.finish()
);
assert!(
same_nibbles <= 8,
"{} bytes of {} -> {:x} vs {:x}",
num,
c,
value,
long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
if string.len() > 0 {
let mut padded = string[1..].to_string();
padded.push(c as char);
for num in 2..=128 {
let mut long = hasher();
padded.push(c as char);
padded.hash(&mut long);
let (same_bytes, same_nibbles) = count_same_bytes_and_nibbles(value, long.finish());
assert!(
same_bytes <= 3,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
assert!(
same_nibbles <= 8,
"string {:?} + {} bytes of {} -> {:x} vs {:x}",
string,
num,
c,
value,
long.finish()
);
let flipped_bits = (value ^ long.finish()).count_ones();
assert!(flipped_bits > 10);
}
}
}
}
}
fn test_length_extension<T: Hasher>(hasher: impl Fn(u128, u128) -> T) {
for key in 0..256 {
let h1 = hasher(key, key);
let v1 = hash_with(&[0_u8, 0, 0, 0, 0, 0, 0, 0], h1);
let h2 = hasher(key, key);
let v2 = hash_with(&[1_u8, 0, 0, 0, 0, 0, 0, 0, 0], h2);
assert_ne!(v1, v2);
}
}
fn test_sparse<T: Hasher>(hasher: impl Fn() -> T) {
use smallvec::SmallVec;
let mut buf = [0u8; 256];
let mut hashes = HashMap::new();
for idx_1 in 0..255_u8 {
for idx_2 in idx_1 + 1..=255_u8 {
for value_1 in [1, 2, 4, 8, 16, 32, 64, 128] {
for value_2 in [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17, 18, 20, 24, 31, 32, 33, 48, 64, 96, 127, 128, 129,
192, 254, 255,
] {
buf[idx_1 as usize] = value_1;
buf[idx_2 as usize] = value_2;
let hash_value = hash_with(&buf, &mut hasher());
let keys = hashes.entry(hash_value).or_insert(SmallVec::<[[u8; 4]; 1]>::new());
keys.push([idx_1, value_1, idx_2, value_2]);
buf[idx_1 as usize] = 0;
buf[idx_2 as usize] = 0;
}
}
}
}
hashes.retain(|_key, value| value.len() != 1);
assert_eq!(0, hashes.len(), "Collision with: {:?}", hashes);
}
#[cfg(test)]
mod fallback_tests {
use crate::fallback_hash::*;
use crate::hash_quality_test::*;
#[test]
fn fallback_single_bit_flip() {
test_single_bit_flip(|| AHasher::new_with_keys(0, 0))
}
#[test]
fn fallback_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::new_with_keys)
}
#[test]
fn fallback_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::new_with_keys(0, 0));
}
#[test]
fn fallback_keys_change_output() {
test_keys_change_output(AHasher::new_with_keys);
}
#[test]
fn fallback_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::new_with_keys);
}
#[test]
fn fallback_keys_affect_every_byte() {
//For fallback second key is not used in every hash.
#[cfg(all(not(specialize), folded_multiply))]
test_keys_affect_every_byte(0, |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte("", |a, b| AHasher::new_with_keys(a ^ b, a));
test_keys_affect_every_byte((0, 0), |a, b| AHasher::new_with_keys(a ^ b, a));
}
#[test]
fn fallback_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn fallback_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(0, 2));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 0));
test_padding_doesnot_collide(|| AHasher::new_with_keys(2, 2));
}
#[test]
fn fallback_length_extension() {
test_length_extension(|a, b| AHasher::new_with_keys(a, b));
}
#[test]
fn test_no_sparse_collisions() {
test_sparse(|| AHasher::new_with_keys(0, 0));
test_sparse(|| AHasher::new_with_keys(1, 2));
}
}
///Basic sanity tests of the cypto properties of aHash.
#[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[cfg(test)]
mod aes_tests {
use crate::aes_hash::*;
use crate::hash_quality_test::*;
use std::hash::{Hash, Hasher};
//This encrypts to 0.
const BAD_KEY2: u128 = 0x6363_6363_6363_6363_6363_6363_6363_6363;
//This decrypts to 0.
const BAD_KEY: u128 = 0x5252_5252_5252_5252_5252_5252_5252_5252;
#[test]
fn test_single_bit_in_byte() {
let mut hasher1 = AHasher::test_with_keys(0, 0);
8_u32.hash(&mut hasher1);
let mut hasher2 = AHasher::test_with_keys(0, 0);
0_u32.hash(&mut hasher2);
assert_sufficiently_different(hasher1.finish(), hasher2.finish(), 1);
}
#[test]
fn aes_single_bit_flip() {
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_single_bit_flip(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_single_key_bit_flip() {
test_single_key_bit_flip(AHasher::test_with_keys)
}
#[test]
fn aes_all_bytes_matter() {
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_all_bytes_matter(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_test_no_pair_collisions() {
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_no_pair_collisions(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn ase_test_no_full_collisions() {
test_no_full_collisions(|| AHasher::test_with_keys(12345, 67890));
}
#[test]
fn aes_keys_change_output() {
test_keys_change_output(AHasher::test_with_keys);
}
#[test]
fn aes_input_affect_every_byte() {
test_input_affect_every_byte(AHasher::test_with_keys);
}
#[test]
fn aes_keys_affect_every_byte() {
#[cfg(not(specialize))]
test_keys_affect_every_byte(0, AHasher::test_with_keys);
test_keys_affect_every_byte("", AHasher::test_with_keys);
test_keys_affect_every_byte((0, 0), AHasher::test_with_keys);
}
#[test]
fn aes_finish_is_consistant() {
test_finish_is_consistent(AHasher::test_with_keys)
}
#[test]
fn aes_padding_doesnot_collide() {
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY, BAD_KEY));
test_padding_doesnot_collide(|| AHasher::test_with_keys(BAD_KEY2, BAD_KEY2));
}
#[test]
fn aes_length_extension() {
test_length_extension(|a, b| AHasher::test_with_keys(a, b));
}
#[test]
fn aes_no_sparse_collisions() {
test_sparse(|| AHasher::test_with_keys(0, 0));
test_sparse(|| AHasher::test_with_keys(1, 2));
}
}
+352
View File
@@ -0,0 +1,352 @@
use crate::RandomState;
use std::collections::{hash_set, HashSet};
use std::fmt::{self, Debug};
use std::hash::{BuildHasher, Hash};
use std::iter::FromIterator;
use std::ops::{BitAnd, BitOr, BitXor, Deref, DerefMut, Sub};
#[cfg(feature = "serde")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
/// A [`HashSet`](std::collections::HashSet) using [`RandomState`](crate::RandomState) to hash the items.
/// (Requires the `std` feature to be enabled.)
#[derive(Clone)]
pub struct AHashSet<T, S = RandomState>(HashSet<T, S>);
impl<T> From<HashSet<T, RandomState>> for AHashSet<T> {
fn from(item: HashSet<T, RandomState>) -> Self {
AHashSet(item)
}
}
impl<T, const N: usize> From<[T; N]> for AHashSet<T>
where
T: Eq + Hash,
{
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let set1 = AHashSet::from([1, 2, 3, 4]);
/// let set2: AHashSet<_> = [1, 2, 3, 4].into();
/// assert_eq!(set1, set2);
/// ```
fn from(arr: [T; N]) -> Self {
Self::from_iter(arr)
}
}
impl<T> Into<HashSet<T, RandomState>> for AHashSet<T> {
fn into(self) -> HashSet<T, RandomState> {
self.0
}
}
impl<T> AHashSet<T, RandomState> {
/// This creates a hashset using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn new() -> Self {
AHashSet(HashSet::with_hasher(RandomState::new()))
}
/// This craetes a hashset with the specified capacity using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
pub fn with_capacity(capacity: usize) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, RandomState::new()))
}
}
impl<T, S> AHashSet<T, S>
where
S: BuildHasher,
{
pub fn with_hasher(hash_builder: S) -> Self {
AHashSet(HashSet::with_hasher(hash_builder))
}
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
AHashSet(HashSet::with_capacity_and_hasher(capacity, hash_builder))
}
}
impl<T, S> Deref for AHashSet<T, S> {
type Target = HashSet<T, S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, S> DerefMut for AHashSet<T, S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T, S> PartialEq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
fn eq(&self, other: &AHashSet<T, S>) -> bool {
self.0.eq(&other.0)
}
}
impl<T, S> Eq for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
}
impl<T, S> BitOr<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the union of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a | &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 3, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitor(&rhs.0))
}
}
impl<T, S> BitAnd<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the intersection of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![2, 3, 4].into_iter().collect();
///
/// let set = &a & &b;
///
/// let mut i = 0;
/// let expected = [2, 3];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitand(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitand(&rhs.0))
}
}
impl<T, S> BitXor<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the symmetric difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a ^ &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 4, 5];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitxor(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.bitxor(&rhs.0))
}
}
impl<T, S> Sub<&AHashSet<T, S>> for &AHashSet<T, S>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
{
type Output = AHashSet<T, S>;
/// Returns the difference of `self` and `rhs` as a new `AHashSet<T, S>`.
///
/// # Examples
///
/// ```
/// use ahash::AHashSet;
///
/// let a: AHashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: AHashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// let set = &a - &b;
///
/// let mut i = 0;
/// let expected = [1, 2];
/// for x in &set {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn sub(self, rhs: &AHashSet<T, S>) -> AHashSet<T, S> {
AHashSet(self.0.sub(&rhs.0))
}
}
impl<T, S> Debug for AHashSet<T, S>
where
T: Debug,
S: BuildHasher,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl<T> FromIterator<T> for AHashSet<T, RandomState>
where
T: Eq + Hash,
{
/// This creates a hashset from the provided iterator using [RandomState::new].
/// See the documentation in [RandomSource] for notes about key strength.
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> AHashSet<T> {
let mut inner = HashSet::with_hasher(RandomState::new());
inner.extend(iter);
AHashSet(inner)
}
}
impl<'a, T, S> IntoIterator for &'a AHashSet<T, S> {
type Item = &'a T;
type IntoIter = hash_set::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<T, S> IntoIterator for AHashSet<T, S> {
type Item = T;
type IntoIter = hash_set::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<T, S> Extend<T> for AHashSet<T, S>
where
T: Eq + Hash,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
impl<'a, T, S> Extend<&'a T> for AHashSet<T, S>
where
T: 'a + Eq + Hash + Copy,
S: BuildHasher,
{
#[inline]
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
/// NOTE: For safety this trait impl is only available available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl<T> Default for AHashSet<T, RandomState> {
/// Creates an empty `AHashSet<T, S>` with the `Default` value for the hasher.
#[inline]
fn default() -> AHashSet<T, RandomState> {
AHashSet(HashSet::default())
}
}
#[cfg(feature = "serde")]
impl<T> Serialize for AHashSet<T>
where
T: Serialize + Eq + Hash,
{
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.deref().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for AHashSet<T>
where
T: Deserialize<'de> + Eq + Hash,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let hash_set = HashSet::deserialize(deserializer);
hash_set.map(|hash_set| Self(hash_set))
}
fn deserialize_in_place<D: Deserializer<'de>>(deserializer: D, place: &mut Self) -> Result<(), D::Error> {
HashSet::deserialize_in_place(deserializer, place)
}
}
#[cfg(all(test, feature = "serde"))]
mod test {
use super::*;
#[test]
fn test_serde() {
let mut set = AHashSet::new();
set.insert("for".to_string());
set.insert("bar".to_string());
let mut serialization = serde_json::to_string(&set).unwrap();
let mut deserialization: AHashSet<String> = serde_json::from_str(&serialization).unwrap();
assert_eq!(deserialization, set);
set.insert("baz".to_string());
serialization = serde_json::to_string(&set).unwrap();
let mut deserializer = serde_json::Deserializer::from_str(&serialization);
AHashSet::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap();
assert_eq!(deserialization, set);
}
}
+358
View File
@@ -0,0 +1,358 @@
//! AHash is a high performance keyed hash function.
//!
//! It quickly provides a high quality hash where the result is not predictable without knowing the Key.
//! AHash works with `HashMap` to hash keys, but without allowing for the possibility that an malicious user can
//! induce a collision.
//!
//! # How aHash works
//!
//! When it is available aHash uses the hardware AES instructions to provide a keyed hash function.
//! When it is not, aHash falls back on a slightly slower alternative algorithm.
//!
//! Because aHash does not have a fixed standard for its output, it is able to improve over time.
//! But this also means that different computers or computers using different versions of ahash may observe different
//! hash values for the same input.
#![cfg_attr(
all(
feature = "std",
any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng")
),
doc = r##"
# Basic Usage
AHash provides an implementation of the [Hasher] trait.
To construct a HashMap using aHash as its hasher do the following:
```
use ahash::{AHasher, RandomState};
use std::collections::HashMap;
let mut map: HashMap<i32, i32, RandomState> = HashMap::default();
map.insert(12, 34);
```
### Randomness
The above requires a source of randomness to generate keys for the hashmap. By default this obtained from the OS.
It is also possible to have randomness supplied via the `compile-time-rng` flag, or manually.
### If randomness is not available
[AHasher::default()] can be used to hash using fixed keys. This works with
[BuildHasherDefault](std::hash::BuildHasherDefault). For example:
```
use std::hash::BuildHasherDefault;
use std::collections::HashMap;
use ahash::AHasher;
let mut m: HashMap<_, _, BuildHasherDefault<AHasher>> = HashMap::default();
# m.insert(12, 34);
```
It is also possible to instantiate [RandomState] directly:
```
use ahash::HashMap;
use ahash::RandomState;
let mut m = HashMap::with_hasher(RandomState::with_seed(42));
# m.insert(1, 2);
```
Or for uses besides a hashhmap:
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::with_seed(42);
let hash = hash_builder.hash_one("Some Data");
```
There are several constructors for [RandomState] with different ways to supply seeds.
# Convenience wrappers
For convenience, both new-type wrappers and type aliases are provided.
The new type wrappers are called called `AHashMap` and `AHashSet`.
```
use ahash::AHashMap;
let mut map: AHashMap<i32, i32> = AHashMap::new();
map.insert(12, 34);
```
This avoids the need to type "RandomState". (For convenience `From`, `Into`, and `Deref` are provided).
# Aliases
For even less typing and better interop with existing libraries (such as rayon) which require a `std::collection::HashMap` ,
the type aliases [HashMap], [HashSet] are provided.
```
use ahash::{HashMap, HashMapExt};
let mut map: HashMap<i32, i32> = HashMap::new();
map.insert(12, 34);
```
Note the import of [HashMapExt]. This is needed for the constructor.
"##
)]
#![deny(clippy::correctness, clippy::complexity, clippy::perf)]
#![allow(clippy::pedantic, clippy::cast_lossless, clippy::unreadable_literal)]
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
#![cfg_attr(specialize, feature(min_specialization))]
#![cfg_attr(feature = "nightly-arm-aes", feature(stdarch_arm_neon_intrinsics))]
#[macro_use]
mod convert;
mod fallback_hash;
cfg_if::cfg_if! {
if #[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))] {
mod aes_hash;
pub use crate::aes_hash::AHasher;
} else {
pub use crate::fallback_hash::AHasher;
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
mod hash_map;
mod hash_set;
pub use crate::hash_map::AHashMap;
pub use crate::hash_set::AHashSet;
/// [Hasher]: std::hash::Hasher
/// [HashMap]: std::collections::HashMap
/// Type alias for [HashMap]<K, V, ahash::RandomState>
pub type HashMap<K, V> = std::collections::HashMap<K, V, crate::RandomState>;
/// Type alias for [HashSet]<K, ahash::RandomState>
pub type HashSet<K> = std::collections::HashSet<K, crate::RandomState>;
}
}
#[cfg(test)]
mod hash_quality_test;
mod operations;
pub mod random_state;
mod specialize;
pub use crate::random_state::RandomState;
use core::hash::BuildHasher;
#[cfg(feature = "std")]
/// A convenience trait that can be used together with the type aliases defined to
/// get access to the `new()` and `with_capacity()` methods for the HashMap type alias.
pub trait HashMapExt {
/// Constructs a new HashMap
fn new() -> Self;
/// Constructs a new HashMap with a given initial capacity
fn with_capacity(capacity: usize) -> Self;
}
#[cfg(feature = "std")]
/// A convenience trait that can be used together with the type aliases defined to
/// get access to the `new()` and `with_capacity()` methods for the HashSet type aliases.
pub trait HashSetExt {
/// Constructs a new HashSet
fn new() -> Self;
/// Constructs a new HashSet with a given initial capacity
fn with_capacity(capacity: usize) -> Self;
}
#[cfg(feature = "std")]
impl<K, V, S> HashMapExt for std::collections::HashMap<K, V, S>
where
S: BuildHasher + Default,
{
fn new() -> Self {
std::collections::HashMap::with_hasher(S::default())
}
fn with_capacity(capacity: usize) -> Self {
std::collections::HashMap::with_capacity_and_hasher(capacity, S::default())
}
}
#[cfg(feature = "std")]
impl<K, S> HashSetExt for std::collections::HashSet<K, S>
where
S: BuildHasher + Default,
{
fn new() -> Self {
std::collections::HashSet::with_hasher(S::default())
}
fn with_capacity(capacity: usize) -> Self {
std::collections::HashSet::with_capacity_and_hasher(capacity, S::default())
}
}
/// Provides a default [Hasher] with fixed keys.
/// This is typically used in conjunction with [BuildHasherDefault] to create
/// [AHasher]s in order to hash the keys of the map.
///
/// Generally it is preferable to use [RandomState] instead, so that different
/// hashmaps will have different keys. However if fixed keys are desirable this
/// may be used instead.
///
/// # Example
/// ```
/// use std::hash::BuildHasherDefault;
/// use ahash::{AHasher, RandomState};
/// use std::collections::HashMap;
///
/// let mut map: HashMap<i32, i32, BuildHasherDefault<AHasher>> = HashMap::default();
/// map.insert(12, 34);
/// ```
///
/// [BuildHasherDefault]: std::hash::BuildHasherDefault
/// [Hasher]: std::hash::Hasher
/// [HashMap]: std::collections::HashMap
impl Default for AHasher {
/// Constructs a new [AHasher] with fixed keys.
/// If `std` is enabled these will be generated upon first invocation.
/// Otherwise if the `compile-time-rng`feature is enabled these will be generated at compile time.
/// If neither of these features are available, hardcoded constants will be used.
///
/// Because the values are fixed, different hashers will all hash elements the same way.
/// This could make hash values predictable, if DOS attacks are a concern. If this behaviour is
/// not required, it may be preferable to use [RandomState] instead.
///
/// # Examples
///
/// ```
/// use ahash::AHasher;
/// use std::hash::Hasher;
///
/// let mut hasher_1 = AHasher::default();
/// let mut hasher_2 = AHasher::default();
///
/// hasher_1.write_u32(1234);
/// hasher_2.write_u32(1234);
///
/// assert_eq!(hasher_1.finish(), hasher_2.finish());
/// ```
#[inline]
fn default() -> AHasher {
RandomState::with_fixed_keys().build_hasher()
}
}
// #[inline(never)]
// #[doc(hidden)]
// pub fn hash_test(input: &[u8]) -> u64 {
// let a = RandomState::with_seeds(11, 22, 33, 44);
// <[u8]>::get_hash(input, &a)
// }
#[cfg(feature = "std")]
#[cfg(test)]
mod test {
use crate::convert::Convert;
use crate::specialize::CallHasher;
use crate::*;
use core::hash::Hash;
use core::hash::Hasher;
use std::collections::HashMap;
#[test]
fn test_ahash_alias_map_construction() {
let mut map = super::HashMap::with_capacity(1234);
map.insert(1, "test");
}
#[test]
fn test_ahash_alias_set_construction() {
let mut set = super::HashSet::with_capacity(1234);
set.insert(1);
}
#[test]
fn test_default_builder() {
use core::hash::BuildHasherDefault;
let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
map.insert(1, 3);
}
#[test]
fn test_builder() {
let mut map = HashMap::<u32, u64, RandomState>::default();
map.insert(1, 3);
}
#[test]
fn test_conversion() {
let input: &[u8] = b"dddddddd";
let bytes: u64 = as_array!(input, 8).convert();
assert_eq!(bytes, 0x6464646464646464);
}
#[test]
fn test_non_zero() {
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
"foo".hash(&mut hasher1);
"bar".hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
let mut hasher1 = AHasher::new_with_keys(0, 0);
let mut hasher2 = AHasher::new_with_keys(0, 0);
3_u64.hash(&mut hasher1);
4_u64.hash(&mut hasher2);
assert_ne!(hasher1.finish(), 0);
assert_ne!(hasher2.finish(), 0);
assert_ne!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_non_zero_specialized() {
let hasher_build = RandomState::with_seeds(0, 0, 0, 0);
let h1 = str::get_hash("foo", &hasher_build);
let h2 = str::get_hash("bar", &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
let h1 = u64::get_hash(&3_u64, &hasher_build);
let h2 = u64::get_hash(&4_u64, &hasher_build);
assert_ne!(h1, 0);
assert_ne!(h2, 0);
assert_ne!(h1, h2);
}
#[test]
fn test_ahasher_construction() {
let _ = AHasher::new_with_keys(1234, 5678);
}
#[test]
fn test_specialize_reference_hash() {
let hasher_build = RandomState::with_seeds(0, 0, 0, 0);
let h1 = hasher_build.hash_one(1u64);
let h2 = hasher_build.hash_one(&1u64);
assert_eq!(h1, h2);
let h1 = u64::get_hash(&1_u64, &hasher_build);
let h2 = <&u64>::get_hash(&&1_u64, &hasher_build);
assert_eq!(h1, h2);
let h1 = hasher_build.hash_one(1u128);
let h2 = hasher_build.hash_one(&1u128);
assert_eq!(h1, h2);
}
}
+374
View File
@@ -0,0 +1,374 @@
use crate::convert::*;
#[allow(unused)]
use zerocopy::transmute;
///This constant comes from Kunth's prng (Empirically it works better than those from splitmix32).
pub(crate) const MULTIPLE: u64 = 6364136223846793005;
/// This is a constant with a lot of special properties found by automated search.
/// See the unit tests below. (Below are alternative values)
#[cfg(all(target_feature = "ssse3", not(miri)))]
const SHUFFLE_MASK: u128 = 0x020a0700_0c01030e_050f0d08_06090b04_u128;
//const SHUFFLE_MASK: u128 = 0x000d0702_0a040301_05080f0c_0e0b0609_u128;
//const SHUFFLE_MASK: u128 = 0x040A0700_030E0106_0D050F08_020B0C09_u128;
#[inline(always)]
#[cfg(folded_multiply)]
pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 {
let result = (s as u128).wrapping_mul(by as u128);
((result & 0xffff_ffff_ffff_ffff) as u64) ^ ((result >> 64) as u64)
}
#[inline(always)]
#[cfg(not(folded_multiply))]
pub(crate) const fn folded_multiply(s: u64, by: u64) -> u64 {
let b1 = s.wrapping_mul(by.swap_bytes());
let b2 = s.swap_bytes().wrapping_mul(!by);
b1 ^ b2.swap_bytes()
}
/// Given a small (less than 8 byte slice) returns the same data stored in two u32s.
/// (order of and non-duplication of bytes is NOT guaranteed)
#[inline(always)]
pub(crate) fn read_small(data: &[u8]) -> [u64; 2] {
debug_assert!(data.len() <= 8);
if data.len() >= 2 {
if data.len() >= 4 {
//len 4-8
[data.read_u32().0 as u64, data.read_last_u32() as u64]
} else {
//len 2-3
[data.read_u16().0 as u64, data[data.len() - 1] as u64]
}
} else {
if data.len() > 0 {
[data[0] as u64, data[0] as u64]
} else {
[0, 0]
}
}
}
#[inline(always)]
pub(crate) fn shuffle(a: u128) -> u128 {
#[cfg(all(target_feature = "ssse3", not(miri)))]
{
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe { transmute!(_mm_shuffle_epi8(transmute!(a), transmute!(SHUFFLE_MASK))) }
}
#[cfg(not(all(target_feature = "ssse3", not(miri))))]
{
a.swap_bytes()
}
}
#[allow(unused)] //not used by fallback
#[inline(always)]
pub(crate) fn add_and_shuffle(a: u128, b: u128) -> u128 {
let sum = add_by_64s(a.convert(), b.convert());
shuffle(sum.convert())
}
#[allow(unused)] //not used by fallback
#[inline(always)]
pub(crate) fn shuffle_and_add(base: u128, to_add: u128) -> u128 {
let shuffled: [u64; 2] = shuffle(base).convert();
add_by_64s(shuffled, to_add.convert()).convert()
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri)))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
unsafe {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
transmute!(_mm_add_epi64(transmute!(a), transmute!(b)))
}
}
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2", not(miri))))]
#[inline(always)]
pub(crate) fn add_by_64s(a: [u64; 2], b: [u64; 2]) -> [u64; 2] {
[a[0].wrapping_add(b[0]), a[1].wrapping_add(b[1])]
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let value = transmute!(value);
transmute!(_mm_aesenc_si128(value, transmute!(xor)))
}
}
#[cfg(any(
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesenc(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
let res = unsafe { vaesmcq_u8(vaeseq_u8(transmute!(value), transmute!(0u128))) };
let value: u128 = transmute!(res);
xor ^ value
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let value = transmute!(value);
transmute!(_mm_aesdec_si128(value, transmute!(xor)))
}
}
#[cfg(any(
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))]
#[allow(unused)]
#[inline(always)]
pub(crate) fn aesdec(value: u128, xor: u128) -> u128 {
#[cfg(target_arch = "aarch64")]
use core::arch::aarch64::*;
#[cfg(target_arch = "arm")]
use core::arch::arm::*;
let res = unsafe { vaesimcq_u8(vaesdq_u8(transmute!(value), transmute!(0u128))) };
let value: u128 = transmute!(res);
xor ^ value
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn add_in_length(enc: &mut u128, len: u64) {
#[cfg(all(target_arch = "x86_64", target_feature = "sse2", not(miri)))]
{
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
unsafe {
let enc = enc as *mut u128;
let len = _mm_cvtsi64_si128(len as i64);
let data = _mm_loadu_si128(enc.cast());
let sum = _mm_add_epi64(data, len);
_mm_storeu_si128(enc.cast(), sum);
}
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "sse2", not(miri))))]
{
let mut t: [u64; 2] = enc.convert();
t[0] = t[0].wrapping_add(len);
*enc = t.convert();
}
}
#[cfg(test)]
mod test {
use super::*;
// This is code to search for the shuffle constant
//
//thread_local! { static MASK: Cell<u128> = Cell::new(0); }
//
// fn shuffle(a: u128) -> u128 {
// use std::intrinsics::transmute;
// #[cfg(target_arch = "x86")]
// use core::arch::x86::*;
// #[cfg(target_arch = "x86_64")]
// use core::arch::x86_64::*;
// MASK.with(|mask| {
// unsafe { transmute!(_mm_shuffle_epi8(transmute!(a), transmute!(mask.get()))) }
// })
// }
//
// #[test]
// fn find_shuffle() {
// use rand::prelude::*;
// use SliceRandom;
// use std::panic;
// use std::io::Write;
//
// let mut value: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15];
// let mut rand = thread_rng();
// let mut successful_list = HashMap::new();
// for _attempt in 0..10000000 {
// rand.shuffle(&mut value);
// let test_val = value.convert();
// MASK.with(|mask| {
// mask.set(test_val);
// });
// if let Ok(successful) = panic::catch_unwind(|| {
// test_shuffle_does_not_collide_with_aes();
// test_shuffle_moves_high_bits();
// test_shuffle_moves_every_value();
// //test_shuffle_does_not_loop();
// value
// }) {
// let successful: u128 = successful.convert();
// successful_list.insert(successful, iters_before_loop());
// }
// }
// let write_file = File::create("/tmp/output").unwrap();
// let mut writer = BufWriter::new(&write_file);
//
// for success in successful_list {
// writeln!(writer, "Found successful: {:x?} - {:?}", success.0, success.1);
// }
// }
//
// fn iters_before_loop() -> u32 {
// let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
// let mut shuffled = shuffle(numbered);
// let mut count = 0;
// loop {
// // println!("{:>16x}", shuffled);
// if numbered == shuffled {
// break;
// }
// count += 1;
// shuffled = shuffle(shuffled);
// }
// count
// }
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
target_feature = "aes",
not(miri)
))]
#[test]
fn test_shuffle_does_not_collide_with_aes() {
let mut value: [u8; 16] = [0; 16];
let zero_mask_enc = aesenc(0, 0);
let zero_mask_dec = aesdec(0, 0);
for index in 0..16 {
value[index] = 1;
let excluded_positions_enc: [u8; 16] = aesenc(value.convert(), zero_mask_enc).convert();
let excluded_positions_dec: [u8; 16] = aesdec(value.convert(), zero_mask_dec).convert();
let actual_location: [u8; 16] = shuffle(value.convert()).convert();
for pos in 0..16 {
if actual_location[pos] != 0 {
assert_eq!(
0, excluded_positions_enc[pos],
"Forward Overlap between {:?} and {:?} at {}",
excluded_positions_enc, actual_location, index
);
assert_eq!(
0, excluded_positions_dec[pos],
"Reverse Overlap between {:?} and {:?} at {}",
excluded_positions_dec, actual_location, index
);
}
}
value[index] = 0;
}
}
#[test]
fn test_shuffle_contains_each_value() {
let value: [u8; 16] = 0x00010203_04050607_08090A0B_0C0D0E0F_u128.convert();
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
for index in 0..16_u8 {
assert!(shuffled.contains(&index), "Value is missing {}", index);
}
}
#[test]
fn test_shuffle_moves_every_value() {
let mut value: [u8; 16] = [0; 16];
for index in 0..16 {
value[index] = 1;
let shuffled: [u8; 16] = shuffle(value.convert()).convert();
assert_eq!(0, shuffled[index], "Value is not moved {}", index);
value[index] = 0;
}
}
#[test]
fn test_shuffle_moves_high_bits() {
assert!(
shuffle(1) > (1_u128 << 80),
"Low bits must be moved to other half {:?} -> {:?}",
0,
shuffle(1)
);
assert!(
shuffle(1_u128 << 58) >= (1_u128 << 64),
"High bits must be moved to other half {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 58) < (1_u128 << 112),
"High bits must not remain high {:?} -> {:?}",
7,
shuffle(1_u128 << 58)
);
assert!(
shuffle(1_u128 << 64) < (1_u128 << 64),
"Low bits must be moved to other half {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 64) >= (1_u128 << 16),
"Low bits must not remain low {:?} -> {:?}",
8,
shuffle(1_u128 << 64)
);
assert!(
shuffle(1_u128 << 120) < (1_u128 << 50),
"High bits must be moved to low half {:?} -> {:?}",
15,
shuffle(1_u128 << 120)
);
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3",
not(miri)
))]
#[test]
fn test_shuffle_does_not_loop() {
let numbered = 0x00112233_44556677_8899AABB_CCDDEEFF;
let mut shuffled = shuffle(numbered);
for count in 0..100 {
// println!("{:>16x}", shuffled);
assert_ne!(numbered, shuffled, "Equal after {} vs {:x}", count, shuffled);
shuffled = shuffle(shuffled);
}
}
#[test]
fn test_add_length() {
let enc : [u64; 2] = [50, u64::MAX];
let mut enc : u128 = enc.convert();
add_in_length(&mut enc, u64::MAX);
let enc : [u64; 2] = enc.convert();
assert_eq!(enc[1], u64::MAX);
assert_eq!(enc[0], 49);
}
}
+523
View File
@@ -0,0 +1,523 @@
use core::hash::Hash;
cfg_if::cfg_if! {
if #[cfg(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(feature = "nightly-arm-aes", target_arch = "arm", target_feature = "aes", not(miri)),
))] {
use crate::aes_hash::*;
} else {
use crate::fallback_hash::*;
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
extern crate std as alloc;
} else {
extern crate alloc;
}
}
#[cfg(feature = "atomic-polyfill")]
use portable_atomic as atomic;
#[cfg(not(feature = "atomic-polyfill"))]
use core::sync::atomic;
use alloc::boxed::Box;
use atomic::{AtomicUsize, Ordering};
use core::any::{Any, TypeId};
use core::fmt;
use core::hash::BuildHasher;
use core::hash::Hasher;
pub(crate) const PI: [u64; 4] = [
0x243f_6a88_85a3_08d3,
0x1319_8a2e_0370_7344,
0xa409_3822_299f_31d0,
0x082e_fa98_ec4e_6c89,
];
pub(crate) const PI2: [u64; 4] = [
0x4528_21e6_38d0_1377,
0xbe54_66cf_34e9_0c6c,
0xc0ac_29b7_c97c_50dd,
0x3f84_d5b5_b547_0917,
];
cfg_if::cfg_if! {
if #[cfg(all(feature = "compile-time-rng", any(test, fuzzing)))] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use const_random::const_random;
const RAND: [[u64; 4]; 2] = [
[
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
], [
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
]
];
&RAND
}
} else if #[cfg(all(feature = "runtime-rng", not(fuzzing)))] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use crate::convert::Convert;
static SEEDS: OnceBox<[[u64; 4]; 2]> = OnceBox::new();
SEEDS.get_or_init(|| {
let mut result: [u8; 64] = [0; 64];
getrandom::fill(&mut result).expect("getrandom::fill() failed.");
Box::new(result.convert())
})
}
} else if #[cfg(feature = "compile-time-rng")] {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
use const_random::const_random;
const RAND: [[u64; 4]; 2] = [
[
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
], [
const_random!(u64),
const_random!(u64),
const_random!(u64),
const_random!(u64),
]
];
&RAND
}
} else {
#[inline]
fn get_fixed_seeds() -> &'static [[u64; 4]; 2] {
&[PI, PI2]
}
}
}
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "arm", target_os = "none")))] {
use once_cell::race::OnceBox;
static RAND_SOURCE: OnceBox<Box<dyn RandomSource + Send + Sync>> = OnceBox::new();
}
}
/// A supplier of Randomness used for different hashers.
/// See [set_random_source].
///
/// If [set_random_source] aHash will default to the best available source of randomness.
/// In order this is:
/// 1. OS provided random number generator (available if the `runtime-rng` flag is enabled which it is by default) - This should be very strong.
/// 2. Strong compile time random numbers used to permute a static "counter". (available if `compile-time-rng` is enabled.
/// __Enabling this is recommended if `runtime-rng` is not possible__)
/// 3. A static counter that adds the memory address of each [RandomState] created permuted with fixed constants.
/// (Similar to above but with fixed keys) - This is the weakest option. The strength of this heavily depends on whether or not ASLR is enabled.
/// (Rust enables ASLR by default)
pub trait RandomSource {
fn gen_hasher_seed(&self) -> usize;
}
struct DefaultRandomSource {
counter: AtomicUsize,
}
impl DefaultRandomSource {
fn new() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(&PI as *const _ as usize),
}
}
#[cfg(all(target_arch = "arm", target_os = "none"))]
const fn default() -> DefaultRandomSource {
DefaultRandomSource {
counter: AtomicUsize::new(PI[3] as usize),
}
}
}
impl RandomSource for DefaultRandomSource {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "arm", target_os = "none"))] {
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
let previous = self.counter.load(Ordering::Relaxed);
let new = previous.wrapping_add(stack);
self.counter.store(new, Ordering::Relaxed);
new
}
} else {
fn gen_hasher_seed(&self) -> usize {
let stack = self as *const _ as usize;
self.counter.fetch_add(stack, Ordering::Relaxed)
}
}
}
}
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "arm", target_os = "none"))] {
#[inline]
fn get_src() -> &'static dyn RandomSource {
static RAND_SOURCE: DefaultRandomSource = DefaultRandomSource::default();
&RAND_SOURCE
}
} else {
/// Provides an optional way to manually supply a source of randomness for Hasher keys.
///
/// The provided [RandomSource] will be used to be used as a source of randomness by [RandomState] to generate new states.
/// If this method is not invoked the standard source of randomness is used as described in the Readme.
///
/// The source of randomness can only be set once, and must be set before the first RandomState is created.
/// If the source has already been specified `Err` is returned with a `bool` indicating if the set failed because
/// method was previously invoked (true) or if the default source is already being used (false).
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub fn set_random_source(source: impl RandomSource + Send + Sync + 'static) -> Result<(), bool> {
RAND_SOURCE.set(Box::new(Box::new(source))).map_err(|s| s.as_ref().type_id() != TypeId::of::<&DefaultRandomSource>())
}
#[inline]
fn get_src() -> &'static dyn RandomSource {
RAND_SOURCE.get_or_init(|| Box::new(Box::new(DefaultRandomSource::new()))).as_ref()
}
}
}
/// Provides a [Hasher] factory. This is typically used (e.g. by [HashMap]) to create
/// [AHasher]s in order to hash the keys of the map. See `build_hasher` below.
///
/// [build_hasher]: ahash::
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
///
/// There are multiple constructors each is documented in more detail below:
///
/// | Constructor | Dynamically random? | Seed |
/// |---------------|---------------------|------|
/// |`new` | Each instance unique|_[RandomSource]_|
/// |`generate_with`| Each instance unique|`u64` x 4 + [RandomSource]|
/// |`with_seed` | Fixed per process |`u64` + static random number|
/// |`with_seeds` | Fixed |`u64` x 4|
///
#[derive(Clone)]
pub struct RandomState {
pub(crate) k0: u64,
pub(crate) k1: u64,
pub(crate) k2: u64,
pub(crate) k3: u64,
}
impl fmt::Debug for RandomState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("RandomState { .. }")
}
}
impl RandomState {
/// Create a new `RandomState` `BuildHasher` using random keys.
///
/// Each instance will have a unique set of keys derived from [RandomSource].
///
#[inline]
pub fn new() -> RandomState {
let src = get_src();
let fixed = get_fixed_seeds();
Self::from_keys(&fixed[0], &fixed[1], src.gen_hasher_seed())
}
/// Create a new `RandomState` `BuildHasher` based on the provided seeds, but in such a way
/// that each time it is called the resulting state will be different and of high quality.
/// This allows fixed constant or poor quality seeds to be provided without the problem of different
/// `BuildHasher`s being identical or weak.
///
/// This is done via permuting the provided values with the value of a static counter and memory address.
/// (This makes this method somewhat more expensive than `with_seeds` below which does not do this).
///
/// The provided values (k0-k3) do not need to be of high quality but they should not all be the same value.
#[inline]
pub fn generate_with(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
let src = get_src();
let fixed = get_fixed_seeds();
RandomState::from_keys(&fixed[0], &[k0, k1, k2, k3], src.gen_hasher_seed())
}
fn from_keys(a: &[u64; 4], b: &[u64; 4], c: usize) -> RandomState {
let &[k0, k1, k2, k3] = a;
let mut hasher = AHasher::from_random_state(&RandomState { k0, k1, k2, k3 });
hasher.write_usize(c);
let mix = |l: u64, r: u64| {
let mut h = hasher.clone();
h.write_u64(l);
h.write_u64(r);
h.finish()
};
RandomState {
k0: mix(b[0], b[2]),
k1: mix(b[1], b[3]),
k2: mix(b[2], b[1]),
k3: mix(b[3], b[0]),
}
}
/// Internal. Used by Default.
#[inline]
pub(crate) fn with_fixed_keys() -> RandomState {
let [k0, k1, k2, k3] = get_fixed_seeds()[0];
RandomState { k0, k1, k2, k3 }
}
/// Build a `RandomState` from a single key. The provided key does not need to be of high quality,
/// but all `RandomState`s created from the same key will produce identical hashers.
/// (In contrast to `generate_with` above)
///
/// This allows for explicitly setting the seed to be used.
///
/// Note: This method does not require the provided seed to be strong.
#[inline]
pub fn with_seed(key: usize) -> RandomState {
let fixed = get_fixed_seeds();
RandomState::from_keys(&fixed[0], &fixed[1], key)
}
/// Allows for explicitly setting the seeds to used.
/// All `RandomState`s created with the same set of keys key will produce identical hashers.
/// (In contrast to `generate_with` above)
///
/// Note: If DOS resistance is desired one of these should be a decent quality random number.
/// If 4 high quality random number are not cheaply available this method is robust against 0s being passed for
/// one or more of the parameters or the same value being passed for more than one parameter.
/// It is recommended to pass numbers in order from highest to lowest quality (if there is any difference).
#[inline]
pub const fn with_seeds(k0: u64, k1: u64, k2: u64, k3: u64) -> RandomState {
RandomState {
k0: k0 ^ PI2[0],
k1: k1 ^ PI2[1],
k2: k2 ^ PI2[2],
k3: k3 ^ PI2[3],
}
}
/// Calculates the hash of a single value. This provides a more convenient (and faster) way to obtain a hash:
/// For example:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::new();
let hash = hash_builder.hash_one("Some Data");
```
"##
)]
/// This is similar to:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
let hash_builder = RandomState::new();
let mut hasher = hash_builder.build_hasher();
"Some Data".hash(&mut hasher);
let hash = hasher.finish();
```
"##
)]
/// (Note that these two ways to get a hash may not produce the same value for the same data)
///
/// This is intended as a convenience for code which *consumes* hashes, such
/// as the implementation of a hash table or in unit tests that check
/// whether a custom [`Hash`] implementation behaves as expected.
///
/// This must not be used in any code which *creates* hashes, such as in an
/// implementation of [`Hash`]. The way to create a combined hash of
/// multiple values is to call [`Hash::hash`] multiple times using the same
/// [`Hasher`], not to call this method repeatedly and combine the results.
#[inline]
pub fn hash_one<T: Hash>(&self, x: T) -> u64
where
Self: Sized,
{
use crate::specialize::CallHasher;
T::get_hash(&x, self)
}
}
/// Creates an instance of RandomState using keys obtained from the random number generator.
/// Each instance created in this way will have a unique set of keys. (But the resulting instance
/// can be used to create many hashers each or which will have the same keys.)
///
/// This is the same as [RandomState::new()]
///
/// NOTE: For safety this trait impl is only available available if either of the flags `runtime-rng` (on by default) or
/// `compile-time-rng` are enabled. This is to prevent weakly keyed maps from being accidentally created. Instead one of
/// constructors for [RandomState] must be used.
#[cfg(any(feature = "compile-time-rng", feature = "runtime-rng", feature = "no-rng"))]
impl Default for RandomState {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl BuildHasher for RandomState {
type Hasher = AHasher;
/// Constructs a new [AHasher] with keys based on this [RandomState] object.
/// This means that two different [RandomState]s will will generate
/// [AHasher]s that will return different hashcodes, but [Hasher]s created from the same [BuildHasher]
/// will generate the same hashes for the same input data.
///
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use ahash::{AHasher, RandomState};
use std::hash::{Hasher, BuildHasher};
let build_hasher = RandomState::new();
let mut hasher_1 = build_hasher.build_hasher();
let mut hasher_2 = build_hasher.build_hasher();
hasher_1.write_u32(1234);
hasher_2.write_u32(1234);
assert_eq!(hasher_1.finish(), hasher_2.finish());
let other_build_hasher = RandomState::new();
let mut different_hasher = other_build_hasher.build_hasher();
different_hasher.write_u32(1234);
assert_ne!(different_hasher.finish(), hasher_1.finish());
```
"##
)]
/// [Hasher]: std::hash::Hasher
/// [BuildHasher]: std::hash::BuildHasher
/// [HashMap]: std::collections::HashMap
#[inline]
fn build_hasher(&self) -> AHasher {
AHasher::from_random_state(self)
}
/// Calculates the hash of a single value. This provides a more convenient (and faster) way to obtain a hash:
/// For example:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::BuildHasher;
use ahash::RandomState;
let hash_builder = RandomState::new();
let hash = hash_builder.hash_one("Some Data");
```
"##
)]
/// This is similar to:
#[cfg_attr(
feature = "std",
doc = r##" # Examples
```
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
let hash_builder = RandomState::new();
let mut hasher = hash_builder.build_hasher();
"Some Data".hash(&mut hasher);
let hash = hasher.finish();
```
"##
)]
/// (Note that these two ways to get a hash may not produce the same value for the same data)
///
/// This is intended as a convenience for code which *consumes* hashes, such
/// as the implementation of a hash table or in unit tests that check
/// whether a custom [`Hash`] implementation behaves as expected.
///
/// This must not be used in any code which *creates* hashes, such as in an
/// implementation of [`Hash`]. The way to create a combined hash of
/// multiple values is to call [`Hash::hash`] multiple times using the same
/// [`Hasher`], not to call this method repeatedly and combine the results.
#[cfg(specialize)]
#[inline]
fn hash_one<T: Hash>(&self, x: T) -> u64 {
RandomState::hash_one(self, x)
}
}
#[cfg(specialize)]
impl RandomState {
#[inline]
pub(crate) fn hash_as_u64<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherU64 {
buffer: self.k1,
pad: self.k0,
};
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub(crate) fn hash_as_fixed_length<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherFixed(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
#[inline]
pub(crate) fn hash_as_str<T: Hash + ?Sized>(&self, value: &T) -> u64 {
let mut hasher = AHasherStr(self.build_hasher());
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_unique() {
let a = RandomState::generate_with(1, 2, 3, 4);
let b = RandomState::generate_with(1, 2, 3, 4);
assert_ne!(a.build_hasher().finish(), b.build_hasher().finish());
}
#[cfg(all(feature = "runtime-rng", not(all(feature = "compile-time-rng", test))))]
#[test]
fn test_not_pi() {
assert_ne!(PI, get_fixed_seeds()[0]);
}
#[cfg(all(feature = "compile-time-rng", any(not(feature = "runtime-rng"), test)))]
#[test]
fn test_not_pi_const() {
assert_ne!(PI, get_fixed_seeds()[0]);
}
#[cfg(all(not(feature = "runtime-rng"), not(feature = "compile-time-rng")))]
#[test]
fn test_pi() {
assert_eq!(PI, get_fixed_seeds()[0]);
}
#[test]
fn test_with_seeds_const() {
const _CONST_RANDOM_STATE: RandomState = RandomState::with_seeds(17, 19, 21, 23);
}
}
+214
View File
@@ -0,0 +1,214 @@
use crate::RandomState;
use core::hash::BuildHasher;
use core::hash::Hash;
use core::hash::Hasher;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(specialize)]
use alloc::string::String;
#[cfg(specialize)]
use alloc::vec::Vec;
/// Provides a way to get an optimized hasher for a given data type.
/// Rather than using a Hasher generically which can hash any value, this provides a way to get a specialized hash
/// for a specific type. So this may be faster for primitive types.
pub(crate) trait CallHasher {
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64;
}
#[cfg(not(specialize))]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
let mut hasher = random_state.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
#[cfg(specialize)]
impl<T> CallHasher for T
where
T: Hash + ?Sized,
{
#[inline]
default fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
let mut hasher = random_state.build_hasher();
value.hash(&mut hasher);
hasher.finish()
}
}
macro_rules! call_hasher_impl_u64 {
($typ:ty) => {
#[cfg(specialize)]
impl CallHasher for $typ {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_u64(value)
}
}
};
}
call_hasher_impl_u64!(u8);
call_hasher_impl_u64!(u16);
call_hasher_impl_u64!(u32);
call_hasher_impl_u64!(u64);
call_hasher_impl_u64!(i8);
call_hasher_impl_u64!(i16);
call_hasher_impl_u64!(i32);
call_hasher_impl_u64!(i64);
call_hasher_impl_u64!(&u8);
call_hasher_impl_u64!(&u16);
call_hasher_impl_u64!(&u32);
call_hasher_impl_u64!(&u64);
call_hasher_impl_u64!(&i8);
call_hasher_impl_u64!(&i16);
call_hasher_impl_u64!(&i32);
call_hasher_impl_u64!(&i64);
macro_rules! call_hasher_impl_fixed_length{
($typ:ty) => {
#[cfg(specialize)]
impl CallHasher for $typ {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_fixed_length(value)
}
}
};
}
call_hasher_impl_fixed_length!(u128);
call_hasher_impl_fixed_length!(i128);
call_hasher_impl_fixed_length!(usize);
call_hasher_impl_fixed_length!(isize);
call_hasher_impl_fixed_length!(&u128);
call_hasher_impl_fixed_length!(&i128);
call_hasher_impl_fixed_length!(&usize);
call_hasher_impl_fixed_length!(&isize);
#[cfg(specialize)]
impl CallHasher for [u8] {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(specialize)]
impl CallHasher for Vec<u8> {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(specialize)]
impl CallHasher for str {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(all(specialize))]
impl CallHasher for String {
#[inline]
fn get_hash<H: Hash + ?Sized>(value: &H, random_state: &RandomState) -> u64 {
random_state.hash_as_str(value)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
#[cfg(specialize)]
pub fn test_specialized_invoked() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
let shortened = u64::get_hash(&0, &build_hasher);
let mut hasher = AHasher::new_with_keys(1, 2);
0_u64.hash(&mut hasher);
assert_ne!(hasher.finish(), shortened);
}
/// Tests that some non-trivial transformation takes place.
#[test]
pub fn test_input_processed() {
let build_hasher = RandomState::with_seeds(2, 2, 2, 2);
assert_ne!(0, u64::get_hash(&0, &build_hasher));
assert_ne!(1, u64::get_hash(&0, &build_hasher));
assert_ne!(2, u64::get_hash(&0, &build_hasher));
assert_ne!(3, u64::get_hash(&0, &build_hasher));
assert_ne!(4, u64::get_hash(&0, &build_hasher));
assert_ne!(5, u64::get_hash(&0, &build_hasher));
assert_ne!(0, u64::get_hash(&1, &build_hasher));
assert_ne!(1, u64::get_hash(&1, &build_hasher));
assert_ne!(2, u64::get_hash(&1, &build_hasher));
assert_ne!(3, u64::get_hash(&1, &build_hasher));
assert_ne!(4, u64::get_hash(&1, &build_hasher));
assert_ne!(5, u64::get_hash(&1, &build_hasher));
let xored = u64::get_hash(&0, &build_hasher) ^ u64::get_hash(&1, &build_hasher);
assert_ne!(0, xored);
assert_ne!(1, xored);
assert_ne!(2, xored);
assert_ne!(3, xored);
assert_ne!(4, xored);
assert_ne!(5, xored);
}
#[test]
pub fn test_ref_independent() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
assert_eq!(u8::get_hash(&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(specialize)]
assert_eq!(
str::get_hash(&"test", &build_hasher),
<[u8]>::get_hash("test".as_bytes(), &build_hasher)
);
let build_hasher = RandomState::with_seeds(10, 20, 30, 40);
assert_eq!(u8::get_hash(&&&1, &build_hasher), u8::get_hash(&1, &build_hasher));
assert_eq!(u16::get_hash(&&&2, &build_hasher), u16::get_hash(&2, &build_hasher));
assert_eq!(u32::get_hash(&&&3, &build_hasher), u32::get_hash(&3, &build_hasher));
assert_eq!(u64::get_hash(&&&4, &build_hasher), u64::get_hash(&4, &build_hasher));
assert_eq!(u128::get_hash(&&&5, &build_hasher), u128::get_hash(&5, &build_hasher));
assert_eq!(
str::get_hash(&&"test", &build_hasher),
str::get_hash("test", &build_hasher)
);
assert_eq!(
str::get_hash(&&"test", &build_hasher),
String::get_hash(&"test".to_string(), &build_hasher)
);
#[cfg(specialize)]
assert_eq!(
str::get_hash(&&"test", &build_hasher),
<[u8]>::get_hash(&"test".to_string().into_bytes(), &build_hasher)
);
}
}
+199
View File
@@ -0,0 +1,199 @@
#![cfg_attr(specialize, feature(build_hasher_simple_hash_one))]
use ahash::{AHasher, RandomState};
use criterion::*;
use fxhash::FxHasher;
use rand::Rng;
use std::collections::hash_map::DefaultHasher;
use std::hash::{BuildHasherDefault, Hash, Hasher};
// Needs to be in sync with `src/lib.rs`
const AHASH_IMPL: &str = if cfg!(any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "aes",
not(miri),
),
all(feature = "nightly-arm-aes", target_arch = "aarch64", target_feature = "aes", not(miri)),
all(
feature = "nightly-arm-aes",
target_arch = "arm",
target_feature = "aes",
not(miri)
),
)) {
"aeshash"
} else {
"fallbackhash"
};
fn ahash<H: Hash>(b: &H) -> u64 {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
build_hasher.hash_one(b)
}
fn fnvhash<H: Hash>(b: &H) -> u64 {
let mut hasher = fnv::FnvHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn siphash<H: Hash>(b: &H) -> u64 {
let mut hasher = DefaultHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn fxhash<H: Hash>(b: &H) -> u64 {
let mut hasher = FxHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
fn seahash<H: Hash>(b: &H) -> u64 {
let mut hasher = seahash::SeaHasher::default();
b.hash(&mut hasher);
hasher.finish()
}
const STRING_LENGTHS: [u32; 12] = [1, 3, 4, 7, 8, 15, 16, 24, 33, 68, 132, 1024];
fn gen_strings() -> Vec<String> {
STRING_LENGTHS
.iter()
.map(|len| {
let mut string = String::default();
for pos in 1..=*len {
let c = (48 + (pos % 10) as u8) as char;
string.push(c);
}
string
})
.collect()
}
macro_rules! bench_inputs {
($group:ident, $hash:ident) => {
// Number of iterations per batch should be high enough to hide timing overhead.
let size = BatchSize::NumIterations(50_000);
let mut rng = rand::thread_rng();
$group.bench_function("u8", |b| b.iter_batched(|| rng.gen::<u8>(), |v| $hash(&v), size));
$group.bench_function("u16", |b| b.iter_batched(|| rng.gen::<u16>(), |v| $hash(&v), size));
$group.bench_function("u32", |b| b.iter_batched(|| rng.gen::<u32>(), |v| $hash(&v), size));
$group.bench_function("u64", |b| b.iter_batched(|| rng.gen::<u64>(), |v| $hash(&v), size));
$group.bench_function("u128", |b| b.iter_batched(|| rng.gen::<u128>(), |v| $hash(&v), size));
$group.bench_with_input("strings", &gen_strings(), |b, s| b.iter(|| $hash(black_box(s))));
};
}
fn bench_ahash(c: &mut Criterion) {
let mut group = c.benchmark_group(AHASH_IMPL);
bench_inputs!(group, ahash);
}
fn bench_fx(c: &mut Criterion) {
let mut group = c.benchmark_group("fx");
bench_inputs!(group, fxhash);
}
fn bench_fnv(c: &mut Criterion) {
let mut group = c.benchmark_group("fnv");
bench_inputs!(group, fnvhash);
}
fn bench_sea(c: &mut Criterion) {
let mut group = c.benchmark_group("sea");
bench_inputs!(group, seahash);
}
fn bench_sip(c: &mut Criterion) {
let mut group = c.benchmark_group("sip");
bench_inputs!(group, siphash);
}
fn bench_map(c: &mut Criterion) {
#[cfg(feature = "std")]
{
let mut group = c.benchmark_group("map");
group.bench_function("aHash-alias", |b| {
b.iter(|| {
let hm: ahash::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-hashBrown", |b| {
b.iter(|| {
let hm: hashbrown::HashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-hashBrown-explicit", |b| {
b.iter(|| {
let hm: hashbrown::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-wrapper", |b| {
b.iter(|| {
let hm: ahash::AHashMap<i32, i32> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-rand", |b| {
b.iter(|| {
let hm: std::collections::HashMap<i32, i32, RandomState> = (0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
group.bench_function("aHash-default", |b| {
b.iter(|| {
let hm: std::collections::HashMap<i32, i32, BuildHasherDefault<AHasher>> =
(0..1_000_000).map(|i| (i, i)).collect();
let mut sum = 0;
for i in 0..1_000_000 {
if let Some(x) = hm.get(&i) {
sum += x;
}
}
})
});
}
}
criterion_main!(benches);
criterion_group!(
benches,
bench_ahash,
bench_fx,
bench_fnv,
bench_sea,
bench_sip,
bench_map
);
+310
View File
@@ -0,0 +1,310 @@
#![cfg_attr(specialize, feature(build_hasher_simple_hash_one))]
use std::hash::{BuildHasher, Hash, Hasher};
use ahash::RandomState;
use criterion::*;
use fxhash::FxHasher;
fn gen_word_pairs() -> Vec<String> {
let words: Vec<_> = r#"
a, ability, able, about, above, accept, according, account, across, act, action,
activity, actually, add, address, administration, admit, adult, affect, after,
again, against, age, agency, agent, ago, agree, agreement, ahead, air, all,
allow, almost, alone, along, already, also, although, always, American, among,
amount, analysis, and, animal, another, answer, any, anyone, anything, appear,
apply, approach, area, argue, arm, around, arrive, art, article, artist, as,
ask, assume, at, attack, attention, attorney, audience, author, authority,
available, avoid, away, baby, back, bad, bag, ball, bank, bar, base, be, beat,
beautiful, because, become, bed, before, begin, behavior, behind, believe,
benefit, best, better, between, beyond, big, bill, billion, bit, black, blood,
blue, board, body, book, born, both, box, boy, break, bring, brother, budget,
build, building, business, but, buy, by, call, camera, campaign, can, cancer,
candidate, capital, car, card, care, career, carry, case, catch, cause, cell,
center, central, century, certain, certainly, chair, challenge, chance, change,
character, charge, check, child, choice, choose, church, citizen, city, civil,
claim, class, clear, clearly, close, coach, cold, collection, college, color,
come, commercial, common, community, company, compare, computer, concern,
condition, conference, Congress, consider, consumer, contain, continue, control,
cost, could, country, couple, course, court, cover, create, crime, cultural,
culture, cup, current, customer, cut, dark, data, daughter, day, dead, deal,
death, debate, decade, decide, decision, deep, defense, degree, Democrat,
democratic, describe, design, despite, detail, determine, develop, development,
die, difference, different, difficult, dinner, direction, director, discover,
discuss, discussion, disease, do, doctor, dog, door, down, draw, dream, drive,
drop, drug, during, each, early, east, easy, eat, economic, economy, edge,
education, effect, effort, eight, either, election, else, employee, end, energy,
enjoy, enough, enter, entire, environment, environmental, especially, establish,
even, evening, event, ever, every, everybody, everyone, everything, evidence,
exactly, example, executive, exist, expect, experience, expert, explain, eye,
face, fact, factor, fail, fall, family, far, fast, father, fear, federal, feel,
feeling, few, field, fight, figure, fill, film, final, finally, financial, find,
fine, finger, finish, fire, firm, first, fish, five, floor, fly, focus, follow,
food, foot, for, force, foreign, forget, form, former, forward, four, free,
friend, from, front, full, fund, future, game, garden, gas, general, generation,
get, girl, give, glass, go, goal, good, government, great, green, ground, group,
grow, growth, guess, gun, guy, hair, half, hand, hang, happen, happy, hard,
have, he, head, health, hear, heart, heat, heavy, help, her, here, herself,
high, him, himself, his, history, hit, hold, home, hope, hospital, hot, hotel,
hour, house, how, however, huge, human, hundred, husband, I, idea, identify, if,
image, imagine, impact, important, improve, in, include, including, increase,
indeed, indicate, individual, industry, information, inside, instead,
institution, interest, interesting, international, interview, into, investment,
involve, issue, it, item, its, itself, job, join, just, keep, key, kid, kill,
kind, kitchen, know, knowledge, land, language, large, last, late, later, laugh,
law, lawyer, lay, lead, leader, learn, least, leave, left, leg, legal, less,
let, letter, level, lie, life, light, like, likely, line, list, listen, little,
live, local, long, look, lose, loss, lot, love, low, machine, magazine, main,
maintain, major, majority, make, man, manage, management, manager, many, market,
marriage, material, matter, may, maybe, me, mean, measure, media, medical, meet,
meeting, member, memory, mention, message, method, middle, might, military,
million, mind, minute, miss, mission, model, modern, moment, money, month, more,
morning, most, mother, mouth, move, movement, movie, Mr, Mrs, much, music, must,
my, myself, name, nation, national, natural, nature, near, nearly, necessary,
need, network, never, new, news, newspaper, next, nice, night, no, none, nor,
north, not, note, nothing, notice, now, n't, number, occur, of, off, offer,
office, officer, official, often, oh, oil, ok, old, on, once, one, only, onto,
open, operation, opportunity, option, or, order, organization, other, others,
our, out, outside, over, own, owner, page, pain, painting, paper, parent, part,
participant, particular, particularly, partner, party, pass, past, patient,
pattern, pay, peace, people, per, perform, performance, perhaps, period, person,
personal, phone, physical, pick, picture, piece, place, plan, plant, play,
player, PM, point, police, policy, political, politics, poor, popular,
population, position, positive, possible, power, practice, prepare, present,
president, pressure, pretty, prevent, price, private, probably, problem,
process, produce, product, production, professional, professor, program,
project, property, protect, prove, provide, public, pull, purpose, push, put,
quality, question, quickly, quite, race, radio, raise, range, rate, rather,
reach, read, ready, real, reality, realize, really, reason, receive, recent,
recently, recognize, record, red, reduce, reflect, region, relate, relationship,
religious, remain, remember, remove, report, represent, Republican, require,
research, resource, respond, response, responsibility, rest, result, return,
reveal, rich, right, rise, risk, road, rock, role, room, rule, run, safe, same,
save, say, scene, school, science, scientist, score, sea, season, seat, second,
section, security, see, seek, seem, sell, send, senior, sense, series, serious,
serve, service, set, seven, several, sex, sexual, shake, share, she, shoot,
short, shot, should, shoulder, show, side, sign, significant, similar, simple,
simply, since, sing, single, sister, sit, site, situation, six, size, skill,
skin, small, smile, so, social, society, soldier, some, somebody, someone,
something, sometimes, son, song, soon, sort, sound, source, south, southern,
space, speak, special, specific, speech, spend, sport, spring, staff, stage,
stand, standard, star, start, state, statement, station, stay, step, still,
stock, stop, store, story, strategy, street, strong, structure, student, study,
stuff, style, subject, success, successful, such, suddenly, suffer, suggest,
summer, support, sure, surface, system, table, take, talk, task, tax, teach,
teacher, team, technology, television, tell, ten, tend, term, test, than, thank,
that, the, their, them, themselves, then, theory, there, these, they, thing,
think, third, this, those, though, thought, thousand, threat, three, through,
throughout, throw, thus, time, to, today, together, tonight, too, top, total,
tough, toward, town, trade, traditional, training, travel, treat, treatment,
tree, trial, trip, trouble, true, truth, try, turn, TV, two, type, under,
understand, unit, until, up, upon, us, use, usually, value, various, very,
victim, view, violence, visit, voice, vote, wait, walk, wall, want, war, watch,
water, way, we, weapon, wear, week, weight, well, west, western, what, whatever,
when, where, whether, which, while, white, who, whole, whom, whose, why, wide,
wife, will, win, wind, window, wish, with, within, without, woman, wonder, word,
work, worker, world, worry, would, write, writer, wrong, yard, yeah, year, yes,
yet, you, young, your, yourself"#
.split(',')
.map(|word| word.trim())
.collect();
let mut word_pairs: Vec<_> = Vec::new();
for word in &words {
for other_word in &words {
word_pairs.push(word.to_string() + " " + other_word);
}
}
assert_eq!(1_000_000, word_pairs.len());
word_pairs
}
#[allow(unused)] // False positive
fn test_hash_common_words<B: BuildHasher>(build_hasher: &B) {
let word_pairs: Vec<_> = gen_word_pairs();
check_for_collisions(build_hasher, &word_pairs, 32);
}
#[allow(unused)] // False positive
fn check_for_collisions<H: Hash, B: BuildHasher>(build_hasher: &B, items: &[H], bucket_count: usize) {
let mut buckets = vec![0; bucket_count];
for item in items {
let value = hash(item, build_hasher) as usize;
buckets[value % bucket_count] += 1;
}
let mean = items.len() / bucket_count;
let max = *buckets.iter().max().unwrap();
let min = *buckets.iter().min().unwrap();
assert!(
(min as f64) > (mean as f64) * 0.95,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
assert!(
(max as f64) < (mean as f64) * 1.05,
"min: {}, max:{}, {:?}",
min,
max,
buckets
);
}
#[cfg(specialize)]
#[allow(unused)] // False positive
fn hash<H: Hash, B: BuildHasher>(b: &H, build_hasher: &B) -> u64 {
build_hasher.hash_one(b)
}
#[cfg(not(specialize))]
#[allow(unused)] // False positive
fn hash<H: Hash, B: BuildHasher>(b: &H, build_hasher: &B) -> u64 {
let mut hasher = build_hasher.build_hasher();
b.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_bucket_distribution() {
let build_hasher = RandomState::with_seeds(1, 2, 3, 4);
test_hash_common_words(&build_hasher);
let sequence: Vec<_> = (0..320000).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000).collect();
check_for_collisions(&build_hasher, &sequence, 256);
let sequence: Vec<_> = (0..320000).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 32);
let sequence: Vec<_> = (0..2560000_u64).map(|i| i * 1024).collect();
check_for_collisions(&build_hasher, &sequence, 256);
}
#[cfg(feature = "std")]
#[test]
fn test_ahash_alias_map_construction() {
let mut map = ahash::HashMap::default();
map.insert(1, "test");
use ahash::HashMapExt;
let mut map = ahash::HashMap::with_capacity(1234);
map.insert(1, "test");
}
#[cfg(feature = "std")]
#[test]
fn test_ahash_alias_set_construction() {
let mut set = ahash::HashSet::default();
set.insert(1);
use ahash::HashSetExt;
let mut set = ahash::HashSet::with_capacity(1235);
set.insert(1);
}
#[cfg(feature = "std")]
#[test]
fn test_key_ref() {
let mut map = ahash::HashMap::default();
map.insert(1, "test");
assert_eq!(Some((1, "test")), map.remove_entry(&1));
let mut map = ahash::HashMap::default();
map.insert(&1, "test");
assert_eq!(Some((&1, "test")), map.remove_entry(&&1));
let mut m = ahash::HashSet::<Box<String>>::default();
m.insert(Box::from("hello".to_string()));
assert!(m.contains(&"hello".to_string()));
let mut m = ahash::HashSet::<String>::default();
m.insert("hello".to_string());
assert!(m.contains("hello"));
let mut m = ahash::HashSet::<Box<[u8]>>::default();
m.insert(Box::from(&b"hello"[..]));
assert!(m.contains(&b"hello"[..]));
}
#[cfg(feature = "std")]
#[test]
fn test_byte_dist() {
use rand::{SeedableRng, Rng, RngCore};
use pcg_mwc::Mwc256XXA64;
let mut r = Mwc256XXA64::seed_from_u64(0xe786_c22b_119c_1479);
let mut lowest = 2.541;
let mut highest = 2.541;
for _round in 0..100 {
let mut table: [bool; 256 * 8] = [false; 256 * 8];
let hasher = RandomState::with_seeds(r.gen(), r.gen(), r.gen(), r.gen());
for i in 0..128 {
let mut keys: [u8; 8] = hasher.hash_one((i as u64) << 30).to_ne_bytes();
//let mut keys = r.next_u64().to_ne_bytes(); //This is a control to test assert sensitivity.
for idx in 0..8 {
while table[idx * 256 + keys[idx] as usize] {
keys[idx] = keys[idx].wrapping_add(1);
}
table[idx * 256 + keys[idx] as usize] = true;
}
}
for idx in 0..8 {
let mut len = 0;
let mut total_len = 0;
let mut num_seq = 0;
for i in 0..256 {
if table[idx * 256 + i] {
len += 1;
} else if len != 0 {
num_seq += 1;
total_len += len;
len = 0;
}
}
let mean = total_len as f32 / num_seq as f32;
println!("Mean sequence length = {}", mean);
if mean > highest {
highest = mean;
}
if mean < lowest {
lowest = mean;
}
}
}
assert!(lowest > 1.9, "Lowest = {}", lowest);
assert!(highest < 3.9, "Highest = {}", highest);
}
fn ahash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = RandomState::with_seeds(12, 34, 56, 78).build_hasher();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn fxhash_vec<H: Hash>(b: &Vec<H>) -> u64 {
let mut total: u64 = 0;
for item in b {
let mut hasher = FxHasher::default();
item.hash(&mut hasher);
total = total.wrapping_add(hasher.finish());
}
total
}
fn bench_ahash_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("aes_words", |b| b.iter(|| black_box(ahash_vec(&words))));
}
fn bench_fx_words(c: &mut Criterion) {
let words = gen_word_pairs();
c.bench_function("fx_words", |b| b.iter(|| black_box(fxhash_vec(&words))));
}
criterion_main!(benches);
criterion_group!(benches, bench_ahash_words, bench_fx_words,);
+81
View File
@@ -0,0 +1,81 @@
use ahash::{AHasher, RandomState};
use std::hash::{BuildHasher, Hash, Hasher};
#[macro_use]
extern crate no_panic;
#[inline(never)]
#[no_panic]
fn hash_test_final(num: i32, string: &str) -> (u64, u64) {
use core::hash::Hasher;
let mut hasher1 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
let mut hasher2 = RandomState::with_seeds(3, 4, 5, 6).build_hasher();
hasher1.write_i32(num);
hasher2.write(string.as_bytes());
(hasher1.finish(), hasher2.finish())
}
#[inline(never)]
fn hash_test_final_wrapper(num: i32, string: &str) {
hash_test_final(num, string);
}
struct SimpleBuildHasher {
hasher: AHasher,
}
impl SimpleBuildHasher {
fn hash_one<T: Hash>(&self, x: T) -> u64
where
Self: Sized,
{
let mut hasher = self.build_hasher();
x.hash(&mut hasher);
hasher.finish()
}
}
impl BuildHasher for SimpleBuildHasher {
type Hasher = AHasher;
fn build_hasher(&self) -> Self::Hasher {
self.hasher.clone()
}
}
#[inline(never)]
#[no_panic]
fn hash_test_specialize(num: i32, string: &str) -> (u64, u64) {
let hasher1 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
let hasher2 = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
(
SimpleBuildHasher { hasher: hasher1 }.hash_one(num),
SimpleBuildHasher { hasher: hasher2 }.hash_one(string.as_bytes()),
)
}
#[inline(never)]
fn hash_test_random_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[inline(never)]
#[no_panic]
fn hash_test_random(num: i32, string: &str) -> (u64, u64) {
let build_hasher1 = RandomState::with_seeds(1, 2, 3, 4);
let build_hasher2 = RandomState::with_seeds(1, 2, 3, 4);
(build_hasher1.hash_one(&num), build_hasher2.hash_one(string.as_bytes()))
}
#[inline(never)]
fn hash_test_specialize_wrapper(num: i32, string: &str) {
hash_test_specialize(num, string);
}
#[test]
fn test_no_panic() {
hash_test_final_wrapper(2, "Foo");
hash_test_specialize_wrapper(2, "Bar");
hash_test_random(2, "Baz");
hash_test_random_wrapper(2, "Bat");
}
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"ace303bf25937bf488919e5461f3994d1b88a19a40465ec8342c63af89cbebf7",".github/FUNDING.yml":"0c65f392d32a8639ba7986bbb42ca124505b462122382f314c89d84c95dd27f1",".github/workflows/ci.yml":"0605d9327a4633916dc789008d5686c692656bb3e1ee57f821f8537e9ad7d7b4",".vim/coc-settings.json":"8237c8f41db352b0d83f1bb10a60bc2f60f56f3234afbf696b4075c8d4d62d9b","COPYING":"01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f","Cargo.lock":"395d3e76f284190cef50c807ab2f00b9a5d388fde7a7bf88b73b02ed9fd346d1","Cargo.toml":"9384d7c725c5c2ebc8adc602081e7cbce8b214693e9e27edef1c40f33e925810","Cargo.toml.orig":"05304eb8b8821d48c0c4d2e991b9ed0f1a0b68cb70afb8881b81c4c317969663","DESIGN.md":"59c960e1b73b1d7fb41e4df6c0c1b1fcf44dd2ebc8a349597a7d0595f8cb5130","LICENSE-MIT":"0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f","README.md":"afc4d559a98cf190029af0bf320fc0022725e349cd2a303aac860254e28f3c53","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","rustfmt.toml":"1ca600239a27401c4a43f363cf3f38183a212affc1f31bff3ae93234bbaec228","src/ahocorasick.rs":"c699c07df70be45c666e128509ad571a7649d2073e4ae16ac1efd6793c9c6890","src/automaton.rs":"22258a3e118672413119f8f543a9b912cce954e63524575c0ebfdf9011f9c2dd","src/dfa.rs":"197075923eb9d760a552f4e8652310fd4f657736613a9b1444ae05ef5d525da3","src/lib.rs":"66dea84d227f269b2f14ecc8109a97e96245b56c22eef0e8ce03b2343b8d6e66","src/macros.rs":"c6c52ae05b24433cffaca7b78b3645d797862c5d5feffddf9f54909095ed6e05","src/nfa/contiguous.rs":"f435c131ce84927e5600109722d006533ea21442dddaf18e03286d8caed82389","src/nfa/mod.rs":"ee7b3109774d14bbad5239c16bb980dd6b8185ec136d94fbaf2f0dc27d5ffa15","src/nfa/noncontiguous.rs":"de94f02b04efd8744fb096759a8897c22012b0e0ca3ace161fd87c71befefe04","src/packed/api.rs":"2197077ff7d7c731ae03a72bed0ae52d89fee56c5564be076313c9a573ce5013","src/packed/ext.rs":"66be06fde8558429da23a290584d4b9fae665bf64c2578db4fe5f5f3ee864869","src/packed/mod.rs":"0020cd6f07ba5c8955923a9516d7f758864260eda53a6b6f629131c45ddeec62","src/packed/pattern.rs":"0e4bca57d4b941495d31fc8246ad32904eed0cd89e3cda732ad35f4deeba3bef","src/packed/rabinkarp.rs":"403146eb1d838a84601d171393542340513cd1ee7ff750f2372161dd47746586","src/packed/teddy/README.md":"3a43194b64e221543d885176aba3beb1224a927385a20eca842daf6b0ea2f342","src/packed/teddy/builder.rs":"08ec116a4a842a2bb1221d296a2515ef3672c54906bed588fb733364c07855d3","src/packed/teddy/generic.rs":"ea252ab05b32cea7dd9d71e332071d243db7dd0362e049252a27e5881ba2bf39","src/packed/teddy/mod.rs":"17d741f7e2fb9dbac5ba7d1bd4542cf1e35e9f146ace728e23fe6bbed20028b2","src/packed/tests.rs":"8e2f56eb3890ed3876ecb47d3121996e416563127b6430110d7b516df3f83b4b","src/packed/vector.rs":"70c325cfa6f7c5c4c9a6af7b133b75a29e65990a7fe0b9a4c4ce3c3d5a0fe587","src/tests.rs":"c68192ab97b6161d0d6ee96fefd80cc7d14e4486ddcd8d1f82b5c92432c24ed5","src/transducer.rs":"02daa33a5d6dac41dcfd67f51df7c0d4a91c5131c781fb54c4de3520c585a6e1","src/util/alphabet.rs":"6dc22658a38deddc0279892035b18870d4585069e35ba7c7e649a24509acfbcc","src/util/buffer.rs":"f9e37f662c46c6ecd734458dedbe76c3bb0e84a93b6b0117c0d4ad3042413891","src/util/byte_frequencies.rs":"2fb85b381c038c1e44ce94294531cdcd339dca48b1e61f41455666e802cbbc9e","src/util/debug.rs":"ab301ad59aa912529cb97233a54a05914dd3cb2ec43e6fec7334170b97ac5998","src/util/error.rs":"ecccd60e7406305023efcc6adcc826eeeb083ab8f7fbfe3d97469438cd4c4e5c","src/util/int.rs":"e264e6abebf5622b59f6500210773db36048371c4e509c930263334095959a52","src/util/mod.rs":"7ab28d11323ecdbd982087f32eb8bceeee84f1a2583f3aae27039c36d58cf12c","src/util/prefilter.rs":"183e32aa9951d9957f89062e4a6ae7235df7060722a3c91995a3d36db5a98111","src/util/primitives.rs":"f89f3fa1d8db4e37de9ca767c6d05e346404837cade6d063bba68972fafa610b","src/util/remapper.rs":"9f12d911583a325c11806eeceb46d0dfec863cfcfa241aed84d31af73da746e5","src/util/search.rs":"6af803e08b8b8c8a33db100623f1621b0d741616524ce40893d8316897f27ffe","src/util/special.rs":"7d2f9cb9dd9771f59816e829b2d96b1239996f32939ba98764e121696c52b146"},"package":"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"}
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "17f8b32e3b7c845ef3c5429b823804f552f14ec9"
},
"path_in_vcs": ""
}
+1
View File
@@ -0,0 +1 @@
github: [BurntSushi]
@@ -0,0 +1,148 @@
name: ci
on:
pull_request:
push:
branches:
- master
schedule:
- cron: '00 01 * * *'
# The section is needed to drop write-all permissions that are granted on
# `schedule` event. By specifying any permission explicitly all others are set
# to none. By using the principle of least privilege the damage a compromised
# workflow can do (because of an injection or compromised third party tool or
# action) is restricted. Currently the worklow doesn't need any additional
# permission except for pulling the code. Adding labels to issues, commenting
# on pull-requests, etc. may need additional permissions:
#
# Syntax for this section:
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
#
# Reference for how to assign permissions on a job-by-job basis:
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
#
# Reference for available permissions that we can enable if needed:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
permissions:
# to fetch code (actions/checkout)
contents: read
jobs:
test:
name: test
env:
# For some builds, we use cross to test on 32-bit and big-endian
# systems.
CARGO: cargo
# When CARGO is set to CROSS, TARGET is set to `--target matrix.target`.
# Note that we only use cross on Linux, so setting a target on a
# different OS will just use normal cargo.
TARGET:
# Bump this as appropriate. We pin to a version to make sure CI
# continues to work as cross releases in the past have broken things
# in subtle ways.
CROSS_VERSION: v0.2.5
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- build: pinned
os: ubuntu-latest
rust: 1.60.0
- build: stable
os: ubuntu-latest
rust: stable
- build: stable-x86
os: ubuntu-latest
rust: stable
target: i686-unknown-linux-gnu
- build: stable-aarch64
os: ubuntu-latest
rust: stable
target: aarch64-unknown-linux-gnu
- build: stable-powerpc64
os: ubuntu-latest
rust: stable
target: powerpc64-unknown-linux-gnu
- build: stable-s390x
os: ubuntu-latest
rust: stable
target: s390x-unknown-linux-gnu
- build: beta
os: ubuntu-latest
rust: beta
- build: nightly
os: ubuntu-latest
rust: nightly
- build: macos
os: macos-latest
rust: stable
- build: win-msvc
os: windows-latest
rust: stable
- build: win-gnu
os: windows-latest
rust: stable-x86_64-gnu
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Install and configure Cross
if: matrix.os == 'ubuntu-latest' && matrix.target != ''
run: |
# In the past, new releases of 'cross' have broken CI. So for now, we
# pin it. We also use their pre-compiled binary releases because cross
# has over 100 dependencies and takes a bit to compile.
dir="$RUNNER_TEMP/cross-download"
mkdir "$dir"
echo "$dir" >> $GITHUB_PATH
cd "$dir"
curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
tar xf cross-x86_64-unknown-linux-musl.tar.gz
# We used to install 'cross' from master, but it kept failing. So now
# we build from a known-good version until 'cross' becomes more stable
# or we find an alternative. Notably, between v0.2.1 and current
# master (2022-06-14), the number of Cross's dependencies has doubled.
echo "CARGO=cross" >> $GITHUB_ENV
echo "TARGET=--target ${{ matrix.target }}" >> $GITHUB_ENV
- name: Show command used for Cargo
run: |
echo "cargo command is: ${{ env.CARGO }}"
echo "target flag is: ${{ env.TARGET }}"
- name: Show CPU info for debugging
if: matrix.os == 'ubuntu-latest'
run: lscpu
# See: https://github.com/rust-lang/regex/blob/a2887636930156023172e4b376a6febad4e49120/.github/workflows/ci.yml#L145-L163
- name: Pin memchr to 2.6.2
if: matrix.build == 'pinned'
run: cargo update -p memchr --precise 2.6.2
- run: ${{ env.CARGO }} build --verbose $TARGET
- run: ${{ env.CARGO }} doc --verbose $TARGET
- run: ${{ env.CARGO }} test --verbose $TARGET
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std,perf-literal $TARGET
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features $TARGET
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std $TARGET
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features perf-literal $TARGET
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std,perf-literal,logging $TARGET
- if: matrix.build == 'nightly'
run: ${{ env.CARGO }} build --manifest-path aho-corasick-debug/Cargo.toml $TARGET
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- name: Check formatting
run: |
cargo fmt --all -- --check
@@ -0,0 +1,12 @@
{
"rust-analyzer.linkedProjects": [
"aho-corasick-debug/Cargo.toml",
"benchmarks/engines/rust-aho-corasick/Cargo.toml",
"benchmarks/engines/rust-daachorse/Cargo.toml",
"benchmarks/engines/rust-jetscii/Cargo.toml",
"benchmarks/engines/naive/Cargo.toml",
"benchmarks/shared/Cargo.toml",
"fuzz/Cargo.toml",
"Cargo.toml"
]
}
+3
View File
@@ -0,0 +1,3 @@
This project is dual-licensed under the Unlicense and MIT licenses.
You may use this code under the terms of either license.
+39
View File
@@ -0,0 +1,39 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.4"
dependencies = [
"doc-comment",
"log",
"memchr",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+80
View File
@@ -0,0 +1,80 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.60.0"
name = "aho-corasick"
version = "1.1.4"
authors = ["Andrew Gallant <jamslam@gmail.com>"]
build = false
exclude = [
"/aho-corasick-debug",
"/benchmarks",
"/tmp",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Fast multiple substring searching."
homepage = "https://github.com/BurntSushi/aho-corasick"
readme = "README.md"
keywords = [
"string",
"search",
"text",
"pattern",
"multi",
]
categories = ["text-processing"]
license = "Unlicense OR MIT"
repository = "https://github.com/BurntSushi/aho-corasick"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
"--generate-link-to-definition",
]
[features]
default = [
"std",
"perf-literal",
]
logging = ["dep:log"]
perf-literal = ["dep:memchr"]
std = ["memchr?/std"]
[lib]
name = "aho_corasick"
path = "src/lib.rs"
[dependencies.log]
version = "0.4.17"
optional = true
[dependencies.memchr]
version = "2.4.0"
optional = true
default-features = false
[dev-dependencies.doc-comment]
version = "0.3.3"
[profile.bench]
debug = 2
[profile.release]
debug = 2
+74
View File
@@ -0,0 +1,74 @@
[package]
name = "aho-corasick"
version = "1.1.4" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = "Fast multiple substring searching."
homepage = "https://github.com/BurntSushi/aho-corasick"
repository = "https://github.com/BurntSushi/aho-corasick"
readme = "README.md"
keywords = ["string", "search", "text", "pattern", "multi"]
license = "Unlicense OR MIT"
categories = ["text-processing"]
autotests = false
exclude = ["/aho-corasick-debug", "/benchmarks", "/tmp"]
edition = "2021"
rust-version = "1.60.0"
[lib]
name = "aho_corasick"
[features]
default = ["std", "perf-literal"]
std = ["memchr?/std"]
# Enables prefilter optimizations that depend on external crates.
perf-literal = ["dep:memchr"]
# Enable logging via the 'log' crate. This is useful for seeing messages about
# internal decisions and metrics. For example, how the choice of the internal
# Aho-Corasick implementation is used or the heap usage of an automaton.
logging = ["dep:log"]
# Provides a trait impl for fst::Automaton for nfa::noncontiguous::NFA,
# nfa::contiguous::NFA and dfa::DFA. This is useful for searching an
# FST with an Aho-Corasick automaton. Note that this does not apply
# to the top-level 'AhoCorasick' type, as it does not implement the
# aho_corasick::automaton::Automaton trait, and thus enabling this feature does
# not cause it to implement fst::Automaton either.
#
# NOTE: Currently this feature is not available as `fst` is not at 1.0 yet,
# and this would make `fst` a public dependency. If you absolutely need this,
# you can copy the (very small) src/transducer.rs file to your tree. It
# specifically does not use any private APIs and should work after replacing
# 'crate::' with 'aho_corasick::'.
#
# NOTE: I think my current plan is to flip this around an add an optional
# dependency on 'aho-corasick' to the 'fst' crate and move the trait impls
# there. But I haven't gotten around to it yet.
# transducer = ["fst"]
[dependencies]
log = { version = "0.4.17", optional = true }
memchr = { version = "2.4.0", default-features = false, optional = true }
[dev-dependencies]
doc-comment = "0.3.3"
# fst = "0.4.5"
[package.metadata.docs.rs]
# We want to document all features.
all-features = true
# This opts into a nightly unstable option to show the features that need to be
# enabled for public API items. To do that, we set 'docsrs', and when that's
# enabled, we enable the 'doc_cfg' feature.
#
# To test this locally, run:
#
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
[profile.release]
debug = true
[profile.bench]
debug = true
+481
View File
@@ -0,0 +1,481 @@
This document describes the internal design of this crate, which is an object
lesson in what happens when you take a fairly simple old algorithm like
Aho-Corasick and make it fast and production ready.
The target audience of this document is Rust programmers that have some
familiarity with string searching, however, one does not need to know the
Aho-Corasick algorithm in order to read this (it is explained below). One
should, however, know what a trie is. (If you don't, go read its Wikipedia
article.)
The center-piece of this crate is an implementation of Aho-Corasick. On its
own, Aho-Corasick isn't that complicated. The complex pieces come from the
different variants of Aho-Corasick implemented in this crate. Specifically,
they are:
* Aho-Corasick as a noncontiguous NFA. States have their transitions
represented sparsely, and each state puts its transitions in its own separate
allocation. Hence the same "noncontiguous."
* Aho-Corasick as a contiguous NFA. This NFA uses a single allocation to
represent the transitions of all states. That is, transitions are laid out
contiguously in memory. Moreover, states near the starting state are
represented densely, such that finding the next state ID takes a constant
number of instructions.
* Aho-Corasick as a DFA. In this case, all states are represented densely in
a transition table that uses one allocation.
* Supporting "standard" match semantics, along with its overlapping variant,
in addition to leftmost-first and leftmost-longest semantics. The "standard"
semantics are typically what you see in a textbook description of
Aho-Corasick. However, Aho-Corasick is also useful as an optimization in
regex engines, which often use leftmost-first or leftmost-longest semantics.
Thus, it is useful to implement those semantics here. The "standard" and
"leftmost" search algorithms are subtly different, and also require slightly
different construction algorithms.
* Support for ASCII case insensitive matching.
* Support for accelerating searches when the patterns all start with a small
number of fixed bytes. Or alternatively, when the patterns all contain a
small number of rare bytes. (Searching for these bytes uses SIMD vectorized
code courtesy of `memchr`.)
* Transparent support for alternative SIMD vectorized search routines for
smaller number of literals, such as the Teddy algorithm. We called these
"packed" search routines because they use SIMD. They can often be an order of
magnitude faster than just Aho-Corasick, but don't scale as well.
* Support for searching streams. This can reuse most of the underlying code,
but does require careful buffering support.
* Support for anchored searches, which permit efficient "is prefix" checks for
a large number of patterns.
When you combine all of this together along with trying to make everything as
fast as possible, what you end up with is enitrely too much code with too much
`unsafe`. Alas, I was not smart enough to figure out how to reduce it. Instead,
we will explain it.
# Basics
The fundamental problem this crate is trying to solve is to determine the
occurrences of possibly many patterns in a haystack. The naive way to solve
this is to look for a match for each pattern at each position in the haystack:
for i in 0..haystack.len():
for p in patterns.iter():
if haystack[i..].starts_with(p.bytes()):
return Match(p.id(), i, i + p.bytes().len())
Those four lines are effectively all this crate does. The problem with those
four lines is that they are very slow, especially when you're searching for a
large number of patterns.
While there are many different algorithms available to solve this, a popular
one is Aho-Corasick. It's a common solution because it's not too hard to
implement, scales quite well even when searching for thousands of patterns and
is generally pretty fast. Aho-Corasick does well here because, regardless of
the number of patterns you're searching for, it always visits each byte in the
haystack exactly once. This means, generally speaking, adding more patterns to
an Aho-Corasick automaton does not make it slower. (Strictly speaking, however,
this is not true, since a larger automaton will make less effective use of the
CPU's cache.)
Aho-Corasick can be succinctly described as a trie with state transitions
between some of the nodes that efficiently instruct the search algorithm to
try matching alternative keys in the trie. The trick is that these state
transitions are arranged such that each byte of input needs to be inspected
only once. These state transitions are typically called "failure transitions,"
because they instruct the searcher (the thing traversing the automaton while
reading from the haystack) what to do when a byte in the haystack does not
correspond to a valid transition in the current state of the trie.
More formally, a failure transition points to a state in the automaton that may
lead to a match whose prefix is a proper suffix of the path traversed through
the trie so far. (If no such proper suffix exists, then the failure transition
points back to the start state of the trie, effectively restarting the search.)
This is perhaps simpler to explain pictorally. For example, let's say we built
an Aho-Corasick automaton with the following patterns: 'abcd' and 'cef'. The
trie looks like this:
a - S1 - b - S2 - c - S3 - d - S4*
/
S0 - c - S5 - e - S6 - f - S7*
where states marked with a `*` are match states (meaning, the search algorithm
should stop and report a match to the caller).
So given this trie, it should be somewhat straight-forward to see how it can
be used to determine whether any particular haystack *starts* with either
`abcd` or `cef`. It's easy to express this in code:
fn has_prefix(trie: &Trie, haystack: &[u8]) -> bool {
let mut state_id = trie.start();
// If the empty pattern is in trie, then state_id is a match state.
if trie.is_match(state_id) {
return true;
}
for (i, &b) in haystack.iter().enumerate() {
state_id = match trie.next_state(state_id, b) {
Some(id) => id,
// If there was no transition for this state and byte, then we know
// the haystack does not start with one of the patterns in our trie.
None => return false,
};
if trie.is_match(state_id) {
return true;
}
}
false
}
And that's pretty much it. All we do is move through the trie starting with the
bytes at the beginning of the haystack. If we find ourselves in a position
where we can't move, or if we've looked through the entire haystack without
seeing a match state, then we know the haystack does not start with any of the
patterns in the trie.
The meat of the Aho-Corasick algorithm is in how we add failure transitions to
our trie to keep searching efficient. Specifically, it permits us to not only
check whether a haystack *starts* with any one of a number of patterns, but
rather, whether the haystack contains any of a number of patterns *anywhere* in
the haystack.
As mentioned before, failure transitions connect a proper suffix of the path
traversed through the trie before, with a path that leads to a match that has a
prefix corresponding to that proper suffix. So in our case, for patterns `abcd`
and `cef`, with a haystack `abcef`, we want to transition to state `S5` (from
the diagram above) from `S3` upon seeing that the byte following `c` is not
`d`. Namely, the proper suffix in this example is `c`, which is a prefix of
`cef`. So the modified diagram looks like this:
a - S1 - b - S2 - c - S3 - d - S4*
/ /
/ ----------------
/ /
S0 - c - S5 - e - S6 - f - S7*
One thing that isn't shown in this diagram is that *all* states have a failure
transition, but only `S3` has a *non-trivial* failure transition. That is, all
other states have a failure transition back to the start state. So if our
haystack was `abzabcd`, then the searcher would transition back to `S0` after
seeing `z`, which effectively restarts the search. (Because there is no pattern
in our trie that has a prefix of `bz` or `z`.)
The code for traversing this *automaton* or *finite state machine* (it is no
longer just a trie) is not that much different from the `has_prefix` code
above:
fn contains(fsm: &FiniteStateMachine, haystack: &[u8]) -> bool {
let mut state_id = fsm.start();
// If the empty pattern is in fsm, then state_id is a match state.
if fsm.is_match(state_id) {
return true;
}
for (i, &b) in haystack.iter().enumerate() {
// While the diagram above doesn't show this, we may wind up needing
// to follow multiple failure transitions before we land on a state
// in which we can advance. Therefore, when searching for the next
// state, we need to loop until we don't see a failure transition.
//
// This loop terminates because the start state has no empty
// transitions. Every transition from the start state either points to
// another state, or loops back to the start state.
loop {
match fsm.next_state(state_id, b) {
Some(id) => {
state_id = id;
break;
}
// Unlike our code above, if there was no transition for this
// state, then we don't quit. Instead, we look for this state's
// failure transition and follow that instead.
None => {
state_id = fsm.next_fail_state(state_id);
}
};
}
if fsm.is_match(state_id) {
return true;
}
}
false
}
Other than the complication around traversing failure transitions, this code
is still roughly "traverse the automaton with bytes from the haystack, and quit
when a match is seen."
And that concludes our section on the basics. While we didn't go deep into how
the automaton is built (see `src/nfa/noncontiguous.rs`, which has detailed
comments about that), the basic structure of Aho-Corasick should be reasonably
clear.
# NFAs and DFAs
There are generally two types of finite automata: non-deterministic finite
automata (NFA) and deterministic finite automata (DFA). The difference between
them is, principally, that an NFA can be in multiple states at once. This is
typically accomplished by things called _epsilon_ transitions, where one could
move to a new state without consuming any bytes from the input. (The other
mechanism by which NFAs can be in more than one state is where the same byte in
a particular state transitions to multiple distinct states.) In contrast, a DFA
can only ever be in one state at a time. A DFA has no epsilon transitions, and
for any given state, a byte transitions to at most one other state.
By this formulation, the Aho-Corasick automaton described in the previous
section is an NFA. This is because failure transitions are, effectively,
epsilon transitions. That is, whenever the automaton is in state `S`, it is
actually in the set of states that are reachable by recursively following
failure transitions from `S` until you reach the start state. (This means
that, for example, the start state is always active since the start state is
reachable via failure transitions from any state in the automaton.)
NFAs have a lot of nice properties. They tend to be easier to construct, and
also tend to use less memory. However, their primary downside is that they are
typically slower to execute a search with. For example, the code above showing
how to search with an Aho-Corasick automaton needs to potentially iterate
through many failure transitions for every byte of input. While this is a
fairly small amount of overhead, this can add up, especially if the automaton
has a lot of overlapping patterns with a lot of failure transitions.
A DFA's search code, by contrast, looks like this:
fn contains(dfa: &DFA, haystack: &[u8]) -> bool {
let mut state_id = dfa.start();
// If the empty pattern is in dfa, then state_id is a match state.
if dfa.is_match(state_id) {
return true;
}
for (i, &b) in haystack.iter().enumerate() {
// An Aho-Corasick DFA *never* has a missing state that requires
// failure transitions to be followed. One byte of input advances the
// automaton by one state. Always.
state_id = dfa.next_state(state_id, b);
if dfa.is_match(state_id) {
return true;
}
}
false
}
The search logic here is much simpler than for the NFA, and this tends to
translate into significant performance benefits as well, since there's a lot
less work being done for each byte in the haystack. How is this accomplished?
It's done by pre-following all failure transitions for all states for all bytes
in the alphabet, and then building a single state transition table. Building
this DFA can be much more costly than building the NFA, and use much more
memory, but the better performance can be worth it.
Users of this crate can actually choose between using one of two possible NFAs
(noncontiguous or contiguous) or a DFA. By default, a contiguous NFA is used,
in most circumstances, but if the number of patterns is small enough a DFA will
be used. A contiguous NFA is chosen because it uses orders of magnitude less
memory than a DFA, takes only a little longer to build than a noncontiguous
NFA and usually gets pretty close to the search speed of a DFA. (Callers can
override this automatic selection via the `AhoCorasickBuilder::start_kind`
configuration.)
# More DFA tricks
As described in the previous section, one of the downsides of using a DFA
is that it uses more memory and can take longer to build. One small way of
mitigating these concerns is to map the alphabet used by the automaton into
a smaller space. Typically, the alphabet of a DFA has 256 elements in it:
one element for each possible value that fits into a byte. However, in many
cases, one does not need the full alphabet. For example, if all patterns in an
Aho-Corasick automaton are ASCII letters, then this only uses up 52 distinct
bytes. As far as the automaton is concerned, the rest of the 204 bytes are
indistinguishable from one another: they will never disrciminate between a
match or a non-match. Therefore, in cases like that, the alphabet can be shrunk
to just 53 elements. One for each ASCII letter, and then another to serve as a
placeholder for every other unused byte.
In practice, this library doesn't quite compute the optimal set of equivalence
classes, but it's close enough in most cases. The key idea is that this then
allows the transition table for the DFA to be potentially much smaller. The
downside of doing this, however, is that since the transition table is defined
in terms of this smaller alphabet space, every byte in the haystack must be
re-mapped to this smaller space. This requires an additional 256-byte table.
In practice, this can lead to a small search time hit, but it can be difficult
to measure. Moreover, it can sometimes lead to faster search times for bigger
automata, since it could be difference between more parts of the automaton
staying in the CPU cache or not.
One other trick for DFAs employed by this crate is the notion of premultiplying
state identifiers. Specifically, the normal way to compute the next transition
in a DFA is via the following (assuming that the transition table is laid out
sequentially in memory, in row-major order, where the rows are states):
next_state_id = dfa.transitions[current_state_id * 256 + current_byte]
However, since the value `256` is a fixed constant, we can actually premultiply
the state identifiers in the table when we build the table initially. Then, the
next transition computation simply becomes:
next_state_id = dfa.transitions[current_state_id + current_byte]
This doesn't seem like much, but when this is being executed for every byte of
input that you're searching, saving that extra multiplication instruction can
add up.
The same optimization works even when equivalence classes are enabled, as
described above. The only difference is that the premultiplication is by the
total number of equivalence classes instead of 256.
There isn't much downside to premultiplying state identifiers, other than it
imposes a smaller limit on the total number of states in the DFA. Namely, with
premultiplied state identifiers, you run out of room in your state identifier
representation more rapidly than if the identifiers are just state indices.
Both equivalence classes and premultiplication are always enabled. There is a
`AhoCorasickBuilder::byte_classes` configuration, but disabling this just makes
it so there are always 256 equivalence classes, i.e., every class corresponds
to precisely one byte. When it's disabled, the equivalence class map itself is
still used. The purpose of disabling it is when one is debugging the underlying
automaton. It can be easier to comprehend when it uses actual byte values for
its transitions instead of equivalence classes.
# Match semantics
One of the more interesting things about this implementation of Aho-Corasick
that (as far as this author knows) separates it from other implementations, is
that it natively supports leftmost-first and leftmost-longest match semantics.
Briefly, match semantics refer to the decision procedure by which searching
will disambiguate matches when there are multiple to choose from:
* **standard** match semantics emits matches as soon as they are detected by
the automaton. This is typically equivalent to the textbook non-overlapping
formulation of Aho-Corasick.
* **leftmost-first** match semantics means that 1) the next match is the match
starting at the leftmost position and 2) among multiple matches starting at
the same leftmost position, the match corresponding to the pattern provided
first by the caller is reported.
* **leftmost-longest** is like leftmost-first, except when there are multiple
matches starting at the same leftmost position, the pattern corresponding to
the longest match is returned.
(The crate API documentation discusses these differences, with examples, in
more depth on the `MatchKind` type.)
The reason why supporting these match semantics is important is because it
gives the user more control over the match procedure. For example,
leftmost-first permits users to implement match priority by simply putting the
higher priority patterns first. Leftmost-longest, on the other hand, permits
finding the longest possible match, which might be useful when trying to find
words matching a dictionary. Additionally, regex engines often want to use
Aho-Corasick as an optimization when searching for an alternation of literals.
In order to preserve correct match semantics, regex engines typically can't use
the standard textbook definition directly, since regex engines will implement
either leftmost-first (Perl-like) or leftmost-longest (POSIX) match semantics.
Supporting leftmost semantics requires a couple key changes:
* Constructing the Aho-Corasick automaton changes a bit in both how the trie is
constructed and how failure transitions are found. Namely, only a subset
of the failure transitions are added. Specifically, only the failure
transitions that either do not occur after a match or do occur after a match
but preserve that match are kept. (More details on this can be found in
`src/nfa/noncontiguous.rs`.)
* The search algorithm changes slightly. Since we are looking for the leftmost
match, we cannot quit as soon as a match is detected. Instead, after a match
is detected, we must keep searching until either the end of the input or
until a dead state is seen. (Dead states are not used for standard match
semantics. Dead states mean that searching should stop after a match has been
found.)
Most other implementations of Aho-Corasick do support leftmost match semantics,
but they do it with more overhead at search time, or even worse, with a queue
of matches and sophisticated hijinks to disambiguate the matches. While our
construction algorithm becomes a bit more complicated, the correct match
semantics fall out from the structure of the automaton itself.
# Overlapping matches
One of the nice properties of an Aho-Corasick automaton is that it can report
all possible matches, even when they overlap with one another. In this mode,
the match semantics don't matter, since all possible matches are reported.
Overlapping searches work just like regular searches, except the state
identifier at which the previous search left off is carried over to the next
search, so that it can pick up where it left off. If there are additional
matches at that state, then they are reported before resuming the search.
Enabling leftmost-first or leftmost-longest match semantics causes the
automaton to use a subset of all failure transitions, which means that
overlapping searches cannot be used. Therefore, if leftmost match semantics are
used, attempting to do an overlapping search will return an error (or panic
when using the infallible APIs). Thus, to get overlapping searches, the caller
must use the default standard match semantics. This behavior was chosen because
there are only two alternatives, which were deemed worse:
* Compile two automatons internally, one for standard semantics and one for
the semantics requested by the caller (if not standard).
* Create a new type, distinct from the `AhoCorasick` type, which has different
capabilities based on the configuration options.
The first is untenable because of the amount of memory used by the automaton.
The second increases the complexity of the API too much by adding too many
types that do similar things. It is conceptually much simpler to keep all
searching isolated to a single type.
# Stream searching
Since Aho-Corasick is an automaton, it is possible to do partial searches on
partial parts of the haystack, and then resume that search on subsequent pieces
of the haystack. This is useful when the haystack you're trying to search is
not stored contiguously in memory, or if one does not want to read the entire
haystack into memory at once.
Currently, only standard semantics are supported for stream searching. This is
some of the more complicated code in this crate, and is something I would very
much like to improve. In particular, it currently has the restriction that it
must buffer at least enough of the haystack in memory in order to fit the
longest possible match. The difficulty in getting stream searching right is
that the implementation choices (such as the buffer size) often impact what the
API looks like and what it's allowed to do.
# Prefilters
In some cases, Aho-Corasick is not the fastest way to find matches containing
multiple patterns. Sometimes, the search can be accelerated using highly
optimized SIMD routines. For example, consider searching the following
patterns:
Sherlock
Moriarty
Watson
It is plausible that it would be much faster to quickly look for occurrences of
the leading bytes, `S`, `M` or `W`, before trying to start searching via the
automaton. Indeed, this is exactly what this crate will do.
When there are more than three distinct starting bytes, then this crate will
look for three distinct bytes occurring at any position in the patterns, while
preferring bytes that are heuristically determined to be rare over others. For
example:
Abuzz
Sanchez
Vasquez
Topaz
Waltz
Here, we have more than 3 distinct starting bytes, but all of the patterns
contain `z`, which is typically a rare byte. In this case, the prefilter will
scan for `z`, back up a bit, and then execute the Aho-Corasick automaton.
If all of that fails, then a packed multiple substring algorithm will be
attempted. Currently, the only algorithm available for this is Teddy, but more
may be added in the future. Teddy is unlike the above prefilters in that it
confirms its own matches, so when Teddy is active, it might not be necessary
for Aho-Corasick to run at all. However, the current Teddy implementation
only works in `x86_64` when SSSE3 or AVX2 are available or in `aarch64`
(using NEON), and moreover, only works _well_ when there are a small number
of patterns (say, less than 100). Teddy also requires the haystack to be of a
certain length (more than 16-34 bytes). When the haystack is shorter than that,
Rabin-Karp is used instead. (See `src/packed/rabinkarp.rs`.)
There is a more thorough description of Teddy at
[`src/packed/teddy/README.md`](src/packed/teddy/README.md).
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Andrew Gallant
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+174
View File
@@ -0,0 +1,174 @@
aho-corasick
============
A library for finding occurrences of many patterns at once with SIMD
acceleration in some cases. This library provides multiple pattern
search principally through an implementation of the
[Aho-Corasick algorithm](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm),
which builds a finite state machine for executing searches in linear time.
Features include case insensitive matching, overlapping matches, fast searching
via SIMD and optional full DFA construction and search & replace in streams.
[![Build status](https://github.com/BurntSushi/aho-corasick/workflows/ci/badge.svg)](https://github.com/BurntSushi/aho-corasick/actions)
[![crates.io](https://img.shields.io/crates/v/aho-corasick.svg)](https://crates.io/crates/aho-corasick)
Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/).
### Documentation
https://docs.rs/aho-corasick
### Usage
Run `cargo add aho-corasick` to automatically add this crate as a dependency
in your `Cargo.toml` file.
### Example: basic searching
This example shows how to search for occurrences of multiple patterns
simultaneously. Each match includes the pattern that matched along with the
byte offsets of the match.
```rust
use aho_corasick::{AhoCorasick, PatternID};
let patterns = &["apple", "maple", "Snapple"];
let haystack = "Nobody likes maple in their apple flavored Snapple.";
let ac = AhoCorasick::new(patterns).unwrap();
let mut matches = vec![];
for mat in ac.find_iter(haystack) {
matches.push((mat.pattern(), mat.start(), mat.end()));
}
assert_eq!(matches, vec![
(PatternID::must(1), 13, 18),
(PatternID::must(0), 28, 33),
(PatternID::must(2), 43, 50),
]);
```
### Example: ASCII case insensitivity
This is like the previous example, but matches `Snapple` case insensitively
using `AhoCorasickBuilder`:
```rust
use aho_corasick::{AhoCorasick, PatternID};
let patterns = &["apple", "maple", "snapple"];
let haystack = "Nobody likes maple in their apple flavored Snapple.";
let ac = AhoCorasick::builder()
.ascii_case_insensitive(true)
.build(patterns)
.unwrap();
let mut matches = vec![];
for mat in ac.find_iter(haystack) {
matches.push((mat.pattern(), mat.start(), mat.end()));
}
assert_eq!(matches, vec![
(PatternID::must(1), 13, 18),
(PatternID::must(0), 28, 33),
(PatternID::must(2), 43, 50),
]);
```
### Example: replacing matches in a stream
This example shows how to execute a search and replace on a stream without
loading the entire stream into memory first.
```rust,ignore
use aho_corasick::AhoCorasick;
let patterns = &["fox", "brown", "quick"];
let replace_with = &["sloth", "grey", "slow"];
// In a real example, these might be `std::fs::File`s instead. All you need to
// do is supply a pair of `std::io::Read` and `std::io::Write` implementations.
let rdr = "The quick brown fox.";
let mut wtr = vec![];
let ac = AhoCorasick::new(patterns).unwrap();
ac.stream_replace_all(rdr.as_bytes(), &mut wtr, replace_with)
.expect("stream_replace_all failed");
assert_eq!(b"The slow grey sloth.".to_vec(), wtr);
```
### Example: finding the leftmost first match
In the textbook description of Aho-Corasick, its formulation is typically
structured such that it reports all possible matches, even when they overlap
with another. In many cases, overlapping matches may not be desired, such as
the case of finding all successive non-overlapping matches like you might with
a standard regular expression.
Unfortunately the "obvious" way to modify the Aho-Corasick algorithm to do
this doesn't always work in the expected way, since it will report matches as
soon as they are seen. For example, consider matching the regex `Samwise|Sam`
against the text `Samwise`. Most regex engines (that are Perl-like, or
non-POSIX) will report `Samwise` as a match, but the standard Aho-Corasick
algorithm modified for reporting non-overlapping matches will report `Sam`.
A novel contribution of this library is the ability to change the match
semantics of Aho-Corasick (without additional search time overhead) such that
`Samwise` is reported instead. For example, here's the standard approach:
```rust
use aho_corasick::AhoCorasick;
let patterns = &["Samwise", "Sam"];
let haystack = "Samwise";
let ac = AhoCorasick::new(patterns).unwrap();
let mat = ac.find(haystack).expect("should have a match");
assert_eq!("Sam", &haystack[mat.start()..mat.end()]);
```
And now here's the leftmost-first version, which matches how a Perl-like
regex will work:
```rust
use aho_corasick::{AhoCorasick, MatchKind};
let patterns = &["Samwise", "Sam"];
let haystack = "Samwise";
let ac = AhoCorasick::builder()
.match_kind(MatchKind::LeftmostFirst)
.build(patterns)
.unwrap();
let mat = ac.find(haystack).expect("should have a match");
assert_eq!("Samwise", &haystack[mat.start()..mat.end()]);
```
In addition to leftmost-first semantics, this library also supports
leftmost-longest semantics, which match the POSIX behavior of a regular
expression alternation. See `MatchKind` in the docs for more details.
### Minimum Rust version policy
This crate's minimum supported `rustc` version is `1.60.0`.
The current policy is that the minimum Rust version required to use this crate
can be increased in minor version updates. For example, if `crate 1.0` requires
Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust
1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum
version of Rust.
In general, this crate will be conservative with respect to the minimum
supported version of Rust.
### FFI bindings
* [G-Research/ahocorasick_rs](https://github.com/G-Research/ahocorasick_rs/)
is a Python wrapper for this library.
* [tmikus/ahocorasick_rs](https://github.com/tmikus/ahocorasick_rs) is a Go
wrapper for this library.
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
+2
View File
@@ -0,0 +1,2 @@
max_width = 79
use_small_heuristics = "max"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+835
View File
@@ -0,0 +1,835 @@
/*!
Provides direct access to a DFA implementation of Aho-Corasick.
This is a low-level API that generally only needs to be used in niche
circumstances. When possible, prefer using [`AhoCorasick`](crate::AhoCorasick)
instead of a DFA directly. Using an `DFA` directly is typically only necessary
when one needs access to the [`Automaton`] trait implementation.
*/
use alloc::{vec, vec::Vec};
use crate::{
automaton::Automaton,
nfa::noncontiguous,
util::{
alphabet::ByteClasses,
error::{BuildError, MatchError},
int::{Usize, U32},
prefilter::Prefilter,
primitives::{IteratorIndexExt, PatternID, SmallIndex, StateID},
search::{Anchored, MatchKind, StartKind},
special::Special,
},
};
/// A DFA implementation of Aho-Corasick.
///
/// When possible, prefer using [`AhoCorasick`](crate::AhoCorasick) instead of
/// this type directly. Using a `DFA` directly is typically only necessary when
/// one needs access to the [`Automaton`] trait implementation.
///
/// This DFA can only be built by first constructing a [`noncontiguous::NFA`].
/// Both [`DFA::new`] and [`Builder::build`] do this for you automatically, but
/// [`Builder::build_from_noncontiguous`] permits doing it explicitly.
///
/// A DFA provides the best possible search performance (in this crate) via two
/// mechanisms:
///
/// * All states use a dense representation for their transitions.
/// * All failure transitions are pre-computed such that they are never
/// explicitly handled at search time.
///
/// These two facts combined mean that every state transition is performed
/// using a constant number of instructions. However, this comes at
/// great cost. The memory usage of a DFA can be quite exorbitant.
/// It is potentially multiple orders of magnitude greater than a
/// [`contiguous::NFA`](crate::nfa::contiguous::NFA) for example. In exchange,
/// a DFA will typically have better search speed than a `contiguous::NFA`, but
/// not by orders of magnitude.
///
/// Unless you have a small number of patterns or memory usage is not a concern
/// and search performance is critical, a DFA is usually not the best choice.
///
/// Moreover, unlike the NFAs in this crate, it is costly for a DFA to
/// support for anchored and unanchored search configurations. Namely,
/// since failure transitions are pre-computed, supporting both anchored
/// and unanchored searches requires a duplication of the transition table,
/// making the memory usage of such a DFA ever bigger. (The NFAs in this crate
/// unconditionally support both anchored and unanchored searches because there
/// is essentially no added cost for doing so.) It is for this reason that
/// a DFA's support for anchored and unanchored searches can be configured
/// via [`Builder::start_kind`]. By default, a DFA only supports unanchored
/// searches.
///
/// # Example
///
/// This example shows how to build an `DFA` directly and use it to execute
/// [`Automaton::try_find`]:
///
/// ```
/// use aho_corasick::{
/// automaton::Automaton,
/// dfa::DFA,
/// Input, Match,
/// };
///
/// let patterns = &["b", "abc", "abcd"];
/// let haystack = "abcd";
///
/// let nfa = DFA::new(patterns).unwrap();
/// assert_eq!(
/// Some(Match::must(0, 1..2)),
/// nfa.try_find(&Input::new(haystack))?,
/// );
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// It is also possible to implement your own version of `try_find`. See the
/// [`Automaton`] documentation for an example.
#[derive(Clone)]
pub struct DFA {
/// The DFA transition table. IDs in this table are pre-multiplied. So
/// instead of the IDs being 0, 1, 2, 3, ..., they are 0*stride, 1*stride,
/// 2*stride, 3*stride, ...
trans: Vec<StateID>,
/// The matches for every match state in this DFA. This is first indexed by
/// state index (so that's `sid >> stride2`) and then by order in which the
/// matches are meant to occur.
matches: Vec<Vec<PatternID>>,
/// The amount of heap memory used, in bytes, by the inner Vecs of
/// 'matches'.
matches_memory_usage: usize,
/// The length of each pattern. This is used to compute the start offset
/// of a match.
pattern_lens: Vec<SmallIndex>,
/// A prefilter for accelerating searches, if one exists.
prefilter: Option<Prefilter>,
/// The match semantics built into this DFA.
match_kind: MatchKind,
/// The total number of states in this DFA.
state_len: usize,
/// The alphabet size, or total number of equivalence classes, for this
/// DFA. Note that the actual number of transitions in each state is
/// stride=2^stride2, where stride is the smallest power of 2 greater than
/// or equal to alphabet_len. We do things this way so that we can use
/// bitshifting to go from a state ID to an index into 'matches'.
alphabet_len: usize,
/// The exponent with a base 2, such that stride=2^stride2. Given a state
/// index 'i', its state identifier is 'i << stride2'. Given a state
/// identifier 'sid', its state index is 'sid >> stride2'.
stride2: usize,
/// The equivalence classes for this DFA. All transitions are defined on
/// equivalence classes and not on the 256 distinct byte values.
byte_classes: ByteClasses,
/// The length of the shortest pattern in this automaton.
min_pattern_len: usize,
/// The length of the longest pattern in this automaton.
max_pattern_len: usize,
/// The information required to deduce which states are "special" in this
/// DFA.
special: Special,
}
impl DFA {
/// Create a new Aho-Corasick DFA using the default configuration.
///
/// Use a [`Builder`] if you want to change the configuration.
pub fn new<I, P>(patterns: I) -> Result<DFA, BuildError>
where
I: IntoIterator<Item = P>,
P: AsRef<[u8]>,
{
DFA::builder().build(patterns)
}
/// A convenience method for returning a new Aho-Corasick DFA builder.
///
/// This usually permits one to just import the `DFA` type.
pub fn builder() -> Builder {
Builder::new()
}
}
impl DFA {
/// A sentinel state ID indicating that a search should stop once it has
/// entered this state. When a search stops, it returns a match if one has
/// been found, otherwise no match. A DFA always has an actual dead state
/// at this ID.
///
/// N.B. DFAs, unlike NFAs, do not have any notion of a FAIL state.
/// Namely, the whole point of a DFA is that the FAIL state is completely
/// compiled away. That is, DFA construction involves pre-computing the
/// failure transitions everywhere, such that failure transitions are no
/// longer used at search time. This, combined with its uniformly dense
/// representation, are the two most important factors in why it's faster
/// than the NFAs in this crate.
const DEAD: StateID = StateID::new_unchecked(0);
/// Adds the given pattern IDs as matches to the given state and also
/// records the added memory usage.
fn set_matches(
&mut self,
sid: StateID,
pids: impl Iterator<Item = PatternID>,
) {
let index = (sid.as_usize() >> self.stride2).checked_sub(2).unwrap();
let mut at_least_one = false;
for pid in pids {
self.matches[index].push(pid);
self.matches_memory_usage += PatternID::SIZE;
at_least_one = true;
}
assert!(at_least_one, "match state must have non-empty pids");
}
}
// SAFETY: 'start_state' always returns a valid state ID, 'next_state' always
// returns a valid state ID given a valid state ID. We otherwise claim that
// all other methods are correct as well.
unsafe impl Automaton for DFA {
#[inline(always)]
fn start_state(&self, anchored: Anchored) -> Result<StateID, MatchError> {
// Either of the start state IDs can be DEAD, in which case, support
// for that type of search is not provided by this DFA. Which start
// state IDs are inactive depends on the 'StartKind' configuration at
// DFA construction time.
match anchored {
Anchored::No => {
let start = self.special.start_unanchored_id;
if start == DFA::DEAD {
Err(MatchError::invalid_input_unanchored())
} else {
Ok(start)
}
}
Anchored::Yes => {
let start = self.special.start_anchored_id;
if start == DFA::DEAD {
Err(MatchError::invalid_input_anchored())
} else {
Ok(start)
}
}
}
}
#[inline(always)]
fn next_state(
&self,
_anchored: Anchored,
sid: StateID,
byte: u8,
) -> StateID {
let class = self.byte_classes.get(byte);
self.trans[(sid.as_u32() + u32::from(class)).as_usize()]
}
#[inline(always)]
fn is_special(&self, sid: StateID) -> bool {
sid <= self.special.max_special_id
}
#[inline(always)]
fn is_dead(&self, sid: StateID) -> bool {
sid == DFA::DEAD
}
#[inline(always)]
fn is_match(&self, sid: StateID) -> bool {
!self.is_dead(sid) && sid <= self.special.max_match_id
}
#[inline(always)]
fn is_start(&self, sid: StateID) -> bool {
sid == self.special.start_unanchored_id
|| sid == self.special.start_anchored_id
}
#[inline(always)]
fn match_kind(&self) -> MatchKind {
self.match_kind
}
#[inline(always)]
fn patterns_len(&self) -> usize {
self.pattern_lens.len()
}
#[inline(always)]
fn pattern_len(&self, pid: PatternID) -> usize {
self.pattern_lens[pid].as_usize()
}
#[inline(always)]
fn min_pattern_len(&self) -> usize {
self.min_pattern_len
}
#[inline(always)]
fn max_pattern_len(&self) -> usize {
self.max_pattern_len
}
#[inline(always)]
fn match_len(&self, sid: StateID) -> usize {
debug_assert!(self.is_match(sid));
let offset = (sid.as_usize() >> self.stride2) - 2;
self.matches[offset].len()
}
#[inline(always)]
fn match_pattern(&self, sid: StateID, index: usize) -> PatternID {
debug_assert!(self.is_match(sid));
let offset = (sid.as_usize() >> self.stride2) - 2;
self.matches[offset][index]
}
#[inline(always)]
fn memory_usage(&self) -> usize {
use core::mem::size_of;
(self.trans.len() * size_of::<u32>())
+ (self.matches.len() * size_of::<Vec<PatternID>>())
+ self.matches_memory_usage
+ (self.pattern_lens.len() * size_of::<SmallIndex>())
+ self.prefilter.as_ref().map_or(0, |p| p.memory_usage())
}
#[inline(always)]
fn prefilter(&self) -> Option<&Prefilter> {
self.prefilter.as_ref()
}
}
impl core::fmt::Debug for DFA {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::{
automaton::{fmt_state_indicator, sparse_transitions},
util::debug::DebugByte,
};
writeln!(f, "dfa::DFA(")?;
for index in 0..self.state_len {
let sid = StateID::new_unchecked(index << self.stride2);
// While we do currently include the FAIL state in the transition
// table (to simplify construction), it is never actually used. It
// poses problems with the code below because it gets treated as
// a match state incidentally when it is, of course, not. So we
// special case it. The fail state is always the first state after
// the dead state.
//
// If the construction is changed to remove the fail state (it
// probably should be), then this special case should be updated.
if index == 1 {
writeln!(f, "F {:06}:", sid.as_usize())?;
continue;
}
fmt_state_indicator(f, self, sid)?;
write!(f, "{:06}: ", sid.as_usize())?;
let it = (0..self.byte_classes.alphabet_len()).map(|class| {
(class.as_u8(), self.trans[sid.as_usize() + class])
});
for (i, (start, end, next)) in sparse_transitions(it).enumerate() {
if i > 0 {
write!(f, ", ")?;
}
if start == end {
write!(
f,
"{:?} => {:?}",
DebugByte(start),
next.as_usize()
)?;
} else {
write!(
f,
"{:?}-{:?} => {:?}",
DebugByte(start),
DebugByte(end),
next.as_usize()
)?;
}
}
write!(f, "\n")?;
if self.is_match(sid) {
write!(f, " matches: ")?;
for i in 0..self.match_len(sid) {
if i > 0 {
write!(f, ", ")?;
}
let pid = self.match_pattern(sid, i);
write!(f, "{}", pid.as_usize())?;
}
write!(f, "\n")?;
}
}
writeln!(f, "match kind: {:?}", self.match_kind)?;
writeln!(f, "prefilter: {:?}", self.prefilter.is_some())?;
writeln!(f, "state length: {:?}", self.state_len)?;
writeln!(f, "pattern length: {:?}", self.patterns_len())?;
writeln!(f, "shortest pattern length: {:?}", self.min_pattern_len)?;
writeln!(f, "longest pattern length: {:?}", self.max_pattern_len)?;
writeln!(f, "alphabet length: {:?}", self.alphabet_len)?;
writeln!(f, "stride: {:?}", 1 << self.stride2)?;
writeln!(f, "byte classes: {:?}", self.byte_classes)?;
writeln!(f, "memory usage: {:?}", self.memory_usage())?;
writeln!(f, ")")?;
Ok(())
}
}
/// A builder for configuring an Aho-Corasick DFA.
///
/// This builder has a subset of the options available to a
/// [`AhoCorasickBuilder`](crate::AhoCorasickBuilder). Of the shared options,
/// their behavior is identical.
#[derive(Clone, Debug)]
pub struct Builder {
noncontiguous: noncontiguous::Builder,
start_kind: StartKind,
byte_classes: bool,
}
impl Default for Builder {
fn default() -> Builder {
Builder {
noncontiguous: noncontiguous::Builder::new(),
start_kind: StartKind::Unanchored,
byte_classes: true,
}
}
}
impl Builder {
/// Create a new builder for configuring an Aho-Corasick DFA.
pub fn new() -> Builder {
Builder::default()
}
/// Build an Aho-Corasick DFA from the given iterator of patterns.
///
/// A builder may be reused to create more DFAs.
pub fn build<I, P>(&self, patterns: I) -> Result<DFA, BuildError>
where
I: IntoIterator<Item = P>,
P: AsRef<[u8]>,
{
let nnfa = self.noncontiguous.build(patterns)?;
self.build_from_noncontiguous(&nnfa)
}
/// Build an Aho-Corasick DFA from the given noncontiguous NFA.
///
/// Note that when this method is used, only the `start_kind` and
/// `byte_classes` settings on this builder are respected. The other
/// settings only apply to the initial construction of the Aho-Corasick
/// automaton. Since using this method requires that initial construction
/// has already completed, all settings impacting only initial construction
/// are no longer relevant.
pub fn build_from_noncontiguous(
&self,
nnfa: &noncontiguous::NFA,
) -> Result<DFA, BuildError> {
debug!("building DFA");
let byte_classes = if self.byte_classes {
nnfa.byte_classes().clone()
} else {
ByteClasses::singletons()
};
let state_len = match self.start_kind {
StartKind::Unanchored | StartKind::Anchored => nnfa.states().len(),
StartKind::Both => {
// These unwraps are OK because we know that the number of
// NFA states is < StateID::LIMIT which is in turn less than
// i32::MAX. Thus, there is always room to multiply by 2.
// Finally, the number of states is always at least 4 in the
// NFA (DEAD, FAIL, START-UNANCHORED, START-ANCHORED), so the
// subtraction of 4 is okay.
//
// Note that we subtract 4 because the "anchored" part of
// the DFA duplicates the unanchored part (without failure
// transitions), but reuses the DEAD, FAIL and START states.
nnfa.states()
.len()
.checked_mul(2)
.unwrap()
.checked_sub(4)
.unwrap()
}
};
let trans_len =
match state_len.checked_shl(byte_classes.stride2().as_u32()) {
Some(trans_len) => trans_len,
None => {
return Err(BuildError::state_id_overflow(
StateID::MAX.as_u64(),
usize::MAX.as_u64(),
))
}
};
StateID::new(trans_len.checked_sub(byte_classes.stride()).unwrap())
.map_err(|e| {
BuildError::state_id_overflow(
StateID::MAX.as_u64(),
e.attempted(),
)
})?;
let num_match_states = match self.start_kind {
StartKind::Unanchored | StartKind::Anchored => {
nnfa.special().max_match_id.as_usize().checked_sub(1).unwrap()
}
StartKind::Both => nnfa
.special()
.max_match_id
.as_usize()
.checked_sub(1)
.unwrap()
.checked_mul(2)
.unwrap(),
};
let mut dfa = DFA {
trans: vec![DFA::DEAD; trans_len],
matches: vec![vec![]; num_match_states],
matches_memory_usage: 0,
pattern_lens: nnfa.pattern_lens_raw().to_vec(),
prefilter: nnfa.prefilter().cloned(),
match_kind: nnfa.match_kind(),
state_len,
alphabet_len: byte_classes.alphabet_len(),
stride2: byte_classes.stride2(),
byte_classes,
min_pattern_len: nnfa.min_pattern_len(),
max_pattern_len: nnfa.max_pattern_len(),
// The special state IDs are set later.
special: Special::zero(),
};
match self.start_kind {
StartKind::Both => {
self.finish_build_both_starts(nnfa, &mut dfa);
}
StartKind::Unanchored => {
self.finish_build_one_start(Anchored::No, nnfa, &mut dfa);
}
StartKind::Anchored => {
self.finish_build_one_start(Anchored::Yes, nnfa, &mut dfa)
}
}
debug!(
"DFA built, <states: {:?}, size: {:?}, \
alphabet len: {:?}, stride: {:?}>",
dfa.state_len,
dfa.memory_usage(),
dfa.byte_classes.alphabet_len(),
dfa.byte_classes.stride(),
);
// The vectors can grow ~twice as big during construction because a
// Vec amortizes growth. But here, let's shrink things back down to
// what we actually need since we're never going to add more to it.
dfa.trans.shrink_to_fit();
dfa.pattern_lens.shrink_to_fit();
dfa.matches.shrink_to_fit();
// TODO: We might also want to shrink each Vec inside of `dfa.matches`,
// or even better, convert it to one contiguous allocation. But I think
// I went with nested allocs for good reason (can't remember), so this
// may be tricky to do. I decided not to shrink them here because it
// might require a fair bit of work to do. It's unclear whether it's
// worth it.
Ok(dfa)
}
/// Finishes building a DFA for either unanchored or anchored searches,
/// but NOT both.
fn finish_build_one_start(
&self,
anchored: Anchored,
nnfa: &noncontiguous::NFA,
dfa: &mut DFA,
) {
// This function always succeeds because we check above that all of the
// states in the NFA can be mapped to DFA state IDs.
let stride2 = dfa.stride2;
let old2new = |oldsid: StateID| {
StateID::new_unchecked(oldsid.as_usize() << stride2)
};
for (oldsid, state) in nnfa.states().iter().with_state_ids() {
let newsid = old2new(oldsid);
if state.is_match() {
dfa.set_matches(newsid, nnfa.iter_matches(oldsid));
}
sparse_iter(
nnfa,
oldsid,
&dfa.byte_classes,
|byte, class, mut oldnextsid| {
if oldnextsid == noncontiguous::NFA::FAIL {
if anchored.is_anchored() {
oldnextsid = noncontiguous::NFA::DEAD;
} else if state.fail() == noncontiguous::NFA::DEAD {
// This is a special case that avoids following
// DEAD transitions in a non-contiguous NFA.
// Following these transitions is pretty slow
// because the non-contiguous NFA will always use
// a sparse representation for it (because the
// DEAD state is usually treated as a sentinel).
// The *vast* majority of failure states are DEAD
// states, so this winds up being pretty slow if
// we go through the non-contiguous NFA state
// transition logic. Instead, just do it ourselves.
oldnextsid = noncontiguous::NFA::DEAD;
} else {
oldnextsid = nnfa.next_state(
Anchored::No,
state.fail(),
byte,
);
}
}
dfa.trans[newsid.as_usize() + usize::from(class)] =
old2new(oldnextsid);
},
);
}
// Now that we've remapped all the IDs in our states, all that's left
// is remapping the special state IDs.
let old = nnfa.special();
let new = &mut dfa.special;
new.max_special_id = old2new(old.max_special_id);
new.max_match_id = old2new(old.max_match_id);
if anchored.is_anchored() {
new.start_unanchored_id = DFA::DEAD;
new.start_anchored_id = old2new(old.start_anchored_id);
} else {
new.start_unanchored_id = old2new(old.start_unanchored_id);
new.start_anchored_id = DFA::DEAD;
}
}
/// Finishes building a DFA that supports BOTH unanchored and anchored
/// searches. It works by inter-leaving unanchored states with anchored
/// states in the same transition table. This way, we avoid needing to
/// re-shuffle states afterward to ensure that our states still look like
/// DEAD, MATCH, ..., START-UNANCHORED, START-ANCHORED, NON-MATCH, ...
///
/// Honestly this is pretty inscrutable... Simplifications are most
/// welcome.
fn finish_build_both_starts(
&self,
nnfa: &noncontiguous::NFA,
dfa: &mut DFA,
) {
let stride2 = dfa.stride2;
let stride = 1 << stride2;
let mut remap_unanchored = vec![DFA::DEAD; nnfa.states().len()];
let mut remap_anchored = vec![DFA::DEAD; nnfa.states().len()];
let mut is_anchored = vec![false; dfa.state_len];
let mut newsid = DFA::DEAD;
let next_dfa_id =
|sid: StateID| StateID::new_unchecked(sid.as_usize() + stride);
for (oldsid, state) in nnfa.states().iter().with_state_ids() {
if oldsid == noncontiguous::NFA::DEAD
|| oldsid == noncontiguous::NFA::FAIL
{
remap_unanchored[oldsid] = newsid;
remap_anchored[oldsid] = newsid;
newsid = next_dfa_id(newsid);
} else if oldsid == nnfa.special().start_unanchored_id
|| oldsid == nnfa.special().start_anchored_id
{
if oldsid == nnfa.special().start_unanchored_id {
remap_unanchored[oldsid] = newsid;
remap_anchored[oldsid] = DFA::DEAD;
} else {
remap_unanchored[oldsid] = DFA::DEAD;
remap_anchored[oldsid] = newsid;
is_anchored[newsid.as_usize() >> stride2] = true;
}
if state.is_match() {
dfa.set_matches(newsid, nnfa.iter_matches(oldsid));
}
sparse_iter(
nnfa,
oldsid,
&dfa.byte_classes,
|_, class, oldnextsid| {
let class = usize::from(class);
if oldnextsid == noncontiguous::NFA::FAIL {
dfa.trans[newsid.as_usize() + class] = DFA::DEAD;
} else {
dfa.trans[newsid.as_usize() + class] = oldnextsid;
}
},
);
newsid = next_dfa_id(newsid);
} else {
let unewsid = newsid;
newsid = next_dfa_id(newsid);
let anewsid = newsid;
newsid = next_dfa_id(newsid);
remap_unanchored[oldsid] = unewsid;
remap_anchored[oldsid] = anewsid;
is_anchored[anewsid.as_usize() >> stride2] = true;
if state.is_match() {
dfa.set_matches(unewsid, nnfa.iter_matches(oldsid));
dfa.set_matches(anewsid, nnfa.iter_matches(oldsid));
}
sparse_iter(
nnfa,
oldsid,
&dfa.byte_classes,
|byte, class, oldnextsid| {
let class = usize::from(class);
if oldnextsid == noncontiguous::NFA::FAIL {
let oldnextsid =
if state.fail() == noncontiguous::NFA::DEAD {
noncontiguous::NFA::DEAD
} else {
nnfa.next_state(
Anchored::No,
state.fail(),
byte,
)
};
dfa.trans[unewsid.as_usize() + class] = oldnextsid;
} else {
dfa.trans[unewsid.as_usize() + class] = oldnextsid;
dfa.trans[anewsid.as_usize() + class] = oldnextsid;
}
},
);
}
}
for i in 0..dfa.state_len {
let sid = i << stride2;
if is_anchored[i] {
for next in dfa.trans[sid..][..stride].iter_mut() {
*next = remap_anchored[*next];
}
} else {
for next in dfa.trans[sid..][..stride].iter_mut() {
*next = remap_unanchored[*next];
}
}
}
// Now that we've remapped all the IDs in our states, all that's left
// is remapping the special state IDs.
let old = nnfa.special();
let new = &mut dfa.special;
new.max_special_id = remap_anchored[old.max_special_id];
new.max_match_id = remap_anchored[old.max_match_id];
new.start_unanchored_id = remap_unanchored[old.start_unanchored_id];
new.start_anchored_id = remap_anchored[old.start_anchored_id];
}
/// Set the desired match semantics.
///
/// This only applies when using [`Builder::build`] and not
/// [`Builder::build_from_noncontiguous`].
///
/// See
/// [`AhoCorasickBuilder::match_kind`](crate::AhoCorasickBuilder::match_kind)
/// for more documentation and examples.
pub fn match_kind(&mut self, kind: MatchKind) -> &mut Builder {
self.noncontiguous.match_kind(kind);
self
}
/// Enable ASCII-aware case insensitive matching.
///
/// This only applies when using [`Builder::build`] and not
/// [`Builder::build_from_noncontiguous`].
///
/// See
/// [`AhoCorasickBuilder::ascii_case_insensitive`](crate::AhoCorasickBuilder::ascii_case_insensitive)
/// for more documentation and examples.
pub fn ascii_case_insensitive(&mut self, yes: bool) -> &mut Builder {
self.noncontiguous.ascii_case_insensitive(yes);
self
}
/// Enable heuristic prefilter optimizations.
///
/// This only applies when using [`Builder::build`] and not
/// [`Builder::build_from_noncontiguous`].
///
/// See
/// [`AhoCorasickBuilder::prefilter`](crate::AhoCorasickBuilder::prefilter)
/// for more documentation and examples.
pub fn prefilter(&mut self, yes: bool) -> &mut Builder {
self.noncontiguous.prefilter(yes);
self
}
/// Sets the starting state configuration for the automaton.
///
/// See
/// [`AhoCorasickBuilder::start_kind`](crate::AhoCorasickBuilder::start_kind)
/// for more documentation and examples.
pub fn start_kind(&mut self, kind: StartKind) -> &mut Builder {
self.start_kind = kind;
self
}
/// A debug setting for whether to attempt to shrink the size of the
/// automaton's alphabet or not.
///
/// This should never be enabled unless you're debugging an automaton.
/// Namely, disabling byte classes makes transitions easier to reason
/// about, since they use the actual bytes instead of equivalence classes.
/// Disabling this confers no performance benefit at search time.
///
/// See
/// [`AhoCorasickBuilder::byte_classes`](crate::AhoCorasickBuilder::byte_classes)
/// for more documentation and examples.
pub fn byte_classes(&mut self, yes: bool) -> &mut Builder {
self.byte_classes = yes;
self
}
}
/// Iterate over all possible equivalence class transitions in this state.
/// The closure is called for all transitions with a distinct equivalence
/// class, even those not explicitly represented in this sparse state. For
/// any implicitly defined transitions, the given closure is called with
/// the fail state ID.
///
/// The closure is guaranteed to be called precisely
/// `byte_classes.alphabet_len()` times, once for every possible class in
/// ascending order.
fn sparse_iter<F: FnMut(u8, u8, StateID)>(
nnfa: &noncontiguous::NFA,
oldsid: StateID,
classes: &ByteClasses,
mut f: F,
) {
let mut prev_class = None;
let mut byte = 0usize;
for t in nnfa.iter_trans(oldsid) {
while byte < usize::from(t.byte()) {
let rep = byte.as_u8();
let class = classes.get(rep);
byte += 1;
if prev_class != Some(class) {
f(rep, class, noncontiguous::NFA::FAIL);
prev_class = Some(class);
}
}
let rep = t.byte();
let class = classes.get(rep);
byte += 1;
if prev_class != Some(class) {
f(rep, class, t.next());
prev_class = Some(class);
}
}
for b in byte..=255 {
let rep = b.as_u8();
let class = classes.get(rep);
if prev_class != Some(class) {
f(rep, class, noncontiguous::NFA::FAIL);
prev_class = Some(class);
}
}
}
+326
View File
@@ -0,0 +1,326 @@
/*!
A library for finding occurrences of many patterns at once. This library
provides multiple pattern search principally through an implementation of the
[Aho-Corasick algorithm](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm),
which builds a fast finite state machine for executing searches in linear time.
Additionally, this library provides a number of configuration options for
building the automaton that permit controlling the space versus time trade
off. Other features include simple ASCII case insensitive matching, finding
overlapping matches, replacements, searching streams and even searching and
replacing text in streams.
Finally, unlike most other Aho-Corasick implementations, this one
supports enabling [leftmost-first](MatchKind::LeftmostFirst) or
[leftmost-longest](MatchKind::LeftmostLongest) match semantics, using a
(seemingly) novel alternative construction algorithm. For more details on what
match semantics means, see the [`MatchKind`] type.
# Overview
This section gives a brief overview of the primary types in this crate:
* [`AhoCorasick`] is the primary type and represents an Aho-Corasick automaton.
This is the type you use to execute searches.
* [`AhoCorasickBuilder`] can be used to build an Aho-Corasick automaton, and
supports configuring a number of options.
* [`Match`] represents a single match reported by an Aho-Corasick automaton.
Each match has two pieces of information: the pattern that matched and the
start and end byte offsets corresponding to the position in the haystack at
which it matched.
# Example: basic searching
This example shows how to search for occurrences of multiple patterns
simultaneously. Each match includes the pattern that matched along with the
byte offsets of the match.
```
use aho_corasick::{AhoCorasick, PatternID};
let patterns = &["apple", "maple", "Snapple"];
let haystack = "Nobody likes maple in their apple flavored Snapple.";
let ac = AhoCorasick::new(patterns).unwrap();
let mut matches = vec![];
for mat in ac.find_iter(haystack) {
matches.push((mat.pattern(), mat.start(), mat.end()));
}
assert_eq!(matches, vec![
(PatternID::must(1), 13, 18),
(PatternID::must(0), 28, 33),
(PatternID::must(2), 43, 50),
]);
```
# Example: case insensitivity
This is like the previous example, but matches `Snapple` case insensitively
using `AhoCorasickBuilder`:
```
use aho_corasick::{AhoCorasick, PatternID};
let patterns = &["apple", "maple", "snapple"];
let haystack = "Nobody likes maple in their apple flavored Snapple.";
let ac = AhoCorasick::builder()
.ascii_case_insensitive(true)
.build(patterns)
.unwrap();
let mut matches = vec![];
for mat in ac.find_iter(haystack) {
matches.push((mat.pattern(), mat.start(), mat.end()));
}
assert_eq!(matches, vec![
(PatternID::must(1), 13, 18),
(PatternID::must(0), 28, 33),
(PatternID::must(2), 43, 50),
]);
```
# Example: replacing matches in a stream
This example shows how to execute a search and replace on a stream without
loading the entire stream into memory first.
```
# #[cfg(feature = "std")] {
use aho_corasick::AhoCorasick;
# fn example() -> Result<(), std::io::Error> {
let patterns = &["fox", "brown", "quick"];
let replace_with = &["sloth", "grey", "slow"];
// In a real example, these might be `std::fs::File`s instead. All you need to
// do is supply a pair of `std::io::Read` and `std::io::Write` implementations.
let rdr = "The quick brown fox.";
let mut wtr = vec![];
let ac = AhoCorasick::new(patterns).unwrap();
ac.try_stream_replace_all(rdr.as_bytes(), &mut wtr, replace_with)?;
assert_eq!(b"The slow grey sloth.".to_vec(), wtr);
# Ok(()) }; example().unwrap()
# }
```
# Example: finding the leftmost first match
In the textbook description of Aho-Corasick, its formulation is typically
structured such that it reports all possible matches, even when they overlap
with another. In many cases, overlapping matches may not be desired, such as
the case of finding all successive non-overlapping matches like you might with
a standard regular expression.
Unfortunately the "obvious" way to modify the Aho-Corasick algorithm to do
this doesn't always work in the expected way, since it will report matches as
soon as they are seen. For example, consider matching the regex `Samwise|Sam`
against the text `Samwise`. Most regex engines (that are Perl-like, or
non-POSIX) will report `Samwise` as a match, but the standard Aho-Corasick
algorithm modified for reporting non-overlapping matches will report `Sam`.
A novel contribution of this library is the ability to change the match
semantics of Aho-Corasick (without additional search time overhead) such that
`Samwise` is reported instead. For example, here's the standard approach:
```
use aho_corasick::AhoCorasick;
let patterns = &["Samwise", "Sam"];
let haystack = "Samwise";
let ac = AhoCorasick::new(patterns).unwrap();
let mat = ac.find(haystack).expect("should have a match");
assert_eq!("Sam", &haystack[mat.start()..mat.end()]);
```
And now here's the leftmost-first version, which matches how a Perl-like
regex will work:
```
use aho_corasick::{AhoCorasick, MatchKind};
let patterns = &["Samwise", "Sam"];
let haystack = "Samwise";
let ac = AhoCorasick::builder()
.match_kind(MatchKind::LeftmostFirst)
.build(patterns)
.unwrap();
let mat = ac.find(haystack).expect("should have a match");
assert_eq!("Samwise", &haystack[mat.start()..mat.end()]);
```
In addition to leftmost-first semantics, this library also supports
leftmost-longest semantics, which match the POSIX behavior of a regular
expression alternation. See [`MatchKind`] for more details.
# Prefilters
While an Aho-Corasick automaton can perform admirably when compared to more
naive solutions, it is generally slower than more specialized algorithms that
are accelerated using vector instructions such as SIMD.
For that reason, this library will internally use a "prefilter" to attempt
to accelerate searches when possible. Currently, this library has several
different algorithms it might use depending on the patterns provided. Once the
number of patterns gets too big, prefilters are no longer used.
While a prefilter is generally good to have on by default since it works
well in the common case, it can lead to less predictable or even sub-optimal
performance in some cases. For that reason, prefilters can be explicitly
disabled via [`AhoCorasickBuilder::prefilter`].
# Lower level APIs
This crate also provides several sub-modules that collectively expose many of
the implementation details of the main [`AhoCorasick`] type. Most users of this
library can completely ignore the submodules and their contents, but if you
needed finer grained control, some parts of them may be useful to you. Here is
a brief overview of each and why you might want to use them:
* The [`packed`] sub-module contains a lower level API for using fast
vectorized routines for finding a small number of patterns in a haystack.
You might want to use this API when you want to completely side-step using
Aho-Corasick automata. Otherwise, the fast vectorized routines are used
automatically as prefilters for `AhoCorasick` searches whenever possible.
* The [`automaton`] sub-module provides a lower level finite state
machine interface that the various Aho-Corasick implementations in
this crate implement. This sub-module's main contribution is the
[`Automaton`](automaton::Automaton) trait, which permits manually walking the
state transitions of an Aho-Corasick automaton.
* The [`dfa`] and [`nfa`] sub-modules provide DFA and NFA implementations of
the aforementioned `Automaton` trait. The main reason one might want to use
these sub-modules is to get access to a type that implements the `Automaton`
trait. (The top-level `AhoCorasick` type does not implement the `Automaton`
trait.)
As mentioned above, if you aren't sure whether you need these sub-modules,
you should be able to safely ignore them and just focus on the [`AhoCorasick`]
type.
# Crate features
This crate exposes a few features for controlling dependency usage and whether
this crate can be used without the standard library.
* **std** -
Enables support for the standard library. This feature is enabled by
default. When disabled, only `core` and `alloc` are used. At an API
level, enabling `std` enables `std::error::Error` trait impls for the
various error types, and higher level stream search routines such as
[`AhoCorasick::try_stream_find_iter`]. But the `std` feature is also required
to enable vectorized prefilters. Prefilters can greatly accelerate searches,
but generally only apply when the number of patterns is small (less than
~100).
* **perf-literal** -
Enables support for literal prefilters that use vectorized routines from
external crates. This feature is enabled by default. If you're only using
Aho-Corasick for large numbers of patterns or otherwise can abide lower
throughput when searching with a small number of patterns, then it is
reasonable to disable this feature.
* **logging** -
Enables a dependency on the `log` crate and emits messages to aide in
diagnostics. This feature is disabled by default.
*/
#![no_std]
#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate std;
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
#[cfg(feature = "std")]
pub use crate::ahocorasick::StreamFindIter;
pub use crate::{
ahocorasick::{
AhoCorasick, AhoCorasickBuilder, AhoCorasickKind, FindIter,
FindOverlappingIter,
},
util::{
error::{BuildError, MatchError, MatchErrorKind},
primitives::{PatternID, PatternIDError},
search::{Anchored, Input, Match, MatchKind, Span, StartKind},
},
};
#[macro_use]
mod macros;
mod ahocorasick;
pub mod automaton;
pub mod dfa;
pub mod nfa;
pub mod packed;
#[cfg(test)]
mod tests;
// I wrote out the module for implementing fst::Automaton only to later realize
// that this would make fst a public dependency and fst is not at 1.0 yet. I
// decided to just keep the code in tree, but build it only during tests.
//
// TODO: I think I've changed my mind again. I'm considering pushing it out
// into either a separate crate or into 'fst' directly as an optional feature.
// #[cfg(test)]
// #[allow(dead_code)]
// mod transducer;
pub(crate) mod util;
#[cfg(test)]
mod testoibits {
use std::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
fn assert_all<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {}
#[test]
fn oibits_main() {
assert_all::<AhoCorasick>();
assert_all::<AhoCorasickBuilder>();
assert_all::<AhoCorasickKind>();
assert_all::<FindIter>();
assert_all::<FindOverlappingIter>();
assert_all::<BuildError>();
assert_all::<MatchError>();
assert_all::<MatchErrorKind>();
assert_all::<Anchored>();
assert_all::<Input>();
assert_all::<Match>();
assert_all::<MatchKind>();
assert_all::<Span>();
assert_all::<StartKind>();
}
#[test]
fn oibits_automaton() {
use crate::{automaton, dfa::DFA};
assert_all::<automaton::FindIter<DFA>>();
assert_all::<automaton::FindOverlappingIter<DFA>>();
#[cfg(feature = "std")]
assert_all::<automaton::StreamFindIter<DFA, std::io::Stdin>>();
assert_all::<automaton::OverlappingState>();
assert_all::<automaton::Prefilter>();
assert_all::<automaton::Candidate>();
}
#[test]
fn oibits_packed() {
use crate::packed;
assert_all::<packed::Config>();
assert_all::<packed::Builder>();
assert_all::<packed::Searcher>();
assert_all::<packed::FindIter>();
assert_all::<packed::MatchKind>();
}
}
+18
View File
@@ -0,0 +1,18 @@
#![allow(unused_macros)]
macro_rules! log {
($($tt:tt)*) => {
#[cfg(feature = "logging")]
{
$($tt)*
}
}
}
macro_rules! debug {
($($tt:tt)*) => { log!(log::debug!($($tt)*)) }
}
macro_rules! trace {
($($tt:tt)*) => { log!(log::trace!($($tt)*)) }
}
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
/*!
Provides direct access to NFA implementations of Aho-Corasick.
The principle characteristic of an NFA in this crate is that it may
transition through multiple states per byte of haystack. In Aho-Corasick
parlance, NFAs follow failure transitions during a search. In contrast,
a [`DFA`](crate::dfa::DFA) pre-computes all failure transitions during
compilation at the expense of a much bigger memory footprint.
Currently, there are two NFA implementations provided: noncontiguous and
contiguous. The names reflect their internal representation, and consequently,
the trade offs associated with them:
* A [`noncontiguous::NFA`] uses a separate allocation for every NFA state to
represent its transitions in a sparse format. This is ideal for building an
NFA, since it cheaply permits different states to have a different number of
transitions. A noncontiguous NFA is where the main Aho-Corasick construction
algorithm is implemented. All other Aho-Corasick implementations are built by
first constructing a noncontiguous NFA.
* A [`contiguous::NFA`] is uses a single allocation to represent all states,
while still encoding most states as sparse states but permitting states near
the starting state to have a dense representation. The dense representation
uses more memory, but permits computing transitions during a search more
quickly. By only making the most active states dense (the states near the
starting state), a contiguous NFA better balances memory usage with search
speed. The single contiguous allocation also uses less overhead per state and
enables compression tricks where most states only use 8 bytes of heap memory.
When given the choice between these two, you almost always want to pick a
contiguous NFA. It takes only a little longer to build, but both its memory
usage and search speed are typically much better than a noncontiguous NFA. A
noncontiguous NFA is useful when prioritizing build times, or when there are
so many patterns that a contiguous NFA could not be built. (Currently, because
of both memory and search speed improvements, a contiguous NFA has a smaller
internal limit on the total number of NFA states it can represent. But you
would likely need to have hundreds of thousands or even millions of patterns
before you hit this limit.)
*/
pub mod contiguous;
pub mod noncontiguous;
File diff suppressed because it is too large Load Diff
+687
View File
@@ -0,0 +1,687 @@
use alloc::sync::Arc;
use crate::{
packed::{pattern::Patterns, rabinkarp::RabinKarp, teddy},
util::search::{Match, Span},
};
/// This is a limit placed on the total number of patterns we're willing to try
/// and match at once. As more sophisticated algorithms are added, this number
/// may be increased.
const PATTERN_LIMIT: usize = 128;
/// A knob for controlling the match semantics of a packed multiple string
/// searcher.
///
/// This differs from the [`MatchKind`](crate::MatchKind) type in the top-level
/// crate module in that it doesn't support "standard" match semantics,
/// and instead only supports leftmost-first or leftmost-longest. Namely,
/// "standard" semantics cannot be easily supported by packed searchers.
///
/// For more information on the distinction between leftmost-first and
/// leftmost-longest, see the docs on the top-level `MatchKind` type.
///
/// Unlike the top-level `MatchKind` type, the default match semantics for this
/// type are leftmost-first.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MatchKind {
/// Use leftmost-first match semantics, which reports leftmost matches.
/// When there are multiple possible leftmost matches, the match
/// corresponding to the pattern that appeared earlier when constructing
/// the automaton is reported.
///
/// This is the default.
LeftmostFirst,
/// Use leftmost-longest match semantics, which reports leftmost matches.
/// When there are multiple possible leftmost matches, the longest match
/// is chosen.
LeftmostLongest,
}
impl Default for MatchKind {
fn default() -> MatchKind {
MatchKind::LeftmostFirst
}
}
/// The configuration for a packed multiple pattern searcher.
///
/// The configuration is currently limited only to being able to select the
/// match semantics (leftmost-first or leftmost-longest) of a searcher. In the
/// future, more knobs may be made available.
///
/// A configuration produces a [`packed::Builder`](Builder), which in turn can
/// be used to construct a [`packed::Searcher`](Searcher) for searching.
///
/// # Example
///
/// This example shows how to use leftmost-longest semantics instead of the
/// default (leftmost-first).
///
/// ```
/// use aho_corasick::{packed::{Config, MatchKind}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Config::new()
/// .match_kind(MatchKind::LeftmostLongest)
/// .builder()
/// .add("foo")
/// .add("foobar")
/// .build()?;
/// let matches: Vec<PatternID> = searcher
/// .find_iter("foobar")
/// .map(|mat| mat.pattern())
/// .collect();
/// assert_eq!(vec![PatternID::must(1)], matches);
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct Config {
kind: MatchKind,
force: Option<ForceAlgorithm>,
only_teddy_fat: Option<bool>,
only_teddy_256bit: Option<bool>,
heuristic_pattern_limits: bool,
}
/// An internal option for forcing the use of a particular packed algorithm.
///
/// When an algorithm is forced, if a searcher could not be constructed for it,
/// then no searcher will be returned even if an alternative algorithm would
/// work.
#[derive(Clone, Debug)]
enum ForceAlgorithm {
Teddy,
RabinKarp,
}
impl Default for Config {
fn default() -> Config {
Config::new()
}
}
impl Config {
/// Create a new default configuration. A default configuration uses
/// leftmost-first match semantics.
pub fn new() -> Config {
Config {
kind: MatchKind::LeftmostFirst,
force: None,
only_teddy_fat: None,
only_teddy_256bit: None,
heuristic_pattern_limits: true,
}
}
/// Create a packed builder from this configuration. The builder can be
/// used to accumulate patterns and create a [`Searcher`] from them.
pub fn builder(&self) -> Builder {
Builder::from_config(self.clone())
}
/// Set the match semantics for this configuration.
pub fn match_kind(&mut self, kind: MatchKind) -> &mut Config {
self.kind = kind;
self
}
/// An undocumented method for forcing the use of the Teddy algorithm.
///
/// This is only exposed for more precise testing and benchmarks. Callers
/// should not use it as it is not part of the API stability guarantees of
/// this crate.
#[doc(hidden)]
pub fn only_teddy(&mut self, yes: bool) -> &mut Config {
if yes {
self.force = Some(ForceAlgorithm::Teddy);
} else {
self.force = None;
}
self
}
/// An undocumented method for forcing the use of the Fat Teddy algorithm.
///
/// This is only exposed for more precise testing and benchmarks. Callers
/// should not use it as it is not part of the API stability guarantees of
/// this crate.
#[doc(hidden)]
pub fn only_teddy_fat(&mut self, yes: Option<bool>) -> &mut Config {
self.only_teddy_fat = yes;
self
}
/// An undocumented method for forcing the use of SSE (`Some(false)`) or
/// AVX (`Some(true)`) algorithms.
///
/// This is only exposed for more precise testing and benchmarks. Callers
/// should not use it as it is not part of the API stability guarantees of
/// this crate.
#[doc(hidden)]
pub fn only_teddy_256bit(&mut self, yes: Option<bool>) -> &mut Config {
self.only_teddy_256bit = yes;
self
}
/// An undocumented method for forcing the use of the Rabin-Karp algorithm.
///
/// This is only exposed for more precise testing and benchmarks. Callers
/// should not use it as it is not part of the API stability guarantees of
/// this crate.
#[doc(hidden)]
pub fn only_rabin_karp(&mut self, yes: bool) -> &mut Config {
if yes {
self.force = Some(ForceAlgorithm::RabinKarp);
} else {
self.force = None;
}
self
}
/// Request that heuristic limitations on the number of patterns be
/// employed. This useful to disable for benchmarking where one wants to
/// explore how Teddy performs on large number of patterns even if the
/// heuristics would otherwise refuse construction.
///
/// This is enabled by default.
pub fn heuristic_pattern_limits(&mut self, yes: bool) -> &mut Config {
self.heuristic_pattern_limits = yes;
self
}
}
/// A builder for constructing a packed searcher from a collection of patterns.
///
/// # Example
///
/// This example shows how to use a builder to construct a searcher. By
/// default, leftmost-first match semantics are used.
///
/// ```
/// use aho_corasick::{packed::{Builder, MatchKind}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Builder::new()
/// .add("foobar")
/// .add("foo")
/// .build()?;
/// let matches: Vec<PatternID> = searcher
/// .find_iter("foobar")
/// .map(|mat| mat.pattern())
/// .collect();
/// assert_eq!(vec![PatternID::ZERO], matches);
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct Builder {
/// The configuration of this builder and subsequent matcher.
config: Config,
/// Set to true if the builder detects that a matcher cannot be built.
inert: bool,
/// The patterns provided by the caller.
patterns: Patterns,
}
impl Builder {
/// Create a new builder for constructing a multi-pattern searcher. This
/// constructor uses the default configuration.
pub fn new() -> Builder {
Builder::from_config(Config::new())
}
fn from_config(config: Config) -> Builder {
Builder { config, inert: false, patterns: Patterns::new() }
}
/// Build a searcher from the patterns added to this builder so far.
pub fn build(&self) -> Option<Searcher> {
if self.inert || self.patterns.is_empty() {
return None;
}
let mut patterns = self.patterns.clone();
patterns.set_match_kind(self.config.kind);
let patterns = Arc::new(patterns);
let rabinkarp = RabinKarp::new(&patterns);
// Effectively, we only want to return a searcher if we can use Teddy,
// since Teddy is our only fast packed searcher at the moment.
// Rabin-Karp is only used when searching haystacks smaller than what
// Teddy can support. Thus, the only way to get a Rabin-Karp searcher
// is to force it using undocumented APIs (for tests/benchmarks).
let (search_kind, minimum_len) = match self.config.force {
None | Some(ForceAlgorithm::Teddy) => {
debug!("trying to build Teddy packed matcher");
let teddy = match self.build_teddy(Arc::clone(&patterns)) {
None => return None,
Some(teddy) => teddy,
};
let minimum_len = teddy.minimum_len();
(SearchKind::Teddy(teddy), minimum_len)
}
Some(ForceAlgorithm::RabinKarp) => {
debug!("using Rabin-Karp packed matcher");
(SearchKind::RabinKarp, 0)
}
};
Some(Searcher { patterns, rabinkarp, search_kind, minimum_len })
}
fn build_teddy(&self, patterns: Arc<Patterns>) -> Option<teddy::Searcher> {
teddy::Builder::new()
.only_256bit(self.config.only_teddy_256bit)
.only_fat(self.config.only_teddy_fat)
.heuristic_pattern_limits(self.config.heuristic_pattern_limits)
.build(patterns)
}
/// Add the given pattern to this set to match.
///
/// The order in which patterns are added is significant. Namely, when
/// using leftmost-first match semantics, then when multiple patterns can
/// match at a particular location, the pattern that was added first is
/// used as the match.
///
/// If the number of patterns added exceeds the amount supported by packed
/// searchers, then the builder will stop accumulating patterns and render
/// itself inert. At this point, constructing a searcher will always return
/// `None`.
pub fn add<P: AsRef<[u8]>>(&mut self, pattern: P) -> &mut Builder {
if self.inert {
return self;
} else if self.patterns.len() >= PATTERN_LIMIT {
self.inert = true;
self.patterns.reset();
return self;
}
// Just in case PATTERN_LIMIT increases beyond u16::MAX.
assert!(self.patterns.len() <= core::u16::MAX as usize);
let pattern = pattern.as_ref();
if pattern.is_empty() {
self.inert = true;
self.patterns.reset();
return self;
}
self.patterns.add(pattern);
self
}
/// Add the given iterator of patterns to this set to match.
///
/// The iterator must yield elements that can be converted into a `&[u8]`.
///
/// The order in which patterns are added is significant. Namely, when
/// using leftmost-first match semantics, then when multiple patterns can
/// match at a particular location, the pattern that was added first is
/// used as the match.
///
/// If the number of patterns added exceeds the amount supported by packed
/// searchers, then the builder will stop accumulating patterns and render
/// itself inert. At this point, constructing a searcher will always return
/// `None`.
pub fn extend<I, P>(&mut self, patterns: I) -> &mut Builder
where
I: IntoIterator<Item = P>,
P: AsRef<[u8]>,
{
for p in patterns {
self.add(p);
}
self
}
/// Returns the number of patterns added to this builder.
pub fn len(&self) -> usize {
self.patterns.len()
}
/// Returns the length, in bytes, of the shortest pattern added.
pub fn minimum_len(&self) -> usize {
self.patterns.minimum_len()
}
}
impl Default for Builder {
fn default() -> Builder {
Builder::new()
}
}
/// A packed searcher for quickly finding occurrences of multiple patterns.
///
/// If callers need more flexible construction, or if one wants to change the
/// match semantics (either leftmost-first or leftmost-longest), then one can
/// use the [`Config`] and/or [`Builder`] types for more fine grained control.
///
/// # Example
///
/// This example shows how to create a searcher from an iterator of patterns.
/// By default, leftmost-first match semantics are used.
///
/// ```
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// let matches: Vec<PatternID> = searcher
/// .find_iter("foobar")
/// .map(|mat| mat.pattern())
/// .collect();
/// assert_eq!(vec![PatternID::ZERO], matches);
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct Searcher {
patterns: Arc<Patterns>,
rabinkarp: RabinKarp,
search_kind: SearchKind,
minimum_len: usize,
}
#[derive(Clone, Debug)]
enum SearchKind {
Teddy(teddy::Searcher),
RabinKarp,
}
impl Searcher {
/// A convenience function for constructing a searcher from an iterator
/// of things that can be converted to a `&[u8]`.
///
/// If a searcher could not be constructed (either because of an
/// unsupported CPU or because there are too many patterns), then `None`
/// is returned.
///
/// # Example
///
/// Basic usage:
///
/// ```
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// let matches: Vec<PatternID> = searcher
/// .find_iter("foobar")
/// .map(|mat| mat.pattern())
/// .collect();
/// assert_eq!(vec![PatternID::ZERO], matches);
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
pub fn new<I, P>(patterns: I) -> Option<Searcher>
where
I: IntoIterator<Item = P>,
P: AsRef<[u8]>,
{
Builder::new().extend(patterns).build()
}
/// A convenience function for calling `Config::new()`.
///
/// This is useful for avoiding an additional import.
pub fn config() -> Config {
Config::new()
}
/// A convenience function for calling `Builder::new()`.
///
/// This is useful for avoiding an additional import.
pub fn builder() -> Builder {
Builder::new()
}
/// Return the first occurrence of any of the patterns in this searcher,
/// according to its match semantics, in the given haystack. The `Match`
/// returned will include the identifier of the pattern that matched, which
/// corresponds to the index of the pattern (starting from `0`) in which it
/// was added.
///
/// # Example
///
/// Basic usage:
///
/// ```
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// let mat = searcher.find("foobar")?;
/// assert_eq!(PatternID::ZERO, mat.pattern());
/// assert_eq!(0, mat.start());
/// assert_eq!(6, mat.end());
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[inline]
pub fn find<B: AsRef<[u8]>>(&self, haystack: B) -> Option<Match> {
let haystack = haystack.as_ref();
self.find_in(haystack, Span::from(0..haystack.len()))
}
/// Return the first occurrence of any of the patterns in this searcher,
/// according to its match semantics, in the given haystack starting from
/// the given position.
///
/// The `Match` returned will include the identifier of the pattern that
/// matched, which corresponds to the index of the pattern (starting from
/// `0`) in which it was added. The offsets in the `Match` will be relative
/// to the start of `haystack` (and not `at`).
///
/// # Example
///
/// Basic usage:
///
/// ```
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID, Span};
///
/// # fn example() -> Option<()> {
/// let haystack = "foofoobar";
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// let mat = searcher.find_in(haystack, Span::from(3..haystack.len()))?;
/// assert_eq!(PatternID::ZERO, mat.pattern());
/// assert_eq!(3, mat.start());
/// assert_eq!(9, mat.end());
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[inline]
pub fn find_in<B: AsRef<[u8]>>(
&self,
haystack: B,
span: Span,
) -> Option<Match> {
let haystack = haystack.as_ref();
match self.search_kind {
SearchKind::Teddy(ref teddy) => {
if haystack[span].len() < teddy.minimum_len() {
return self.find_in_slow(haystack, span);
}
teddy.find(&haystack[..span.end], span.start)
}
SearchKind::RabinKarp => {
self.rabinkarp.find_at(&haystack[..span.end], span.start)
}
}
}
/// Return an iterator of non-overlapping occurrences of the patterns in
/// this searcher, according to its match semantics, in the given haystack.
///
/// # Example
///
/// Basic usage:
///
/// ```
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
///
/// # fn example() -> Option<()> {
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// let matches: Vec<PatternID> = searcher
/// .find_iter("foobar fooba foofoo")
/// .map(|mat| mat.pattern())
/// .collect();
/// assert_eq!(vec![
/// PatternID::must(0),
/// PatternID::must(1),
/// PatternID::must(1),
/// PatternID::must(1),
/// ], matches);
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[inline]
pub fn find_iter<'a, 'b, B: ?Sized + AsRef<[u8]>>(
&'a self,
haystack: &'b B,
) -> FindIter<'a, 'b> {
let haystack = haystack.as_ref();
let span = Span::from(0..haystack.len());
FindIter { searcher: self, haystack, span }
}
/// Returns the match kind used by this packed searcher.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use aho_corasick::packed::{MatchKind, Searcher};
///
/// # fn example() -> Option<()> {
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
/// // leftmost-first is the default.
/// assert_eq!(&MatchKind::LeftmostFirst, searcher.match_kind());
/// # Some(()) }
/// # if cfg!(all(feature = "std", any(
/// # target_arch = "x86_64", target_arch = "aarch64",
/// # ))) {
/// # example().unwrap()
/// # } else {
/// # assert!(example().is_none());
/// # }
/// ```
#[inline]
pub fn match_kind(&self) -> &MatchKind {
self.patterns.match_kind()
}
/// Returns the minimum length of a haystack that is required in order for
/// packed searching to be effective.
///
/// In some cases, the underlying packed searcher may not be able to search
/// very short haystacks. When that occurs, the implementation will defer
/// to a slower non-packed searcher (which is still generally faster than
/// Aho-Corasick for a small number of patterns). However, callers may
/// want to avoid ever using the slower variant, which one can do by
/// never passing a haystack shorter than the minimum length returned by
/// this method.
#[inline]
pub fn minimum_len(&self) -> usize {
self.minimum_len
}
/// Returns the approximate total amount of heap used by this searcher, in
/// units of bytes.
#[inline]
pub fn memory_usage(&self) -> usize {
self.patterns.memory_usage()
+ self.rabinkarp.memory_usage()
+ self.search_kind.memory_usage()
}
/// Use a slow (non-packed) searcher.
///
/// This is useful when a packed searcher could be constructed, but could
/// not be used to search a specific haystack. For example, if Teddy was
/// built but the haystack is smaller than ~34 bytes, then Teddy might not
/// be able to run.
fn find_in_slow(&self, haystack: &[u8], span: Span) -> Option<Match> {
self.rabinkarp.find_at(&haystack[..span.end], span.start)
}
}
impl SearchKind {
fn memory_usage(&self) -> usize {
match *self {
SearchKind::Teddy(ref ted) => ted.memory_usage(),
SearchKind::RabinKarp => 0,
}
}
}
/// An iterator over non-overlapping matches from a packed searcher.
///
/// The lifetime `'s` refers to the lifetime of the underlying [`Searcher`],
/// while the lifetime `'h` refers to the lifetime of the haystack being
/// searched.
#[derive(Debug)]
pub struct FindIter<'s, 'h> {
searcher: &'s Searcher,
haystack: &'h [u8],
span: Span,
}
impl<'s, 'h> Iterator for FindIter<'s, 'h> {
type Item = Match;
fn next(&mut self) -> Option<Match> {
if self.span.start > self.span.end {
return None;
}
match self.searcher.find_in(self.haystack, self.span) {
None => None,
Some(m) => {
self.span.start = m.end();
Some(m)
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
/// A trait for adding some helper routines to pointers.
pub(crate) trait Pointer {
/// Returns the distance, in units of `T`, between `self` and `origin`.
///
/// # Safety
///
/// Same as `ptr::offset_from` in addition to `self >= origin`.
unsafe fn distance(self, origin: Self) -> usize;
/// Casts this pointer to `usize`.
///
/// Callers should not convert the `usize` back to a pointer if at all
/// possible. (And if you believe it's necessary, open an issue to discuss
/// why. Otherwise, it has the potential to violate pointer provenance.)
/// The purpose of this function is just to be able to do arithmetic, i.e.,
/// computing offsets or alignments.
fn as_usize(self) -> usize;
}
impl<T> Pointer for *const T {
unsafe fn distance(self, origin: *const T) -> usize {
// TODO: Replace with `ptr::sub_ptr` once stabilized.
usize::try_from(self.offset_from(origin)).unwrap_unchecked()
}
fn as_usize(self) -> usize {
self as usize
}
}
impl<T> Pointer for *mut T {
unsafe fn distance(self, origin: *mut T) -> usize {
(self as *const T).distance(origin as *const T)
}
fn as_usize(self) -> usize {
(self as *const T).as_usize()
}
}
+120
View File
@@ -0,0 +1,120 @@
/*!
Provides packed multiple substring search, principally for a small number of
patterns.
This sub-module provides vectorized routines for quickly finding
matches of a small number of patterns. In general, users of this crate
shouldn't need to interface with this module directly, as the primary
[`AhoCorasick`](crate::AhoCorasick) searcher will use these routines
automatically as a prefilter when applicable. However, in some cases, callers
may want to bypass the Aho-Corasick machinery entirely and use this vectorized
searcher directly.
# Overview
The primary types in this sub-module are:
* [`Searcher`] executes the actual search algorithm to report matches in a
haystack.
* [`Builder`] accumulates patterns incrementally and can construct a
`Searcher`.
* [`Config`] permits tuning the searcher, and itself will produce a `Builder`
(which can then be used to build a `Searcher`). Currently, the only tuneable
knob are the match semantics, but this may be expanded in the future.
# Examples
This example shows how to create a searcher from an iterator of patterns.
By default, leftmost-first match semantics are used. (See the top-level
[`MatchKind`] type for more details about match semantics, which apply
similarly to packed substring search.)
```
use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
# fn example() -> Option<()> {
let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
let matches: Vec<PatternID> = searcher
.find_iter("foobar")
.map(|mat| mat.pattern())
.collect();
assert_eq!(vec![PatternID::ZERO], matches);
# Some(()) }
# if cfg!(all(feature = "std", any(
# target_arch = "x86_64", target_arch = "aarch64",
# ))) {
# example().unwrap()
# } else {
# assert!(example().is_none());
# }
```
This example shows how to use [`Config`] to change the match semantics to
leftmost-longest:
```
use aho_corasick::{packed::{Config, MatchKind}, PatternID};
# fn example() -> Option<()> {
let searcher = Config::new()
.match_kind(MatchKind::LeftmostLongest)
.builder()
.add("foo")
.add("foobar")
.build()?;
let matches: Vec<PatternID> = searcher
.find_iter("foobar")
.map(|mat| mat.pattern())
.collect();
assert_eq!(vec![PatternID::must(1)], matches);
# Some(()) }
# if cfg!(all(feature = "std", any(
# target_arch = "x86_64", target_arch = "aarch64",
# ))) {
# example().unwrap()
# } else {
# assert!(example().is_none());
# }
```
# Packed substring searching
Packed substring searching refers to the use of SIMD (Single Instruction,
Multiple Data) to accelerate the detection of matches in a haystack. Unlike
conventional algorithms, such as Aho-Corasick, SIMD algorithms for substring
search tend to do better with a small number of patterns, where as Aho-Corasick
generally maintains reasonably consistent performance regardless of the number
of patterns you give it. Because of this, the vectorized searcher in this
sub-module cannot be used as a general purpose searcher, since building the
searcher may fail even when given a small number of patterns. However, in
exchange, when searching for a small number of patterns, searching can be quite
a bit faster than Aho-Corasick (sometimes by an order of magnitude).
The key take away here is that constructing a searcher from a list of patterns
is a fallible operation with no clear rules for when it will fail. While the
precise conditions under which building a searcher can fail is specifically an
implementation detail, here are some common reasons:
* Too many patterns were given. Typically, the limit is on the order of 100 or
so, but this limit may fluctuate based on available CPU features.
* The available packed algorithms require CPU features that aren't available.
For example, currently, this crate only provides packed algorithms for
`x86_64` and `aarch64`. Therefore, constructing a packed searcher on any
other target will always fail.
* Zero patterns were given, or one of the patterns given was empty. Packed
searchers require at least one pattern and that all patterns are non-empty.
* Something else about the nature of the patterns (typically based on
heuristics) suggests that a packed searcher would perform very poorly, so
no searcher is built.
*/
pub use crate::packed::api::{Builder, Config, FindIter, MatchKind, Searcher};
mod api;
mod ext;
mod pattern;
mod rabinkarp;
mod teddy;
#[cfg(all(feature = "std", test))]
mod tests;
mod vector;
+480
View File
@@ -0,0 +1,480 @@
use core::{cmp, fmt, mem, u16, usize};
use alloc::{boxed::Box, string::String, vec, vec::Vec};
use crate::{
packed::{api::MatchKind, ext::Pointer},
PatternID,
};
/// A non-empty collection of non-empty patterns to search for.
///
/// This collection of patterns is what is passed around to both execute
/// searches and to construct the searchers themselves. Namely, this permits
/// searches to avoid copying all of the patterns, and allows us to keep only
/// one copy throughout all packed searchers.
///
/// Note that this collection is not a set. The same pattern can appear more
/// than once.
#[derive(Clone, Debug)]
pub(crate) struct Patterns {
/// The match semantics supported by this collection of patterns.
///
/// The match semantics determines the order of the iterator over patterns.
/// For leftmost-first, patterns are provided in the same order as were
/// provided by the caller. For leftmost-longest, patterns are provided in
/// descending order of length, with ties broken by the order in which they
/// were provided by the caller.
kind: MatchKind,
/// The collection of patterns, indexed by their identifier.
by_id: Vec<Vec<u8>>,
/// The order of patterns defined for iteration, given by pattern
/// identifiers. The order of `by_id` and `order` is always the same for
/// leftmost-first semantics, but may be different for leftmost-longest
/// semantics.
order: Vec<PatternID>,
/// The length of the smallest pattern, in bytes.
minimum_len: usize,
/// The total number of pattern bytes across the entire collection. This
/// is used for reporting total heap usage in constant time.
total_pattern_bytes: usize,
}
// BREADCRUMBS: I think we want to experiment with a different bucket
// representation. Basically, each bucket is just a Range<usize> to a single
// contiguous allocation? Maybe length-prefixed patterns or something? The
// idea is to try to get rid of the pointer chasing in verification. I don't
// know that that is the issue, but I suspect it is.
impl Patterns {
/// Create a new collection of patterns for the given match semantics. The
/// ID of each pattern is the index of the pattern at which it occurs in
/// the `by_id` slice.
///
/// If any of the patterns in the slice given are empty, then this panics.
/// Similarly, if the number of patterns given is zero, then this also
/// panics.
pub(crate) fn new() -> Patterns {
Patterns {
kind: MatchKind::default(),
by_id: vec![],
order: vec![],
minimum_len: usize::MAX,
total_pattern_bytes: 0,
}
}
/// Add a pattern to this collection.
///
/// This panics if the pattern given is empty.
pub(crate) fn add(&mut self, bytes: &[u8]) {
assert!(!bytes.is_empty());
assert!(self.by_id.len() <= u16::MAX as usize);
let id = PatternID::new(self.by_id.len()).unwrap();
self.order.push(id);
self.by_id.push(bytes.to_vec());
self.minimum_len = cmp::min(self.minimum_len, bytes.len());
self.total_pattern_bytes += bytes.len();
}
/// Set the match kind semantics for this collection of patterns.
///
/// If the kind is not set, then the default is leftmost-first.
pub(crate) fn set_match_kind(&mut self, kind: MatchKind) {
self.kind = kind;
match self.kind {
MatchKind::LeftmostFirst => {
self.order.sort();
}
MatchKind::LeftmostLongest => {
let (order, by_id) = (&mut self.order, &mut self.by_id);
order.sort_by(|&id1, &id2| {
by_id[id1].len().cmp(&by_id[id2].len()).reverse()
});
}
}
}
/// Return the number of patterns in this collection.
///
/// This is guaranteed to be greater than zero.
pub(crate) fn len(&self) -> usize {
self.by_id.len()
}
/// Returns true if and only if this collection of patterns is empty.
pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the approximate total amount of heap used by these patterns, in
/// units of bytes.
pub(crate) fn memory_usage(&self) -> usize {
self.order.len() * mem::size_of::<PatternID>()
+ self.by_id.len() * mem::size_of::<Vec<u8>>()
+ self.total_pattern_bytes
}
/// Clears all heap memory associated with this collection of patterns and
/// resets all state such that it is a valid empty collection.
pub(crate) fn reset(&mut self) {
self.kind = MatchKind::default();
self.by_id.clear();
self.order.clear();
self.minimum_len = usize::MAX;
}
/// Returns the length, in bytes, of the smallest pattern.
///
/// This is guaranteed to be at least one.
pub(crate) fn minimum_len(&self) -> usize {
self.minimum_len
}
/// Returns the match semantics used by these patterns.
pub(crate) fn match_kind(&self) -> &MatchKind {
&self.kind
}
/// Return the pattern with the given identifier. If such a pattern does
/// not exist, then this panics.
pub(crate) fn get(&self, id: PatternID) -> Pattern<'_> {
Pattern(&self.by_id[id])
}
/// Return the pattern with the given identifier without performing bounds
/// checks.
///
/// # Safety
///
/// Callers must ensure that a pattern with the given identifier exists
/// before using this method.
pub(crate) unsafe fn get_unchecked(&self, id: PatternID) -> Pattern<'_> {
Pattern(self.by_id.get_unchecked(id.as_usize()))
}
/// Return an iterator over all the patterns in this collection, in the
/// order in which they should be matched.
///
/// Specifically, in a naive multi-pattern matcher, the following is
/// guaranteed to satisfy the match semantics of this collection of
/// patterns:
///
/// ```ignore
/// for i in 0..haystack.len():
/// for p in patterns.iter():
/// if haystack[i..].starts_with(p.bytes()):
/// return Match(p.id(), i, i + p.bytes().len())
/// ```
///
/// Namely, among the patterns in a collection, if they are matched in
/// the order provided by this iterator, then the result is guaranteed
/// to satisfy the correct match semantics. (Either leftmost-first or
/// leftmost-longest.)
pub(crate) fn iter(&self) -> PatternIter<'_> {
PatternIter { patterns: self, i: 0 }
}
}
/// An iterator over the patterns in the `Patterns` collection.
///
/// The order of the patterns provided by this iterator is consistent with the
/// match semantics of the originating collection of patterns.
///
/// The lifetime `'p` corresponds to the lifetime of the collection of patterns
/// this is iterating over.
#[derive(Debug)]
pub(crate) struct PatternIter<'p> {
patterns: &'p Patterns,
i: usize,
}
impl<'p> Iterator for PatternIter<'p> {
type Item = (PatternID, Pattern<'p>);
fn next(&mut self) -> Option<(PatternID, Pattern<'p>)> {
if self.i >= self.patterns.len() {
return None;
}
let id = self.patterns.order[self.i];
let p = self.patterns.get(id);
self.i += 1;
Some((id, p))
}
}
/// A pattern that is used in packed searching.
#[derive(Clone)]
pub(crate) struct Pattern<'a>(&'a [u8]);
impl<'a> fmt::Debug for Pattern<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pattern")
.field("lit", &String::from_utf8_lossy(self.0))
.finish()
}
}
impl<'p> Pattern<'p> {
/// Returns the length of this pattern, in bytes.
pub(crate) fn len(&self) -> usize {
self.0.len()
}
/// Returns the bytes of this pattern.
pub(crate) fn bytes(&self) -> &[u8] {
self.0
}
/// Returns the first `len` low nybbles from this pattern. If this pattern
/// is shorter than `len`, then this panics.
pub(crate) fn low_nybbles(&self, len: usize) -> Box<[u8]> {
let mut nybs = vec![0; len].into_boxed_slice();
for (i, byte) in self.bytes().iter().take(len).enumerate() {
nybs[i] = byte & 0xF;
}
nybs
}
/// Returns true if this pattern is a prefix of the given bytes.
#[inline(always)]
pub(crate) fn is_prefix(&self, bytes: &[u8]) -> bool {
is_prefix(bytes, self.bytes())
}
/// Returns true if this pattern is a prefix of the haystack given by the
/// raw `start` and `end` pointers.
///
/// # Safety
///
/// * It must be the case that `start < end` and that the distance between
/// them is at least equal to `V::BYTES`. That is, it must always be valid
/// to do at least an unaligned load of `V` at `start`.
/// * Both `start` and `end` must be valid for reads.
/// * Both `start` and `end` must point to an initialized value.
/// * Both `start` and `end` must point to the same allocated object and
/// must either be in bounds or at most one byte past the end of the
/// allocated object.
/// * Both `start` and `end` must be _derived from_ a pointer to the same
/// object.
/// * The distance between `start` and `end` must not overflow `isize`.
/// * The distance being in bounds must not rely on "wrapping around" the
/// address space.
#[inline(always)]
pub(crate) unsafe fn is_prefix_raw(
&self,
start: *const u8,
end: *const u8,
) -> bool {
let patlen = self.bytes().len();
let haylen = end.distance(start);
if patlen > haylen {
return false;
}
// SAFETY: We've checked that the haystack has length at least equal
// to this pattern. All other safety concerns are the responsibility
// of the caller.
is_equal_raw(start, self.bytes().as_ptr(), patlen)
}
}
/// Returns true if and only if `needle` is a prefix of `haystack`.
///
/// This uses a latency optimized variant of `memcmp` internally which *might*
/// make this faster for very short strings.
///
/// # Inlining
///
/// This routine is marked `inline(always)`. If you want to call this function
/// in a way that is not always inlined, you'll need to wrap a call to it in
/// another function that is marked as `inline(never)` or just `inline`.
#[inline(always)]
fn is_prefix(haystack: &[u8], needle: &[u8]) -> bool {
if needle.len() > haystack.len() {
return false;
}
// SAFETY: Our pointers are derived directly from borrowed slices which
// uphold all of our safety guarantees except for length. We account for
// length with the check above.
unsafe { is_equal_raw(haystack.as_ptr(), needle.as_ptr(), needle.len()) }
}
/// Compare corresponding bytes in `x` and `y` for equality.
///
/// That is, this returns true if and only if `x.len() == y.len()` and
/// `x[i] == y[i]` for all `0 <= i < x.len()`.
///
/// Note that this isn't used. We only use it in tests as a convenient way
/// of testing `is_equal_raw`.
///
/// # Inlining
///
/// This routine is marked `inline(always)`. If you want to call this function
/// in a way that is not always inlined, you'll need to wrap a call to it in
/// another function that is marked as `inline(never)` or just `inline`.
///
/// # Motivation
///
/// Why not use slice equality instead? Well, slice equality usually results in
/// a call out to the current platform's `libc` which might not be inlineable
/// or have other overhead. This routine isn't guaranteed to be a win, but it
/// might be in some cases.
#[cfg(test)]
#[inline(always)]
fn is_equal(x: &[u8], y: &[u8]) -> bool {
if x.len() != y.len() {
return false;
}
// SAFETY: Our pointers are derived directly from borrowed slices which
// uphold all of our safety guarantees except for length. We account for
// length with the check above.
unsafe { is_equal_raw(x.as_ptr(), y.as_ptr(), x.len()) }
}
/// Compare `n` bytes at the given pointers for equality.
///
/// This returns true if and only if `*x.add(i) == *y.add(i)` for all
/// `0 <= i < n`.
///
/// # Inlining
///
/// This routine is marked `inline(always)`. If you want to call this function
/// in a way that is not always inlined, you'll need to wrap a call to it in
/// another function that is marked as `inline(never)` or just `inline`.
///
/// # Motivation
///
/// Why not use slice equality instead? Well, slice equality usually results in
/// a call out to the current platform's `libc` which might not be inlineable
/// or have other overhead. This routine isn't guaranteed to be a win, but it
/// might be in some cases.
///
/// # Safety
///
/// * Both `x` and `y` must be valid for reads of up to `n` bytes.
/// * Both `x` and `y` must point to an initialized value.
/// * Both `x` and `y` must each point to an allocated object and
/// must either be in bounds or at most one byte past the end of the
/// allocated object. `x` and `y` do not need to point to the same allocated
/// object, but they may.
/// * Both `x` and `y` must be _derived from_ a pointer to their respective
/// allocated objects.
/// * The distance between `x` and `x+n` must not overflow `isize`. Similarly
/// for `y` and `y+n`.
/// * The distance being in bounds must not rely on "wrapping around" the
/// address space.
#[inline(always)]
unsafe fn is_equal_raw(mut x: *const u8, mut y: *const u8, n: usize) -> bool {
// If we don't have enough bytes to do 4-byte at a time loads, then
// handle each possible length specially. Note that I used to have a
// byte-at-a-time loop here and that turned out to be quite a bit slower
// for the memmem/pathological/defeat-simple-vector-alphabet benchmark.
if n < 4 {
return match n {
0 => true,
1 => x.read() == y.read(),
2 => {
x.cast::<u16>().read_unaligned()
== y.cast::<u16>().read_unaligned()
}
// I also tried copy_nonoverlapping here and it looks like the
// codegen is the same.
3 => x.cast::<[u8; 3]>().read() == y.cast::<[u8; 3]>().read(),
_ => unreachable!(),
};
}
// When we have 4 or more bytes to compare, then proceed in chunks of 4 at
// a time using unaligned loads.
//
// Also, why do 4 byte loads instead of, say, 8 byte loads? The reason is
// that this particular version of memcmp is likely to be called with tiny
// needles. That means that if we do 8 byte loads, then a higher proportion
// of memcmp calls will use the slower variant above. With that said, this
// is a hypothesis and is only loosely supported by benchmarks. There's
// likely some improvement that could be made here. The main thing here
// though is to optimize for latency, not throughput.
// SAFETY: The caller is responsible for ensuring the pointers we get are
// valid and readable for at least `n` bytes. We also do unaligned loads,
// so there's no need to ensure we're aligned. (This is justified by this
// routine being specifically for short strings.)
let xend = x.add(n.wrapping_sub(4));
let yend = y.add(n.wrapping_sub(4));
while x < xend {
let vx = x.cast::<u32>().read_unaligned();
let vy = y.cast::<u32>().read_unaligned();
if vx != vy {
return false;
}
x = x.add(4);
y = y.add(4);
}
let vx = xend.cast::<u32>().read_unaligned();
let vy = yend.cast::<u32>().read_unaligned();
vx == vy
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equals_different_lengths() {
assert!(!is_equal(b"", b"a"));
assert!(!is_equal(b"a", b""));
assert!(!is_equal(b"ab", b"a"));
assert!(!is_equal(b"a", b"ab"));
}
#[test]
fn equals_mismatch() {
let one_mismatch = [
(&b"a"[..], &b"x"[..]),
(&b"ab"[..], &b"ax"[..]),
(&b"abc"[..], &b"abx"[..]),
(&b"abcd"[..], &b"abcx"[..]),
(&b"abcde"[..], &b"abcdx"[..]),
(&b"abcdef"[..], &b"abcdex"[..]),
(&b"abcdefg"[..], &b"abcdefx"[..]),
(&b"abcdefgh"[..], &b"abcdefgx"[..]),
(&b"abcdefghi"[..], &b"abcdefghx"[..]),
(&b"abcdefghij"[..], &b"abcdefghix"[..]),
(&b"abcdefghijk"[..], &b"abcdefghijx"[..]),
(&b"abcdefghijkl"[..], &b"abcdefghijkx"[..]),
(&b"abcdefghijklm"[..], &b"abcdefghijklx"[..]),
(&b"abcdefghijklmn"[..], &b"abcdefghijklmx"[..]),
];
for (x, y) in one_mismatch {
assert_eq!(x.len(), y.len(), "lengths should match");
assert!(!is_equal(x, y));
assert!(!is_equal(y, x));
}
}
#[test]
fn equals_yes() {
assert!(is_equal(b"", b""));
assert!(is_equal(b"a", b"a"));
assert!(is_equal(b"ab", b"ab"));
assert!(is_equal(b"abc", b"abc"));
assert!(is_equal(b"abcd", b"abcd"));
assert!(is_equal(b"abcde", b"abcde"));
assert!(is_equal(b"abcdef", b"abcdef"));
assert!(is_equal(b"abcdefg", b"abcdefg"));
assert!(is_equal(b"abcdefgh", b"abcdefgh"));
assert!(is_equal(b"abcdefghi", b"abcdefghi"));
}
#[test]
fn prefix() {
assert!(is_prefix(b"", b""));
assert!(is_prefix(b"a", b""));
assert!(is_prefix(b"ab", b""));
assert!(is_prefix(b"foo", b"foo"));
assert!(is_prefix(b"foobar", b"foo"));
assert!(!is_prefix(b"foo", b"fob"));
assert!(!is_prefix(b"foobar", b"fob"));
}
}
@@ -0,0 +1,168 @@
use alloc::{sync::Arc, vec, vec::Vec};
use crate::{packed::pattern::Patterns, util::search::Match, PatternID};
/// The type of the rolling hash used in the Rabin-Karp algorithm.
type Hash = usize;
/// The number of buckets to store our patterns in. We don't want this to be
/// too big in order to avoid wasting memory, but we don't want it to be too
/// small either to avoid spending too much time confirming literals.
///
/// The number of buckets MUST be a power of two. Otherwise, determining the
/// bucket from a hash will slow down the code considerably. Using a power
/// of two means `hash % NUM_BUCKETS` can compile down to a simple `and`
/// instruction.
const NUM_BUCKETS: usize = 64;
/// An implementation of the Rabin-Karp algorithm. The main idea of this
/// algorithm is to maintain a rolling hash as it moves through the input, and
/// then check whether that hash corresponds to the same hash for any of the
/// patterns we're looking for.
///
/// A draw back of naively scaling Rabin-Karp to multiple patterns is that
/// it requires all of the patterns to be the same length, which in turn
/// corresponds to the number of bytes to hash. We adapt this to work for
/// multiple patterns of varying size by fixing the number of bytes to hash
/// to be the length of the smallest pattern. We also split the patterns into
/// several buckets to hopefully make the confirmation step faster.
///
/// Wikipedia has a decent explanation, if a bit heavy on the theory:
/// https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm
///
/// But ESMAJ provides something a bit more concrete:
/// https://www-igm.univ-mlv.fr/~lecroq/string/node5.html
#[derive(Clone, Debug)]
pub(crate) struct RabinKarp {
/// The patterns we're searching for.
patterns: Arc<Patterns>,
/// The order of patterns in each bucket is significant. Namely, they are
/// arranged such that the first one to match is the correct match. This
/// may not necessarily correspond to the order provided by the caller.
/// For example, if leftmost-longest semantics are used, then the patterns
/// are sorted by their length in descending order. If leftmost-first
/// semantics are used, then the patterns are sorted by their pattern ID
/// in ascending order (which corresponds to the caller's order).
buckets: Vec<Vec<(Hash, PatternID)>>,
/// The length of the hashing window. Generally, this corresponds to the
/// length of the smallest pattern.
hash_len: usize,
/// The factor to subtract out of a hash before updating it with a new
/// byte.
hash_2pow: usize,
}
impl RabinKarp {
/// Compile a new Rabin-Karp matcher from the patterns given.
///
/// This panics if any of the patterns in the collection are empty, or if
/// the collection is itself empty.
pub(crate) fn new(patterns: &Arc<Patterns>) -> RabinKarp {
assert!(patterns.len() >= 1);
let hash_len = patterns.minimum_len();
assert!(hash_len >= 1);
let mut hash_2pow = 1usize;
for _ in 1..hash_len {
hash_2pow = hash_2pow.wrapping_shl(1);
}
let mut rk = RabinKarp {
patterns: Arc::clone(patterns),
buckets: vec![vec![]; NUM_BUCKETS],
hash_len,
hash_2pow,
};
for (id, pat) in patterns.iter() {
let hash = rk.hash(&pat.bytes()[..rk.hash_len]);
let bucket = hash % NUM_BUCKETS;
rk.buckets[bucket].push((hash, id));
}
rk
}
/// Return the first matching pattern in the given haystack, begining the
/// search at `at`.
pub(crate) fn find_at(
&self,
haystack: &[u8],
mut at: usize,
) -> Option<Match> {
assert_eq!(NUM_BUCKETS, self.buckets.len());
if at + self.hash_len > haystack.len() {
return None;
}
let mut hash = self.hash(&haystack[at..at + self.hash_len]);
loop {
let bucket = &self.buckets[hash % NUM_BUCKETS];
for &(phash, pid) in bucket {
if phash == hash {
if let Some(c) = self.verify(pid, haystack, at) {
return Some(c);
}
}
}
if at + self.hash_len >= haystack.len() {
return None;
}
hash = self.update_hash(
hash,
haystack[at],
haystack[at + self.hash_len],
);
at += 1;
}
}
/// Returns the approximate total amount of heap used by this searcher, in
/// units of bytes.
pub(crate) fn memory_usage(&self) -> usize {
self.buckets.len() * core::mem::size_of::<Vec<(Hash, PatternID)>>()
+ self.patterns.len() * core::mem::size_of::<(Hash, PatternID)>()
}
/// Verify whether the pattern with the given id matches at
/// `haystack[at..]`.
///
/// We tag this function as `cold` because it helps improve codegen.
/// Intuitively, it would seem like inlining it would be better. However,
/// the only time this is called and a match is not found is when there
/// there is a hash collision, or when a prefix of a pattern matches but
/// the entire pattern doesn't match. This is hopefully fairly rare, and
/// if it does occur a lot, it's going to be slow no matter what we do.
#[cold]
fn verify(
&self,
id: PatternID,
haystack: &[u8],
at: usize,
) -> Option<Match> {
let pat = self.patterns.get(id);
if pat.is_prefix(&haystack[at..]) {
Some(Match::new(id, at..at + pat.len()))
} else {
None
}
}
/// Hash the given bytes.
fn hash(&self, bytes: &[u8]) -> Hash {
assert_eq!(self.hash_len, bytes.len());
let mut hash = 0usize;
for &b in bytes {
hash = hash.wrapping_shl(1).wrapping_add(b as usize);
}
hash
}
/// Update the hash given based on removing `old_byte` at the beginning
/// of some byte string, and appending `new_byte` to the end of that same
/// byte string.
fn update_hash(&self, prev: Hash, old_byte: u8, new_byte: u8) -> Hash {
prev.wrapping_sub((old_byte as usize).wrapping_mul(self.hash_2pow))
.wrapping_shl(1)
.wrapping_add(new_byte as usize)
}
}
@@ -0,0 +1,386 @@
Teddy is a SIMD accelerated multiple substring matching algorithm. The name
and the core ideas in the algorithm were learned from the [Hyperscan][1_u]
project. The implementation in this repository was mostly motivated for use in
accelerating regex searches by searching for small sets of required literals
extracted from the regex.
# Background
The key idea of Teddy is to do *packed* substring matching. In the literature,
packed substring matching is the idea of examining multiple bytes in a haystack
at a time to detect matches. Implementations of, for example, memchr (which
detects matches of a single byte) have been doing this for years. Only
recently, with the introduction of various SIMD instructions, has this been
extended to substring matching. The PCMPESTRI instruction (and its relatives),
for example, implements substring matching in hardware. It is, however, limited
to substrings of length 16 bytes or fewer, but this restriction is fine in a
regex engine, since we rarely care about the performance difference between
searching for a 16 byte literal and a 16 + N literal; 16 is already long
enough. The key downside of the PCMPESTRI instruction, on current (2016) CPUs
at least, is its latency and throughput. As a result, it is often faster to
do substring search with a Boyer-Moore (or Two-Way) variant and a well placed
memchr to quickly skip through the haystack.
There are fewer results from the literature on packed substring matching,
and even fewer for packed multiple substring matching. Ben-Kiki et al. [2]
describes use of PCMPESTRI for substring matching, but is mostly theoretical
and hand-waves performance. There is other theoretical work done by Bille [3]
as well.
The rest of the work in the field, as far as I'm aware, is by Faro and Kulekci
and is generally focused on multiple pattern search. Their first paper [4a]
introduces the concept of a fingerprint, which is computed for every block of
N bytes in every pattern. The haystack is then scanned N bytes at a time and
a fingerprint is computed in the same way it was computed for blocks in the
patterns. If the fingerprint corresponds to one that was found in a pattern,
then a verification step follows to confirm that one of the substrings with the
corresponding fingerprint actually matches at the current location. Various
implementation tricks are employed to make sure the fingerprint lookup is fast;
typically by truncating the fingerprint. (This may, of course, provoke more
steps in the verification process, so a balance must be struck.)
The main downside of [4a] is that the minimum substring length is 32 bytes,
presumably because of how the algorithm uses certain SIMD instructions. This
essentially makes it useless for general purpose regex matching, where a small
number of short patterns is far more likely.
Faro and Kulekci published another paper [4b] that is conceptually very similar
to [4a]. The key difference is that it uses the CRC32 instruction (introduced
as part of SSE 4.2) to compute fingerprint values. This also enables the
algorithm to work effectively on substrings as short as 7 bytes with 4 byte
windows. 7 bytes is unfortunately still too long. The window could be
technically shrunk to 2 bytes, thereby reducing minimum length to 3, but the
small window size ends up negating most performance benefits—and it's likely
the common case in a general purpose regex engine.
Faro and Kulekci also published [4c] that appears to be intended as a
replacement to using PCMPESTRI. In particular, it is specifically motivated by
the high throughput/latency time of PCMPESTRI and therefore chooses other SIMD
instructions that are faster. While this approach works for short substrings,
I personally couldn't see a way to generalize it to multiple substring search.
Faro and Kulekci have another paper [4d] that I haven't been able to read
because it is behind a paywall.
# Teddy
Finally, we get to Teddy. If the above literature review is complete, then it
appears that Teddy is a novel algorithm. More than that, in my experience, it
completely blows away the competition for short substrings, which is exactly
what we want in a general purpose regex engine. Again, the algorithm appears
to be developed by the authors of [Hyperscan][1_u]. Hyperscan was open sourced
late 2015, and no earlier history could be found. Therefore, tracking the exact
provenance of the algorithm with respect to the published literature seems
difficult.
At a high level, Teddy works somewhat similarly to the fingerprint algorithms
published by Faro and Kulekci, but Teddy does it in a way that scales a bit
better. Namely:
1. Teddy's core algorithm scans the haystack in 16 (for SSE, or 32 for AVX)
byte chunks. 16 (or 32) is significant because it corresponds to the number
of bytes in a SIMD vector.
2. Bitwise operations are performed on each chunk to discover if any region of
it matches a set of precomputed fingerprints from the patterns. If there are
matches, then a verification step is performed. In this implementation, our
verification step is naive. This can be improved upon.
The details to make this work are quite clever. First, we must choose how to
pick our fingerprints. In Hyperscan's implementation, I *believe* they use the
last N bytes of each substring, where N must be at least the minimum length of
any substring in the set being searched. In this implementation, we use the
first N bytes of each substring. (The tradeoffs between these choices aren't
yet clear to me.) We then must figure out how to quickly test whether an
occurrence of any fingerprint from the set of patterns appears in a 16 byte
block from the haystack. To keep things simple, let's assume N = 1 and examine
some examples to motivate the approach. Here are our patterns:
```ignore
foo
bar
baz
```
The corresponding fingerprints, for N = 1, are `f`, `b` and `b`. Now let's set
our 16 byte block to:
```ignore
bat cat foo bump
xxxxxxxxxxxxxxxx
```
To cut to the chase, Teddy works by using bitsets. In particular, Teddy creates
a mask that allows us to quickly compute membership of a fingerprint in a 16
byte block that also tells which pattern the fingerprint corresponds to. In
this case, our fingerprint is a single byte, so an appropriate abstraction is
a map from a single byte to a list of patterns that contain that fingerprint:
```ignore
f |--> foo
b |--> bar, baz
```
Now, all we need to do is figure out how to represent this map in vector space
and use normal SIMD operations to perform a lookup. The first simplification
we can make is to represent our patterns as bit fields occupying a single
byte. This is important, because a single SIMD vector can store 16 bytes.
```ignore
f |--> 00000001
b |--> 00000010, 00000100
```
How do we perform lookup though? It turns out that SSSE3 introduced a very cool
instruction called PSHUFB. The instruction takes two SIMD vectors, `A` and `B`,
and returns a third vector `C`. All vectors are treated as 16 8-bit integers.
`C` is formed by `C[i] = A[B[i]]`. (This is a bit of a simplification, but true
for the purposes of this algorithm. For full details, see [Intel's Intrinsics
Guide][5_u].) This essentially lets us use the values in `B` to lookup values
in `A`.
If we could somehow cause `B` to contain our 16 byte block from the haystack,
and if `A` could contain our bitmasks, then we'd end up with something like
this for `A`:
```ignore
0x00 0x01 ... 0x62 ... 0x66 ... 0xFF
A = 0 0 00000110 00000001 0
```
And if `B` contains our window from our haystack, we could use shuffle to take
the values from `B` and use them to look up our bitsets in `A`. But of course,
we can't do this because `A` in the above example contains 256 bytes, which
is much larger than the size of a SIMD vector.
Nybbles to the rescue! A nybble is 4 bits. Instead of one mask to hold all of
our bitsets, we can use two masks, where one mask corresponds to the lower four
bits of our fingerprint and the other mask corresponds to the upper four bits.
So our map now looks like:
```ignore
'f' & 0xF = 0x6 |--> 00000001
'f' >> 4 = 0x6 |--> 00000111
'b' & 0xF = 0x2 |--> 00000110
'b' >> 4 = 0x6 |--> 00000111
```
Notice that the bitsets for each nybble correspond to the union of all
fingerprints that contain that nybble. For example, both `f` and `b` have the
same upper 4 bits but differ on the lower 4 bits. Putting this together, we
have `A0`, `A1` and `B`, where `A0` is our mask for the lower nybble, `A1` is
our mask for the upper nybble and `B` is our 16 byte block from the haystack:
```ignore
0x00 0x01 0x02 0x03 ... 0x06 ... 0xF
A0 = 0 0 00000110 0 00000001 0
A1 = 0 0 0 0 00000111 0
B = b a t _ t p
B = 0x62 0x61 0x74 0x20 0x74 0x70
```
But of course, we can't use `B` with `PSHUFB` yet, since its values are 8 bits,
and we need indexes that are at most 4 bits (corresponding to one of 16
values). We can apply the same transformation to split `B` into lower and upper
nybbles as we did `A`. As before, `B0` corresponds to the lower nybbles and
`B1` corresponds to the upper nybbles:
```ignore
b a t _ c a t _ f o o _ b u m p
B0 = 0x2 0x1 0x4 0x0 0x3 0x1 0x4 0x0 0x6 0xF 0xF 0x0 0x2 0x5 0xD 0x0
B1 = 0x6 0x6 0x7 0x2 0x6 0x6 0x7 0x2 0x6 0x6 0x6 0x2 0x6 0x7 0x6 0x7
```
And now we have a nice correspondence. `B0` can index `A0` and `B1` can index
`A1`. Here's what we get when we apply `C0 = PSHUFB(A0, B0)`:
```ignore
b a ... f o ... p
A0[0x2] A0[0x1] A0[0x6] A0[0xF] A0[0x0]
C0 = 00000110 0 00000001 0 0
```
And `C1 = PSHUFB(A1, B1)`:
```ignore
b a ... f o ... p
A1[0x6] A1[0x6] A1[0x6] A1[0x6] A1[0x7]
C1 = 00000111 00000111 00000111 00000111 0
```
Notice how neither one of `C0` or `C1` is guaranteed to report fully correct
results all on its own. For example, `C1` claims that `b` is a fingerprint for
the pattern `foo` (since `A1[0x6] = 00000111`), and that `o` is a fingerprint
for all of our patterns. But if we combined `C0` and `C1` with an `AND`
operation:
```ignore
b a ... f o ... p
C = 00000110 0 00000001 0 0
```
Then we now have that `C[i]` contains a bitset corresponding to the matching
fingerprints in a haystack's 16 byte block, where `i` is the `ith` byte in that
block.
Once we have that, we can look for the position of the least significant bit
in `C`. (Least significant because we only target little endian here. Thus,
the least significant bytes correspond to bytes in our haystack at a lower
address.) That position, modulo `8`, gives us the pattern that the fingerprint
matches. That position, integer divided by `8`, also gives us the byte offset
that the fingerprint occurs in inside the 16 byte haystack block. Using those
two pieces of information, we can run a verification procedure that tries
to match all substrings containing that fingerprint at that position in the
haystack.
# Implementation notes
The problem with the algorithm as described above is that it uses a single byte
for a fingerprint. This will work well if the fingerprints are rare in the
haystack (e.g., capital letters or special characters in normal English text),
but if the fingerprints are common, you'll wind up spending too much time in
the verification step, which effectively negates the performance benefits of
scanning 16 bytes at a time. Remember, the key to the performance of this
algorithm is to do as little work as possible per 16 (or 32) bytes.
This algorithm can be extrapolated in a relatively straight-forward way to use
larger fingerprints. That is, instead of a single byte prefix, we might use a
two or three byte prefix. The implementation here implements N = {1, 2, 3}
and always picks the largest N possible. The rationale is that the bigger the
fingerprint, the fewer verification steps we'll do. Of course, if N is too
large, then we'll end up doing too much on each step.
The way to extend it is:
1. Add a mask for each byte in the fingerprint. (Remember that each mask is
composed of two SIMD vectors.) This results in a value of `C` for each byte
in the fingerprint while searching.
2. When testing each 16 (or 32) byte block, each value of `C` must be shifted
so that they are aligned. Once aligned, they should all be `AND`'d together.
This will give you only the bitsets corresponding to the full match of the
fingerprint. To do this, one needs to save the last byte (for N=2) or last
two bytes (for N=3) from the previous iteration, and then line them up with
the first one or two bytes of the next iteration.
## Verification
Verification generally follows the procedure outlined above. The tricky parts
are in the right formulation of operations to get our bits out of our vectors.
We have a limited set of operations available to us on SIMD vectors as 128-bit
or 256-bit numbers, so we wind up needing to rip out 2 (or 4) 64-bit integers
from our vectors, and then run our verification step on each of those. The
verification step looks at the least significant bit set, and from its
position, we can derive the byte offset and bucket. (Again, as described
above.) Once we know the bucket, we do a fairly naive exhaustive search for
every literal in that bucket. (Hyperscan is a bit smarter here and uses a hash
table, but I haven't had time to thoroughly explore that. A few initial
half-hearted attempts resulted in worse performance.)
## AVX
The AVX version of Teddy extrapolates almost perfectly from the SSE version.
The only hickup is that PALIGNR is used to align chunks in the 16-bit version,
and there is no equivalent instruction in AVX. AVX does have VPALIGNR, but it
only works within 128-bit lanes. So there's a bit of tomfoolery to get around
this by shuffling the vectors before calling VPALIGNR.
The only other aspect to AVX is that since our masks are still fundamentally
16-bytes (0x0-0xF), they are duplicated to 32-bytes, so that they can apply to
32-byte chunks.
## Fat Teddy
In the version of Teddy described above, 8 buckets are used to group patterns
that we want to search for. However, when AVX is available, we can extend the
number of buckets to 16 by permitting each byte in our masks to use 16-bits
instead of 8-bits to represent the buckets it belongs to. (This variant is also
in Hyperscan.) However, what we give up is the ability to scan 32 bytes at a
time, even though we're using AVX. Instead, we have to scan 16 bytes at a time.
What we gain, though, is (hopefully) less work in our verification routine.
It patterns are more spread out across more buckets, then there should overall
be fewer false positives. In general, Fat Teddy permits us to grow our capacity
a bit and search for more literals before Teddy gets overwhelmed.
The tricky part of Fat Teddy is in how we adjust our masks and our verification
procedure. For the masks, we simply represent the first 8 buckets in each of
the low 16 bytes, and then the second 8 buckets in each of the high 16 bytes.
Then, in the search loop, instead of loading 32 bytes from the haystack, we
load the same 16 bytes from the haystack into both the low and high 16 byte
portions of our 256-bit vector. So for example, a mask might look like this:
bits: 00100001 00000000 ... 11000000 00000000 00000001 ... 00000000
byte: 31 30 16 15 14 0
offset: 15 14 0 15 14 0
buckets: 8-15 8-15 8-15 0-7 0-7 0-7
Where `byte` is the position in the vector (higher numbers corresponding to
more significant bits), `offset` is the corresponding position in the haystack
chunk, and `buckets` corresponds to the bucket assignments for that particular
byte.
In particular, notice that the bucket assignments for offset `0` are spread
out between bytes `0` and `16`. This works well for the chunk-by-chunk search
procedure, but verification really wants to process all bucket assignments for
each offset at once. Otherwise, we might wind up finding a match at offset
`1` in one the first 8 buckets, when we really should have reported a match
at offset `0` in one of the second 8 buckets. (Because we want the leftmost
match.)
Thus, for verification, we rearrange the above vector such that it is a
sequence of 16-bit integers, where the least significant 16-bit integer
corresponds to all of the bucket assignments for offset `0`. So with the
above vector, the least significant 16-bit integer would be
11000000 000000
which was taken from bytes `16` and `0`. Then the verification step pretty much
runs as described, except with 16 buckets instead of 8.
# References
- **[1]** [Hyperscan on GitHub](https://github.com/intel/hyperscan),
[webpage](https://www.hyperscan.io/)
- **[2a]** Ben-Kiki, O., Bille, P., Breslauer, D., Gasieniec, L., Grossi, R.,
& Weimann, O. (2011).
_Optimal packed string matching_.
In LIPIcs-Leibniz International Proceedings in Informatics (Vol. 13).
Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik.
DOI: 10.4230/LIPIcs.FSTTCS.2011.423.
[PDF](https://drops.dagstuhl.de/opus/volltexte/2011/3355/pdf/37.pdf).
- **[2b]** Ben-Kiki, O., Bille, P., Breslauer, D., Ga̧sieniec, L., Grossi, R.,
& Weimann, O. (2014).
_Towards optimal packed string matching_.
Theoretical Computer Science, 525, 111-129.
DOI: 10.1016/j.tcs.2013.06.013.
[PDF](https://www.cs.haifa.ac.il/~oren/Publications/bpsm.pdf).
- **[3]** Bille, P. (2011).
_Fast searching in packed strings_.
Journal of Discrete Algorithms, 9(1), 49-56.
DOI: 10.1016/j.jda.2010.09.003.
[PDF](https://www.sciencedirect.com/science/article/pii/S1570866710000353).
- **[4a]** Faro, S., & Külekci, M. O. (2012, October).
_Fast multiple string matching using streaming SIMD extensions technology_.
In String Processing and Information Retrieval (pp. 217-228).
Springer Berlin Heidelberg.
DOI: 10.1007/978-3-642-34109-0_23.
[PDF](https://www.dmi.unict.it/faro/papers/conference/faro32.pdf).
- **[4b]** Faro, S., & Külekci, M. O. (2013, September).
_Towards a Very Fast Multiple String Matching Algorithm for Short Patterns_.
In Stringology (pp. 78-91).
[PDF](https://www.dmi.unict.it/faro/papers/conference/faro36.pdf).
- **[4c]** Faro, S., & Külekci, M. O. (2013, January).
_Fast packed string matching for short patterns_.
In Proceedings of the Meeting on Algorithm Engineering & Expermiments
(pp. 113-121).
Society for Industrial and Applied Mathematics.
[PDF](https://arxiv.org/pdf/1209.6449.pdf).
- **[4d]** Faro, S., & Külekci, M. O. (2014).
_Fast and flexible packed string matching_.
Journal of Discrete Algorithms, 28, 61-72.
DOI: 10.1016/j.jda.2014.07.003.
[1_u]: https://github.com/intel/hyperscan
[5_u]: https://software.intel.com/sites/landingpage/IntrinsicsGuide
@@ -0,0 +1,792 @@
use core::{
fmt::Debug,
panic::{RefUnwindSafe, UnwindSafe},
};
use alloc::sync::Arc;
use crate::packed::{ext::Pointer, pattern::Patterns, teddy::generic::Match};
/// A builder for constructing a Teddy matcher.
///
/// The builder primarily permits fine grained configuration of the Teddy
/// matcher. Most options are made only available for testing/benchmarking
/// purposes. In reality, options are automatically determined by the nature
/// and number of patterns given to the builder.
#[derive(Clone, Debug)]
pub(crate) struct Builder {
/// When none, this is automatically determined. Otherwise, `false` means
/// slim Teddy is used (8 buckets) and `true` means fat Teddy is used
/// (16 buckets). Fat Teddy requires AVX2, so if that CPU feature isn't
/// available and Fat Teddy was requested, no matcher will be built.
only_fat: Option<bool>,
/// When none, this is automatically determined. Otherwise, `false` means
/// that 128-bit vectors will be used (up to SSSE3 instructions) where as
/// `true` means that 256-bit vectors will be used. As with `fat`, if
/// 256-bit vectors are requested and they aren't available, then a
/// searcher will not be built.
only_256bit: Option<bool>,
/// When true (the default), the number of patterns will be used as a
/// heuristic for refusing construction of a Teddy searcher. The point here
/// is that too many patterns can overwhelm Teddy. But this can be disabled
/// in cases where the caller knows better.
heuristic_pattern_limits: bool,
}
impl Default for Builder {
fn default() -> Builder {
Builder::new()
}
}
impl Builder {
/// Create a new builder for configuring a Teddy matcher.
pub(crate) fn new() -> Builder {
Builder {
only_fat: None,
only_256bit: None,
heuristic_pattern_limits: true,
}
}
/// Build a matcher for the set of patterns given. If a matcher could not
/// be built, then `None` is returned.
///
/// Generally, a matcher isn't built if the necessary CPU features aren't
/// available, an unsupported target or if the searcher is believed to be
/// slower than standard techniques (i.e., if there are too many literals).
pub(crate) fn build(&self, patterns: Arc<Patterns>) -> Option<Searcher> {
self.build_imp(patterns)
}
/// Require the use of Fat (true) or Slim (false) Teddy. Fat Teddy uses
/// 16 buckets where as Slim Teddy uses 8 buckets. More buckets are useful
/// for a larger set of literals.
///
/// `None` is the default, which results in an automatic selection based
/// on the number of literals and available CPU features.
pub(crate) fn only_fat(&mut self, yes: Option<bool>) -> &mut Builder {
self.only_fat = yes;
self
}
/// Request the use of 256-bit vectors (true) or 128-bit vectors (false).
/// Generally, a larger vector size is better since it either permits
/// matching more patterns or matching more bytes in the haystack at once.
///
/// `None` is the default, which results in an automatic selection based on
/// the number of literals and available CPU features.
pub(crate) fn only_256bit(&mut self, yes: Option<bool>) -> &mut Builder {
self.only_256bit = yes;
self
}
/// Request that heuristic limitations on the number of patterns be
/// employed. This useful to disable for benchmarking where one wants to
/// explore how Teddy performs on large number of patterns even if the
/// heuristics would otherwise refuse construction.
///
/// This is enabled by default.
pub(crate) fn heuristic_pattern_limits(
&mut self,
yes: bool,
) -> &mut Builder {
self.heuristic_pattern_limits = yes;
self
}
fn build_imp(&self, patterns: Arc<Patterns>) -> Option<Searcher> {
let patlimit = self.heuristic_pattern_limits;
// There's no particular reason why we limit ourselves to little endian
// here, but it seems likely that some parts of Teddy as they are
// currently written (e.g., the uses of `trailing_zeros`) are likely
// wrong on non-little-endian targets. Such things are likely easy to
// fix, but at the time of writing (2023/09/18), I actually do not know
// how to test this code on a big-endian target. So for now, we're
// conservative and just bail out.
if !cfg!(target_endian = "little") {
debug!("skipping Teddy because target isn't little endian");
return None;
}
// Too many patterns will overwhelm Teddy and likely lead to slow
// downs, typically in the verification step.
if patlimit && patterns.len() > 64 {
debug!("skipping Teddy because of too many patterns");
return None;
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
{
use self::x86_64::{FatAVX2, SlimAVX2, SlimSSSE3};
let mask_len = core::cmp::min(4, patterns.minimum_len());
let beefy = patterns.len() > 32;
let has_avx2 = self::x86_64::is_available_avx2();
let has_ssse3 = has_avx2 || self::x86_64::is_available_ssse3();
let use_avx2 = if self.only_256bit == Some(true) {
if !has_avx2 {
debug!(
"skipping Teddy because avx2 was demanded but unavailable"
);
return None;
}
true
} else if self.only_256bit == Some(false) {
if !has_ssse3 {
debug!(
"skipping Teddy because ssse3 was demanded but unavailable"
);
return None;
}
false
} else if !has_ssse3 && !has_avx2 {
debug!(
"skipping Teddy because ssse3 and avx2 are unavailable"
);
return None;
} else {
has_avx2
};
let fat = match self.only_fat {
None => use_avx2 && beefy,
Some(false) => false,
Some(true) if !use_avx2 => {
debug!(
"skipping Teddy because fat was demanded, but fat \
Teddy requires avx2 which is unavailable"
);
return None;
}
Some(true) => true,
};
// Just like for aarch64, it's possible that too many patterns will
// overhwelm Teddy. Unlike aarch64 though, we have Fat teddy which
// helps things scale a bit more by spreading patterns over more
// buckets.
//
// These thresholds were determined by looking at the measurements
// for the rust/aho-corasick/packed/leftmost-first and
// rust/aho-corasick/dfa/leftmost-first engines on the `teddy/`
// benchmarks.
if patlimit && mask_len == 1 && patterns.len() > 16 {
debug!(
"skipping Teddy (mask len: 1) because there are \
too many patterns",
);
return None;
}
match (mask_len, use_avx2, fat) {
(1, false, _) => {
debug!("Teddy choice: 128-bit slim, 1 byte");
SlimSSSE3::<1>::new(&patterns)
}
(1, true, false) => {
debug!("Teddy choice: 256-bit slim, 1 byte");
SlimAVX2::<1>::new(&patterns)
}
(1, true, true) => {
debug!("Teddy choice: 256-bit fat, 1 byte");
FatAVX2::<1>::new(&patterns)
}
(2, false, _) => {
debug!("Teddy choice: 128-bit slim, 2 bytes");
SlimSSSE3::<2>::new(&patterns)
}
(2, true, false) => {
debug!("Teddy choice: 256-bit slim, 2 bytes");
SlimAVX2::<2>::new(&patterns)
}
(2, true, true) => {
debug!("Teddy choice: 256-bit fat, 2 bytes");
FatAVX2::<2>::new(&patterns)
}
(3, false, _) => {
debug!("Teddy choice: 128-bit slim, 3 bytes");
SlimSSSE3::<3>::new(&patterns)
}
(3, true, false) => {
debug!("Teddy choice: 256-bit slim, 3 bytes");
SlimAVX2::<3>::new(&patterns)
}
(3, true, true) => {
debug!("Teddy choice: 256-bit fat, 3 bytes");
FatAVX2::<3>::new(&patterns)
}
(4, false, _) => {
debug!("Teddy choice: 128-bit slim, 4 bytes");
SlimSSSE3::<4>::new(&patterns)
}
(4, true, false) => {
debug!("Teddy choice: 256-bit slim, 4 bytes");
SlimAVX2::<4>::new(&patterns)
}
(4, true, true) => {
debug!("Teddy choice: 256-bit fat, 4 bytes");
FatAVX2::<4>::new(&patterns)
}
_ => {
debug!("no supported Teddy configuration found");
None
}
}
}
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
))]
{
use self::aarch64::SlimNeon;
let mask_len = core::cmp::min(4, patterns.minimum_len());
if self.only_256bit == Some(true) {
debug!(
"skipping Teddy because 256-bits were demanded \
but unavailable"
);
return None;
}
if self.only_fat == Some(true) {
debug!(
"skipping Teddy because fat was demanded but unavailable"
);
}
// Since we don't have Fat teddy in aarch64 (I think we'd want at
// least 256-bit vectors for that), we need to be careful not to
// allow too many patterns as it might overwhelm Teddy. Generally
// speaking, as the mask length goes up, the more patterns we can
// handle because the mask length results in fewer candidates
// generated.
//
// These thresholds were determined by looking at the measurements
// for the rust/aho-corasick/packed/leftmost-first and
// rust/aho-corasick/dfa/leftmost-first engines on the `teddy/`
// benchmarks.
match mask_len {
1 => {
if patlimit && patterns.len() > 16 {
debug!(
"skipping Teddy (mask len: 1) because there are \
too many patterns",
);
}
debug!("Teddy choice: 128-bit slim, 1 byte");
SlimNeon::<1>::new(&patterns)
}
2 => {
if patlimit && patterns.len() > 32 {
debug!(
"skipping Teddy (mask len: 2) because there are \
too many patterns",
);
}
debug!("Teddy choice: 128-bit slim, 2 bytes");
SlimNeon::<2>::new(&patterns)
}
3 => {
if patlimit && patterns.len() > 48 {
debug!(
"skipping Teddy (mask len: 3) because there are \
too many patterns",
);
}
debug!("Teddy choice: 128-bit slim, 3 bytes");
SlimNeon::<3>::new(&patterns)
}
4 => {
debug!("Teddy choice: 128-bit slim, 4 bytes");
SlimNeon::<4>::new(&patterns)
}
_ => {
debug!("no supported Teddy configuration found");
None
}
}
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "sse2"),
all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
)
)))]
{
None
}
}
}
/// A searcher that dispatches to one of several possible Teddy variants.
#[derive(Clone, Debug)]
pub(crate) struct Searcher {
/// The Teddy variant we use. We use dynamic dispatch under the theory that
/// it results in better codegen then a enum, although this is a specious
/// claim.
///
/// This `Searcher` is essentially a wrapper for a `SearcherT` trait
/// object. We just make `memory_usage` and `minimum_len` available without
/// going through dynamic dispatch.
imp: Arc<dyn SearcherT>,
/// Total heap memory used by the Teddy variant.
memory_usage: usize,
/// The minimum haystack length this searcher can handle. It is intended
/// for callers to use some other search routine (such as Rabin-Karp) in
/// cases where the haystack (or remainer of the haystack) is too short.
minimum_len: usize,
}
impl Searcher {
/// Look for the leftmost occurrence of any pattern in this search in the
/// given haystack starting at the given position.
///
/// # Panics
///
/// This panics when `haystack[at..].len()` is less than the minimum length
/// for this haystack.
#[inline(always)]
pub(crate) fn find(
&self,
haystack: &[u8],
at: usize,
) -> Option<crate::Match> {
// SAFETY: The Teddy implementations all require a minimum haystack
// length, and this is required for safety. Therefore, we assert it
// here in order to make this method sound.
assert!(haystack[at..].len() >= self.minimum_len);
let hayptr = haystack.as_ptr();
// SAFETY: Construction of the searcher guarantees that we are able
// to run it in the current environment (i.e., we won't get an AVX2
// searcher on a x86-64 CPU without AVX2 support). Also, the pointers
// are valid as they are derived directly from a borrowed slice.
let teddym = unsafe {
self.imp.find(hayptr.add(at), hayptr.add(haystack.len()))?
};
let start = teddym.start().as_usize().wrapping_sub(hayptr.as_usize());
let end = teddym.end().as_usize().wrapping_sub(hayptr.as_usize());
let span = crate::Span { start, end };
// OK because we won't permit the construction of a searcher that
// could report a pattern ID bigger than what can fit in the crate-wide
// PatternID type.
let pid = crate::PatternID::new_unchecked(teddym.pattern().as_usize());
let m = crate::Match::new(pid, span);
Some(m)
}
/// Returns the approximate total amount of heap used by this type, in
/// units of bytes.
#[inline(always)]
pub(crate) fn memory_usage(&self) -> usize {
self.memory_usage
}
/// Returns the minimum length, in bytes, that a haystack must be in order
/// to use it with this searcher.
#[inline(always)]
pub(crate) fn minimum_len(&self) -> usize {
self.minimum_len
}
}
/// A trait that provides dynamic dispatch over the different possible Teddy
/// variants on the same algorithm.
///
/// On `x86_64` for example, it isn't known until runtime which of 12 possible
/// variants will be used. One might use one of the four slim 128-bit vector
/// variants, or one of the four 256-bit vector variants or even one of the
/// four fat 256-bit vector variants.
///
/// Since this choice is generally made when the Teddy searcher is constructed
/// and this choice is based on the patterns given and what the current CPU
/// supports, it follows that there must be some kind of indirection at search
/// time that "selects" the variant chosen at build time.
///
/// There are a few different ways to go about this. One approach is to use an
/// enum. It works fine, but in my experiments, this generally results in worse
/// codegen. Another approach, which is what we use here, is dynamic dispatch
/// via a trait object. We basically implement this trait for each possible
/// variant, select the variant we want at build time and convert it to a
/// trait object for use at search time.
///
/// Another approach is to use function pointers and stick each of the possible
/// variants into a union. This is essentially isomorphic to the dynamic
/// dispatch approach, but doesn't require any allocations. Since this crate
/// requires `alloc`, there's no real reason (AFAIK) to go down this path. (The
/// `memchr` crate does this.)
trait SearcherT:
Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static
{
/// Execute a search on the given haystack (identified by `start` and `end`
/// raw pointers).
///
/// # Safety
///
/// Essentially, the `start` and `end` pointers must be valid and point
/// to a haystack one can read. As long as you derive them from, for
/// example, a `&[u8]`, they should automatically satisfy all of the safety
/// obligations:
///
/// * Both `start` and `end` must be valid for reads.
/// * Both `start` and `end` must point to an initialized value.
/// * Both `start` and `end` must point to the same allocated object and
/// must either be in bounds or at most one byte past the end of the
/// allocated object.
/// * Both `start` and `end` must be _derived from_ a pointer to the same
/// object.
/// * The distance between `start` and `end` must not overflow `isize`.
/// * The distance being in bounds must not rely on "wrapping around" the
/// address space.
/// * It must be the case that `start <= end`.
/// * `end - start` must be greater than the minimum length for this
/// searcher.
///
/// Also, it is expected that implementations of this trait will tag this
/// method with a `target_feature` attribute. Callers must ensure that
/// they are executing this method in an environment where that attribute
/// is valid.
unsafe fn find(&self, start: *const u8, end: *const u8) -> Option<Match>;
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
mod x86_64 {
use core::arch::x86_64::{__m128i, __m256i};
use alloc::sync::Arc;
use crate::packed::{
ext::Pointer,
pattern::Patterns,
teddy::generic::{self, Match},
};
use super::{Searcher, SearcherT};
#[derive(Clone, Debug)]
pub(super) struct SlimSSSE3<const BYTES: usize> {
slim128: generic::Slim<__m128i, BYTES>,
}
// Defines SlimSSSE3 wrapper functions for 1, 2, 3 and 4 bytes.
macro_rules! slim_ssse3 {
($len:expr) => {
impl SlimSSSE3<$len> {
/// Creates a new searcher using "slim" Teddy with 128-bit
/// vectors. If SSSE3 is not available in the current
/// environment, then this returns `None`.
pub(super) fn new(
patterns: &Arc<Patterns>,
) -> Option<Searcher> {
if !is_available_ssse3() {
return None;
}
Some(unsafe { SlimSSSE3::<$len>::new_unchecked(patterns) })
}
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors without checking whether SSSE3 is available or not.
///
/// # Safety
///
/// Callers must ensure that SSSE3 is available in the current
/// environment.
#[target_feature(enable = "ssse3")]
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
let slim128 = generic::Slim::<__m128i, $len>::new(
Arc::clone(patterns),
);
let memory_usage = slim128.memory_usage();
let minimum_len = slim128.minimum_len();
let imp = Arc::new(SlimSSSE3 { slim128 });
Searcher { imp, memory_usage, minimum_len }
}
}
impl SearcherT for SlimSSSE3<$len> {
#[target_feature(enable = "ssse3")]
#[inline]
unsafe fn find(
&self,
start: *const u8,
end: *const u8,
) -> Option<Match> {
// SAFETY: All obligations except for `target_feature` are
// passed to the caller. Our use of `target_feature` is
// safe because construction of this type requires that the
// requisite target features are available.
self.slim128.find(start, end)
}
}
};
}
slim_ssse3!(1);
slim_ssse3!(2);
slim_ssse3!(3);
slim_ssse3!(4);
#[derive(Clone, Debug)]
pub(super) struct SlimAVX2<const BYTES: usize> {
slim128: generic::Slim<__m128i, BYTES>,
slim256: generic::Slim<__m256i, BYTES>,
}
// Defines SlimAVX2 wrapper functions for 1, 2, 3 and 4 bytes.
macro_rules! slim_avx2 {
($len:expr) => {
impl SlimAVX2<$len> {
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors. If AVX2 is not available in the current
/// environment, then this returns `None`.
pub(super) fn new(
patterns: &Arc<Patterns>,
) -> Option<Searcher> {
if !is_available_avx2() {
return None;
}
Some(unsafe { SlimAVX2::<$len>::new_unchecked(patterns) })
}
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors without checking whether AVX2 is available or not.
///
/// # Safety
///
/// Callers must ensure that AVX2 is available in the current
/// environment.
#[target_feature(enable = "avx2")]
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
let slim128 = generic::Slim::<__m128i, $len>::new(
Arc::clone(&patterns),
);
let slim256 = generic::Slim::<__m256i, $len>::new(
Arc::clone(&patterns),
);
let memory_usage =
slim128.memory_usage() + slim256.memory_usage();
let minimum_len = slim128.minimum_len();
let imp = Arc::new(SlimAVX2 { slim128, slim256 });
Searcher { imp, memory_usage, minimum_len }
}
}
impl SearcherT for SlimAVX2<$len> {
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn find(
&self,
start: *const u8,
end: *const u8,
) -> Option<Match> {
// SAFETY: All obligations except for `target_feature` are
// passed to the caller. Our use of `target_feature` is
// safe because construction of this type requires that the
// requisite target features are available.
let len = end.distance(start);
if len < self.slim256.minimum_len() {
self.slim128.find(start, end)
} else {
self.slim256.find(start, end)
}
}
}
};
}
slim_avx2!(1);
slim_avx2!(2);
slim_avx2!(3);
slim_avx2!(4);
#[derive(Clone, Debug)]
pub(super) struct FatAVX2<const BYTES: usize> {
fat256: generic::Fat<__m256i, BYTES>,
}
// Defines SlimAVX2 wrapper functions for 1, 2, 3 and 4 bytes.
macro_rules! fat_avx2 {
($len:expr) => {
impl FatAVX2<$len> {
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors. If AVX2 is not available in the current
/// environment, then this returns `None`.
pub(super) fn new(
patterns: &Arc<Patterns>,
) -> Option<Searcher> {
if !is_available_avx2() {
return None;
}
Some(unsafe { FatAVX2::<$len>::new_unchecked(patterns) })
}
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors without checking whether AVX2 is available or not.
///
/// # Safety
///
/// Callers must ensure that AVX2 is available in the current
/// environment.
#[target_feature(enable = "avx2")]
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
let fat256 = generic::Fat::<__m256i, $len>::new(
Arc::clone(&patterns),
);
let memory_usage = fat256.memory_usage();
let minimum_len = fat256.minimum_len();
let imp = Arc::new(FatAVX2 { fat256 });
Searcher { imp, memory_usage, minimum_len }
}
}
impl SearcherT for FatAVX2<$len> {
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn find(
&self,
start: *const u8,
end: *const u8,
) -> Option<Match> {
// SAFETY: All obligations except for `target_feature` are
// passed to the caller. Our use of `target_feature` is
// safe because construction of this type requires that the
// requisite target features are available.
self.fat256.find(start, end)
}
}
};
}
fat_avx2!(1);
fat_avx2!(2);
fat_avx2!(3);
fat_avx2!(4);
#[inline]
pub(super) fn is_available_ssse3() -> bool {
#[cfg(not(target_feature = "sse2"))]
{
false
}
#[cfg(target_feature = "sse2")]
{
#[cfg(target_feature = "ssse3")]
{
true
}
#[cfg(not(target_feature = "ssse3"))]
{
#[cfg(feature = "std")]
{
std::is_x86_feature_detected!("ssse3")
}
#[cfg(not(feature = "std"))]
{
false
}
}
}
}
#[inline]
pub(super) fn is_available_avx2() -> bool {
#[cfg(not(target_feature = "sse2"))]
{
false
}
#[cfg(target_feature = "sse2")]
{
#[cfg(target_feature = "avx2")]
{
true
}
#[cfg(not(target_feature = "avx2"))]
{
#[cfg(feature = "std")]
{
std::is_x86_feature_detected!("avx2")
}
#[cfg(not(feature = "std"))]
{
false
}
}
}
}
}
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
target_endian = "little"
))]
mod aarch64 {
use core::arch::aarch64::uint8x16_t;
use alloc::sync::Arc;
use crate::packed::{
pattern::Patterns,
teddy::generic::{self, Match},
};
use super::{Searcher, SearcherT};
#[derive(Clone, Debug)]
pub(super) struct SlimNeon<const BYTES: usize> {
slim128: generic::Slim<uint8x16_t, BYTES>,
}
// Defines SlimSSSE3 wrapper functions for 1, 2, 3 and 4 bytes.
macro_rules! slim_neon {
($len:expr) => {
impl SlimNeon<$len> {
/// Creates a new searcher using "slim" Teddy with 128-bit
/// vectors. If SSSE3 is not available in the current
/// environment, then this returns `None`.
pub(super) fn new(
patterns: &Arc<Patterns>,
) -> Option<Searcher> {
Some(unsafe { SlimNeon::<$len>::new_unchecked(patterns) })
}
/// Creates a new searcher using "slim" Teddy with 256-bit
/// vectors without checking whether SSSE3 is available or not.
///
/// # Safety
///
/// Callers must ensure that SSSE3 is available in the current
/// environment.
#[target_feature(enable = "neon")]
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
let slim128 = generic::Slim::<uint8x16_t, $len>::new(
Arc::clone(patterns),
);
let memory_usage = slim128.memory_usage();
let minimum_len = slim128.minimum_len();
let imp = Arc::new(SlimNeon { slim128 });
Searcher { imp, memory_usage, minimum_len }
}
}
impl SearcherT for SlimNeon<$len> {
#[target_feature(enable = "neon")]
#[inline]
unsafe fn find(
&self,
start: *const u8,
end: *const u8,
) -> Option<Match> {
// SAFETY: All obligations except for `target_feature` are
// passed to the caller. Our use of `target_feature` is
// safe because construction of this type requires that the
// requisite target features are available.
self.slim128.find(start, end)
}
}
};
}
slim_neon!(1);
slim_neon!(2);
slim_neon!(3);
slim_neon!(4);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
// Regrettable, but Teddy stuff just isn't used on all targets. And for some
// targets, like aarch64, only "slim" Teddy is used and so "fat" Teddy gets a
// bunch of dead-code warnings. Just not worth trying to squash them. Blech.
#![allow(dead_code)]
pub(crate) use self::builder::{Builder, Searcher};
mod builder;
mod generic;
+583
View File
@@ -0,0 +1,583 @@
use std::collections::HashMap;
use alloc::{
format,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::{
packed::{Config, MatchKind},
util::search::Match,
};
/// A description of a single test against a multi-pattern searcher.
///
/// A single test may not necessarily pass on every configuration of a
/// searcher. The tests are categorized and grouped appropriately below.
#[derive(Clone, Debug, Eq, PartialEq)]
struct SearchTest {
/// The name of this test, for debugging.
name: &'static str,
/// The patterns to search for.
patterns: &'static [&'static str],
/// The text to search.
haystack: &'static str,
/// Each match is a triple of (pattern_index, start, end), where
/// pattern_index is an index into `patterns` and `start`/`end` are indices
/// into `haystack`.
matches: &'static [(usize, usize, usize)],
}
struct SearchTestOwned {
offset: usize,
name: String,
patterns: Vec<String>,
haystack: String,
matches: Vec<(usize, usize, usize)>,
}
impl SearchTest {
fn variations(&self) -> Vec<SearchTestOwned> {
let count = if cfg!(miri) { 1 } else { 261 };
let mut tests = vec![];
for i in 0..count {
tests.push(self.offset_prefix(i));
tests.push(self.offset_suffix(i));
tests.push(self.offset_both(i));
}
tests
}
fn offset_both(&self, off: usize) -> SearchTestOwned {
SearchTestOwned {
offset: off,
name: self.name.to_string(),
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
haystack: format!(
"{}{}{}",
"Z".repeat(off),
self.haystack,
"Z".repeat(off)
),
matches: self
.matches
.iter()
.map(|&(id, s, e)| (id, s + off, e + off))
.collect(),
}
}
fn offset_prefix(&self, off: usize) -> SearchTestOwned {
SearchTestOwned {
offset: off,
name: self.name.to_string(),
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
haystack: format!("{}{}", "Z".repeat(off), self.haystack),
matches: self
.matches
.iter()
.map(|&(id, s, e)| (id, s + off, e + off))
.collect(),
}
}
fn offset_suffix(&self, off: usize) -> SearchTestOwned {
SearchTestOwned {
offset: off,
name: self.name.to_string(),
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
haystack: format!("{}{}", self.haystack, "Z".repeat(off)),
matches: self.matches.to_vec(),
}
}
}
/// Short-hand constructor for SearchTest. We use it a lot below.
macro_rules! t {
($name:ident, $patterns:expr, $haystack:expr, $matches:expr) => {
SearchTest {
name: stringify!($name),
patterns: $patterns,
haystack: $haystack,
matches: $matches,
}
};
}
/// A collection of test groups.
type TestCollection = &'static [&'static [SearchTest]];
// Define several collections corresponding to the different type of match
// semantics supported. These collections have some overlap, but each
// collection should have some tests that no other collection has.
/// Tests for leftmost-first match semantics.
const PACKED_LEFTMOST_FIRST: TestCollection =
&[BASICS, LEFTMOST, LEFTMOST_FIRST, REGRESSION, TEDDY];
/// Tests for leftmost-longest match semantics.
const PACKED_LEFTMOST_LONGEST: TestCollection =
&[BASICS, LEFTMOST, LEFTMOST_LONGEST, REGRESSION, TEDDY];
// Now define the individual tests that make up the collections above.
/// A collection of tests for the that should always be true regardless of
/// match semantics. That is, all combinations of leftmost-{first, longest}
/// should produce the same answer.
const BASICS: &'static [SearchTest] = &[
t!(basic001, &["a"], "", &[]),
t!(basic010, &["a"], "a", &[(0, 0, 1)]),
t!(basic020, &["a"], "aa", &[(0, 0, 1), (0, 1, 2)]),
t!(basic030, &["a"], "aaa", &[(0, 0, 1), (0, 1, 2), (0, 2, 3)]),
t!(basic040, &["a"], "aba", &[(0, 0, 1), (0, 2, 3)]),
t!(basic050, &["a"], "bba", &[(0, 2, 3)]),
t!(basic060, &["a"], "bbb", &[]),
t!(basic070, &["a"], "bababbbba", &[(0, 1, 2), (0, 3, 4), (0, 8, 9)]),
t!(basic100, &["aa"], "", &[]),
t!(basic110, &["aa"], "aa", &[(0, 0, 2)]),
t!(basic120, &["aa"], "aabbaa", &[(0, 0, 2), (0, 4, 6)]),
t!(basic130, &["aa"], "abbab", &[]),
t!(basic140, &["aa"], "abbabaa", &[(0, 5, 7)]),
t!(basic150, &["aaa"], "aaa", &[(0, 0, 3)]),
t!(basic200, &["abc"], "abc", &[(0, 0, 3)]),
t!(basic210, &["abc"], "zazabzabcz", &[(0, 6, 9)]),
t!(basic220, &["abc"], "zazabczabcz", &[(0, 3, 6), (0, 7, 10)]),
t!(basic230, &["abcd"], "abcd", &[(0, 0, 4)]),
t!(basic240, &["abcd"], "zazabzabcdz", &[(0, 6, 10)]),
t!(basic250, &["abcd"], "zazabcdzabcdz", &[(0, 3, 7), (0, 8, 12)]),
t!(basic300, &["a", "b"], "", &[]),
t!(basic310, &["a", "b"], "z", &[]),
t!(basic320, &["a", "b"], "b", &[(1, 0, 1)]),
t!(basic330, &["a", "b"], "a", &[(0, 0, 1)]),
t!(
basic340,
&["a", "b"],
"abba",
&[(0, 0, 1), (1, 1, 2), (1, 2, 3), (0, 3, 4),]
),
t!(
basic350,
&["b", "a"],
"abba",
&[(1, 0, 1), (0, 1, 2), (0, 2, 3), (1, 3, 4),]
),
t!(basic360, &["abc", "bc"], "xbc", &[(1, 1, 3),]),
t!(basic400, &["foo", "bar"], "", &[]),
t!(basic410, &["foo", "bar"], "foobar", &[(0, 0, 3), (1, 3, 6),]),
t!(basic420, &["foo", "bar"], "barfoo", &[(1, 0, 3), (0, 3, 6),]),
t!(basic430, &["foo", "bar"], "foofoo", &[(0, 0, 3), (0, 3, 6),]),
t!(basic440, &["foo", "bar"], "barbar", &[(1, 0, 3), (1, 3, 6),]),
t!(basic450, &["foo", "bar"], "bafofoo", &[(0, 4, 7),]),
t!(basic460, &["bar", "foo"], "bafofoo", &[(1, 4, 7),]),
t!(basic470, &["foo", "bar"], "fobabar", &[(1, 4, 7),]),
t!(basic480, &["bar", "foo"], "fobabar", &[(0, 4, 7),]),
t!(basic700, &["yabcdef", "abcdezghi"], "yabcdefghi", &[(0, 0, 7),]),
t!(basic710, &["yabcdef", "abcdezghi"], "yabcdezghi", &[(1, 1, 10),]),
t!(
basic720,
&["yabcdef", "bcdeyabc", "abcdezghi"],
"yabcdezghi",
&[(2, 1, 10),]
),
t!(basic810, &["abcd", "bcd", "cd"], "abcd", &[(0, 0, 4),]),
t!(basic820, &["bcd", "cd", "abcd"], "abcd", &[(2, 0, 4),]),
t!(basic830, &["abc", "bc"], "zazabcz", &[(0, 3, 6),]),
t!(
basic840,
&["ab", "ba"],
"abababa",
&[(0, 0, 2), (0, 2, 4), (0, 4, 6),]
),
t!(basic850, &["foo", "foo"], "foobarfoo", &[(0, 0, 3), (0, 6, 9),]),
];
/// Tests for leftmost match semantics. These should pass for both
/// leftmost-first and leftmost-longest match kinds. Stated differently, among
/// ambiguous matches, the longest match and the match that appeared first when
/// constructing the automaton should always be the same.
const LEFTMOST: &'static [SearchTest] = &[
t!(leftmost000, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
t!(leftmost030, &["a", "ab"], "aa", &[(0, 0, 1), (0, 1, 2)]),
t!(leftmost031, &["ab", "a"], "aa", &[(1, 0, 1), (1, 1, 2)]),
t!(leftmost032, &["ab", "a"], "xayabbbz", &[(1, 1, 2), (0, 3, 5)]),
t!(leftmost300, &["abcd", "bce", "b"], "abce", &[(1, 1, 4)]),
t!(leftmost310, &["abcd", "ce", "bc"], "abce", &[(2, 1, 3)]),
t!(leftmost320, &["abcd", "bce", "ce", "b"], "abce", &[(1, 1, 4)]),
t!(leftmost330, &["abcd", "bce", "cz", "bc"], "abcz", &[(3, 1, 3)]),
t!(leftmost340, &["bce", "cz", "bc"], "bcz", &[(2, 0, 2)]),
t!(leftmost350, &["abc", "bd", "ab"], "abd", &[(2, 0, 2)]),
t!(
leftmost360,
&["abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(2, 0, 8),]
),
t!(
leftmost370,
&["abcdefghi", "cde", "hz", "abcdefgh"],
"abcdefghz",
&[(3, 0, 8),]
),
t!(
leftmost380,
&["abcdefghi", "hz", "abcdefgh", "a"],
"abcdefghz",
&[(2, 0, 8),]
),
t!(
leftmost390,
&["b", "abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(3, 0, 8),]
),
t!(
leftmost400,
&["h", "abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(3, 0, 8),]
),
t!(
leftmost410,
&["z", "abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(3, 0, 8), (0, 8, 9),]
),
];
/// Tests for non-overlapping leftmost-first match semantics. These tests
/// should generally be specific to leftmost-first, which means they should
/// generally fail under leftmost-longest semantics.
const LEFTMOST_FIRST: &'static [SearchTest] = &[
t!(leftfirst000, &["ab", "abcd"], "abcd", &[(0, 0, 2)]),
t!(leftfirst020, &["abcd", "ab"], "abcd", &[(0, 0, 4)]),
t!(leftfirst030, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
t!(leftfirst040, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (0, 3, 4)]),
t!(leftfirst100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(1, 1, 5)]),
t!(leftfirst110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
t!(leftfirst300, &["abcd", "b", "bce"], "abce", &[(1, 1, 2)]),
t!(
leftfirst310,
&["abcd", "b", "bce", "ce"],
"abce",
&[(1, 1, 2), (3, 2, 4),]
),
t!(
leftfirst320,
&["a", "abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(0, 0, 1), (2, 7, 9),]
),
t!(leftfirst330, &["a", "abab"], "abab", &[(0, 0, 1), (0, 2, 3)]),
t!(
leftfirst340,
&["abcdef", "x", "x", "x", "x", "x", "x", "abcde"],
"abcdef",
&[(0, 0, 6)]
),
];
/// Tests for non-overlapping leftmost-longest match semantics. These tests
/// should generally be specific to leftmost-longest, which means they should
/// generally fail under leftmost-first semantics.
const LEFTMOST_LONGEST: &'static [SearchTest] = &[
t!(leftlong000, &["ab", "abcd"], "abcd", &[(1, 0, 4)]),
t!(leftlong010, &["abcd", "bcd", "cd", "b"], "abcd", &[(0, 0, 4),]),
t!(leftlong040, &["a", "ab"], "a", &[(0, 0, 1)]),
t!(leftlong050, &["a", "ab"], "ab", &[(1, 0, 2)]),
t!(leftlong060, &["ab", "a"], "a", &[(1, 0, 1)]),
t!(leftlong070, &["ab", "a"], "ab", &[(0, 0, 2)]),
t!(leftlong100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(2, 1, 6)]),
t!(leftlong110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
t!(leftlong300, &["abcd", "b", "bce"], "abce", &[(2, 1, 4)]),
t!(
leftlong310,
&["a", "abcdefghi", "hz", "abcdefgh"],
"abcdefghz",
&[(3, 0, 8),]
),
t!(leftlong320, &["a", "abab"], "abab", &[(1, 0, 4)]),
t!(leftlong330, &["abcd", "b", "ce"], "abce", &[(1, 1, 2), (2, 2, 4),]),
t!(leftlong340, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (1, 3, 5)]),
];
/// Regression tests that are applied to all combinations.
///
/// If regression tests are needed for specific match semantics, then add them
/// to the appropriate group above.
const REGRESSION: &'static [SearchTest] = &[
t!(regression010, &["inf", "ind"], "infind", &[(0, 0, 3), (1, 3, 6),]),
t!(regression020, &["ind", "inf"], "infind", &[(1, 0, 3), (0, 3, 6),]),
t!(
regression030,
&["libcore/", "libstd/"],
"libcore/char/methods.rs",
&[(0, 0, 8),]
),
t!(
regression040,
&["libstd/", "libcore/"],
"libcore/char/methods.rs",
&[(1, 0, 8),]
),
t!(
regression050,
&["\x00\x00\x01", "\x00\x00\x00"],
"\x00\x00\x00",
&[(1, 0, 3),]
),
t!(
regression060,
&["\x00\x00\x00", "\x00\x00\x01"],
"\x00\x00\x00",
&[(0, 0, 3),]
),
];
const TEDDY: &'static [SearchTest] = &[
t!(
teddy010,
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"],
"abcdefghijk",
&[
(0, 0, 1),
(1, 1, 2),
(2, 2, 3),
(3, 3, 4),
(4, 4, 5),
(5, 5, 6),
(6, 6, 7),
(7, 7, 8),
(8, 8, 9),
(9, 9, 10),
(10, 10, 11)
]
),
t!(
teddy020,
&["ab", "bc", "cd", "de", "ef", "fg", "gh", "hi", "ij", "jk", "kl"],
"abcdefghijk",
&[(0, 0, 2), (2, 2, 4), (4, 4, 6), (6, 6, 8), (8, 8, 10),]
),
t!(
teddy030,
&["abc"],
"abcdefghijklmnopqrstuvwxyzabcdefghijk",
&[(0, 0, 3), (0, 26, 29)]
),
];
// Now define a test for each combination of things above that we want to run.
// Since there are a few different combinations for each collection of tests,
// we define a couple of macros to avoid repetition drudgery. The testconfig
// macro constructs the automaton from a given match kind, and runs the search
// tests one-by-one over the given collection. The `with` parameter allows one
// to configure the config with additional parameters. The testcombo macro
// invokes testconfig in precisely this way: it sets up several tests where
// each one turns a different knob on Config.
macro_rules! testconfig {
($name:ident, $collection:expr, $with:expr) => {
#[test]
fn $name() {
run_search_tests($collection, |test| {
let mut config = Config::new();
$with(&mut config);
let mut builder = config.builder();
builder.extend(test.patterns.iter().map(|p| p.as_bytes()));
let searcher = match builder.build() {
Some(searcher) => searcher,
None => {
// For x86-64 and aarch64, not building a searcher is
// probably a bug, so be loud.
if cfg!(any(
target_arch = "x86_64",
target_arch = "aarch64"
)) {
panic!("failed to build packed searcher")
}
return None;
}
};
Some(searcher.find_iter(&test.haystack).collect())
});
}
};
}
testconfig!(
search_default_leftmost_first,
PACKED_LEFTMOST_FIRST,
|_: &mut Config| {}
);
testconfig!(
search_default_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.match_kind(MatchKind::LeftmostLongest);
}
);
testconfig!(
search_teddy_leftmost_first,
PACKED_LEFTMOST_FIRST,
|c: &mut Config| {
c.only_teddy(true);
}
);
testconfig!(
search_teddy_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
}
);
testconfig!(
search_teddy_ssse3_leftmost_first,
PACKED_LEFTMOST_FIRST,
|c: &mut Config| {
c.only_teddy(true);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("ssse3") {
c.only_teddy_256bit(Some(false));
}
}
);
testconfig!(
search_teddy_ssse3_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("ssse3") {
c.only_teddy_256bit(Some(false));
}
}
);
testconfig!(
search_teddy_avx2_leftmost_first,
PACKED_LEFTMOST_FIRST,
|c: &mut Config| {
c.only_teddy(true);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("avx2") {
c.only_teddy_256bit(Some(true));
}
}
);
testconfig!(
search_teddy_avx2_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("avx2") {
c.only_teddy_256bit(Some(true));
}
}
);
testconfig!(
search_teddy_fat_leftmost_first,
PACKED_LEFTMOST_FIRST,
|c: &mut Config| {
c.only_teddy(true);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("avx2") {
c.only_teddy_fat(Some(true));
}
}
);
testconfig!(
search_teddy_fat_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
#[cfg(target_arch = "x86_64")]
if std::is_x86_feature_detected!("avx2") {
c.only_teddy_fat(Some(true));
}
}
);
testconfig!(
search_rabinkarp_leftmost_first,
PACKED_LEFTMOST_FIRST,
|c: &mut Config| {
c.only_rabin_karp(true);
}
);
testconfig!(
search_rabinkarp_leftmost_longest,
PACKED_LEFTMOST_LONGEST,
|c: &mut Config| {
c.only_rabin_karp(true).match_kind(MatchKind::LeftmostLongest);
}
);
#[test]
fn search_tests_have_unique_names() {
let assert = |constname, tests: &[SearchTest]| {
let mut seen = HashMap::new(); // map from test name to position
for (i, test) in tests.iter().enumerate() {
if !seen.contains_key(test.name) {
seen.insert(test.name, i);
} else {
let last = seen[test.name];
panic!(
"{} tests have duplicate names at positions {} and {}",
constname, last, i
);
}
}
};
assert("BASICS", BASICS);
assert("LEFTMOST", LEFTMOST);
assert("LEFTMOST_FIRST", LEFTMOST_FIRST);
assert("LEFTMOST_LONGEST", LEFTMOST_LONGEST);
assert("REGRESSION", REGRESSION);
assert("TEDDY", TEDDY);
}
fn run_search_tests<F: FnMut(&SearchTestOwned) -> Option<Vec<Match>>>(
which: TestCollection,
mut f: F,
) {
let get_match_triples =
|matches: Vec<Match>| -> Vec<(usize, usize, usize)> {
matches
.into_iter()
.map(|m| (m.pattern().as_usize(), m.start(), m.end()))
.collect()
};
for &tests in which {
for spec in tests {
for test in spec.variations() {
let results = match f(&test) {
None => continue,
Some(results) => results,
};
assert_eq!(
test.matches,
get_match_triples(results).as_slice(),
"test: {}, patterns: {:?}, haystack(len={:?}): {:?}, \
offset: {:?}",
test.name,
test.patterns,
test.haystack.len(),
test.haystack,
test.offset,
);
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
/*!
Provides implementations of `fst::Automaton` for Aho-Corasick automata.
This works by providing two wrapper types, [`Anchored`] and [`Unanchored`].
The former executes an anchored search on an FST while the latter executes
an unanchored search. Building these wrappers is fallible and will fail if
the underlying Aho-Corasick automaton does not support the type of search it
represents.
*/
use crate::{
automaton::{Automaton, StateID},
Anchored as AcAnchored, Input, MatchError,
};
/// Represents an unanchored Aho-Corasick search of a finite state transducer.
///
/// Wrapping an Aho-Corasick automaton in `Unanchored` will fail if the
/// underlying automaton does not support unanchored searches.
///
/// # Example
///
/// This shows how to build an FST of keys and then run an unanchored search on
/// those keys using an Aho-Corasick automaton.
///
/// ```
/// use aho_corasick::{nfa::contiguous::NFA, transducer::Unanchored};
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
///
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
/// let nfa = NFA::new(&["bcd", "x"]).unwrap();
/// // NFAs always support both unanchored and anchored searches.
/// let searcher = Unanchored::new(&nfa).unwrap();
///
/// let mut stream = set.search(searcher).into_stream();
/// let mut results = vec![];
/// while let Some(key) = stream.next() {
/// results.push(std::str::from_utf8(key).unwrap().to_string());
/// }
/// assert_eq!(vec!["abcd", "bcd", "xyz"], results);
/// ```
#[derive(Clone, Debug)]
pub struct Unanchored<A>(A);
impl<A: Automaton> Unanchored<A> {
/// Create a new `Unanchored` implementation of the `fst::Automaton` trait.
///
/// If the given Aho-Corasick automaton does not support unanchored
/// searches, then this returns an error.
pub fn new(aut: A) -> Result<Unanchored<A>, MatchError> {
let input = Input::new("").anchored(AcAnchored::No);
let _ = aut.start_state(&input)?;
Ok(Unanchored(aut))
}
/// Returns a borrow to the underlying automaton.
pub fn as_ref(&self) -> &A {
&self.0
}
/// Unwrap this value and return the inner automaton.
pub fn into_inner(self) -> A {
self.0
}
}
impl<A: Automaton> fst::Automaton for Unanchored<A> {
type State = StateID;
#[inline]
fn start(&self) -> StateID {
let input = Input::new("").anchored(AcAnchored::No);
self.0.start_state(&input).expect("support for unanchored searches")
}
#[inline]
fn is_match(&self, state: &StateID) -> bool {
self.0.is_match(*state)
}
#[inline]
fn accept(&self, state: &StateID, byte: u8) -> StateID {
if fst::Automaton::is_match(self, state) {
return *state;
}
self.0.next_state(AcAnchored::No, *state, byte)
}
#[inline]
fn can_match(&self, state: &StateID) -> bool {
!self.0.is_dead(*state)
}
}
/// Represents an anchored Aho-Corasick search of a finite state transducer.
///
/// Wrapping an Aho-Corasick automaton in `Unanchored` will fail if the
/// underlying automaton does not support unanchored searches.
///
/// # Example
///
/// This shows how to build an FST of keys and then run an anchored search on
/// those keys using an Aho-Corasick automaton.
///
/// ```
/// use aho_corasick::{nfa::contiguous::NFA, transducer::Anchored};
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
///
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
/// let nfa = NFA::new(&["bcd", "x"]).unwrap();
/// // NFAs always support both unanchored and anchored searches.
/// let searcher = Anchored::new(&nfa).unwrap();
///
/// let mut stream = set.search(searcher).into_stream();
/// let mut results = vec![];
/// while let Some(key) = stream.next() {
/// results.push(std::str::from_utf8(key).unwrap().to_string());
/// }
/// assert_eq!(vec!["bcd", "xyz"], results);
/// ```
///
/// This is like the example above, except we use an Aho-Corasick DFA, which
/// requires explicitly configuring it to support anchored searches. (NFAs
/// unconditionally support both unanchored and anchored searches.)
///
/// ```
/// use aho_corasick::{dfa::DFA, transducer::Anchored, StartKind};
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
///
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
/// let dfa = DFA::builder()
/// .start_kind(StartKind::Anchored)
/// .build(&["bcd", "x"])
/// .unwrap();
/// // We've explicitly configured our DFA to support anchored searches.
/// let searcher = Anchored::new(&dfa).unwrap();
///
/// let mut stream = set.search(searcher).into_stream();
/// let mut results = vec![];
/// while let Some(key) = stream.next() {
/// results.push(std::str::from_utf8(key).unwrap().to_string());
/// }
/// assert_eq!(vec!["bcd", "xyz"], results);
/// ```
#[derive(Clone, Debug)]
pub struct Anchored<A>(A);
impl<A: Automaton> Anchored<A> {
/// Create a new `Anchored` implementation of the `fst::Automaton` trait.
///
/// If the given Aho-Corasick automaton does not support anchored searches,
/// then this returns an error.
pub fn new(aut: A) -> Result<Anchored<A>, MatchError> {
let input = Input::new("").anchored(AcAnchored::Yes);
let _ = aut.start_state(&input)?;
Ok(Anchored(aut))
}
/// Returns a borrow to the underlying automaton.
pub fn as_ref(&self) -> &A {
&self.0
}
/// Unwrap this value and return the inner automaton.
pub fn into_inner(self) -> A {
self.0
}
}
impl<A: Automaton> fst::Automaton for Anchored<A> {
type State = StateID;
#[inline]
fn start(&self) -> StateID {
let input = Input::new("").anchored(AcAnchored::Yes);
self.0.start_state(&input).expect("support for unanchored searches")
}
#[inline]
fn is_match(&self, state: &StateID) -> bool {
self.0.is_match(*state)
}
#[inline]
fn accept(&self, state: &StateID, byte: u8) -> StateID {
if fst::Automaton::is_match(self, state) {
return *state;
}
self.0.next_state(AcAnchored::Yes, *state, byte)
}
#[inline]
fn can_match(&self, state: &StateID) -> bool {
!self.0.is_dead(*state)
}
}
#[cfg(test)]
mod tests {
use alloc::{string::String, vec, vec::Vec};
use fst::{Automaton, IntoStreamer, Set, Streamer};
use crate::{
dfa::DFA,
nfa::{contiguous, noncontiguous},
StartKind,
};
use super::*;
fn search<A: Automaton, D: AsRef<[u8]>>(
set: &Set<D>,
aut: A,
) -> Vec<String> {
let mut stream = set.search(aut).into_stream();
let mut results = vec![];
while let Some(key) = stream.next() {
results.push(String::from(core::str::from_utf8(key).unwrap()));
}
results
}
#[test]
fn unanchored() {
let set =
Set::from_iter(&["a", "bar", "baz", "wat", "xba", "xbax", "z"])
.unwrap();
let patterns = vec!["baz", "bax"];
let expected = vec!["baz", "xbax"];
let aut = Unanchored(noncontiguous::NFA::new(&patterns).unwrap());
let got = search(&set, &aut);
assert_eq!(got, expected);
let aut = Unanchored(contiguous::NFA::new(&patterns).unwrap());
let got = search(&set, &aut);
assert_eq!(got, expected);
let aut = Unanchored(DFA::new(&patterns).unwrap());
let got = search(&set, &aut);
assert_eq!(got, expected);
}
#[test]
fn anchored() {
let set =
Set::from_iter(&["a", "bar", "baz", "wat", "xba", "xbax", "z"])
.unwrap();
let patterns = vec!["baz", "bax"];
let expected = vec!["baz"];
let aut = Anchored(noncontiguous::NFA::new(&patterns).unwrap());
let got = search(&set, &aut);
assert_eq!(got, expected);
let aut = Anchored(contiguous::NFA::new(&patterns).unwrap());
let got = search(&set, &aut);
assert_eq!(got, expected);
let aut = Anchored(
DFA::builder()
.start_kind(StartKind::Anchored)
.build(&patterns)
.unwrap(),
);
let got = search(&set, &aut);
assert_eq!(got, expected);
}
}
+409
View File
@@ -0,0 +1,409 @@
use crate::util::int::Usize;
/// A representation of byte oriented equivalence classes.
///
/// This is used in finite state machines to reduce the size of the transition
/// table. This can have a particularly large impact not only on the total size
/// of an FSM, but also on FSM build times because it reduces the number of
/// transitions that need to be visited/set.
#[derive(Clone, Copy)]
pub(crate) struct ByteClasses([u8; 256]);
impl ByteClasses {
/// Creates a new set of equivalence classes where all bytes are mapped to
/// the same class.
pub(crate) fn empty() -> ByteClasses {
ByteClasses([0; 256])
}
/// Creates a new set of equivalence classes where each byte belongs to
/// its own equivalence class.
pub(crate) fn singletons() -> ByteClasses {
let mut classes = ByteClasses::empty();
for b in 0..=255 {
classes.set(b, b);
}
classes
}
/// Set the equivalence class for the given byte.
#[inline]
pub(crate) fn set(&mut self, byte: u8, class: u8) {
self.0[usize::from(byte)] = class;
}
/// Get the equivalence class for the given byte.
#[inline]
pub(crate) fn get(&self, byte: u8) -> u8 {
self.0[usize::from(byte)]
}
/// Return the total number of elements in the alphabet represented by
/// these equivalence classes. Equivalently, this returns the total number
/// of equivalence classes.
#[inline]
pub(crate) fn alphabet_len(&self) -> usize {
// Add one since the number of equivalence classes is one bigger than
// the last one.
usize::from(self.0[255]) + 1
}
/// Returns the stride, as a base-2 exponent, required for these
/// equivalence classes.
///
/// The stride is always the smallest power of 2 that is greater than or
/// equal to the alphabet length. This is done so that converting between
/// state IDs and indices can be done with shifts alone, which is much
/// faster than integer division. The "stride2" is the exponent. i.e.,
/// `2^stride2 = stride`.
pub(crate) fn stride2(&self) -> usize {
let zeros = self.alphabet_len().next_power_of_two().trailing_zeros();
usize::try_from(zeros).unwrap()
}
/// Returns the stride for these equivalence classes, which corresponds
/// to the smallest power of 2 greater than or equal to the number of
/// equivalence classes.
pub(crate) fn stride(&self) -> usize {
1 << self.stride2()
}
/// Returns true if and only if every byte in this class maps to its own
/// equivalence class. Equivalently, there are 257 equivalence classes
/// and each class contains exactly one byte (plus the special EOI class).
#[inline]
pub(crate) fn is_singleton(&self) -> bool {
self.alphabet_len() == 256
}
/// Returns an iterator over all equivalence classes in this set.
pub(crate) fn iter(&self) -> ByteClassIter {
ByteClassIter { it: 0..self.alphabet_len() }
}
/// Returns an iterator of the bytes in the given equivalence class.
pub(crate) fn elements(&self, class: u8) -> ByteClassElements {
ByteClassElements { classes: self, class, bytes: 0..=255 }
}
/// Returns an iterator of byte ranges in the given equivalence class.
///
/// That is, a sequence of contiguous ranges are returned. Typically, every
/// class maps to a single contiguous range.
fn element_ranges(&self, class: u8) -> ByteClassElementRanges {
ByteClassElementRanges { elements: self.elements(class), range: None }
}
}
impl core::fmt::Debug for ByteClasses {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_singleton() {
write!(f, "ByteClasses(<one-class-per-byte>)")
} else {
write!(f, "ByteClasses(")?;
for (i, class) in self.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:?} => [", class)?;
for (start, end) in self.element_ranges(class) {
if start == end {
write!(f, "{:?}", start)?;
} else {
write!(f, "{:?}-{:?}", start, end)?;
}
}
write!(f, "]")?;
}
write!(f, ")")
}
}
}
/// An iterator over each equivalence class.
#[derive(Debug)]
pub(crate) struct ByteClassIter {
it: core::ops::Range<usize>,
}
impl Iterator for ByteClassIter {
type Item = u8;
fn next(&mut self) -> Option<u8> {
self.it.next().map(|class| class.as_u8())
}
}
/// An iterator over all elements in a specific equivalence class.
#[derive(Debug)]
pub(crate) struct ByteClassElements<'a> {
classes: &'a ByteClasses,
class: u8,
bytes: core::ops::RangeInclusive<u8>,
}
impl<'a> Iterator for ByteClassElements<'a> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
while let Some(byte) = self.bytes.next() {
if self.class == self.classes.get(byte) {
return Some(byte);
}
}
None
}
}
/// An iterator over all elements in an equivalence class expressed as a
/// sequence of contiguous ranges.
#[derive(Debug)]
pub(crate) struct ByteClassElementRanges<'a> {
elements: ByteClassElements<'a>,
range: Option<(u8, u8)>,
}
impl<'a> Iterator for ByteClassElementRanges<'a> {
type Item = (u8, u8);
fn next(&mut self) -> Option<(u8, u8)> {
loop {
let element = match self.elements.next() {
None => return self.range.take(),
Some(element) => element,
};
match self.range.take() {
None => {
self.range = Some((element, element));
}
Some((start, end)) => {
if usize::from(end) + 1 != usize::from(element) {
self.range = Some((element, element));
return Some((start, end));
}
self.range = Some((start, element));
}
}
}
}
}
/// A partitioning of bytes into equivalence classes.
///
/// A byte class set keeps track of an *approximation* of equivalence classes
/// of bytes during NFA construction. That is, every byte in an equivalence
/// class cannot discriminate between a match and a non-match.
///
/// Note that this may not compute the minimal set of equivalence classes.
/// Basically, any byte in a pattern given to the noncontiguous NFA builder
/// will automatically be treated as its own equivalence class. All other
/// bytes---any byte not in any pattern---will be treated as their own
/// equivalence classes. In theory, all bytes not in any pattern should
/// be part of a single equivalence class, but in practice, we only treat
/// contiguous ranges of bytes as an equivalence class. So the number of
/// classes computed may be bigger than necessary. This usually doesn't make
/// much of a difference, and keeps the implementation simple.
#[derive(Clone, Debug)]
pub(crate) struct ByteClassSet(ByteSet);
impl Default for ByteClassSet {
fn default() -> ByteClassSet {
ByteClassSet::empty()
}
}
impl ByteClassSet {
/// Create a new set of byte classes where all bytes are part of the same
/// equivalence class.
pub(crate) fn empty() -> Self {
ByteClassSet(ByteSet::empty())
}
/// Indicate the the range of byte given (inclusive) can discriminate a
/// match between it and all other bytes outside of the range.
pub(crate) fn set_range(&mut self, start: u8, end: u8) {
debug_assert!(start <= end);
if start > 0 {
self.0.add(start - 1);
}
self.0.add(end);
}
/// Convert this boolean set to a map that maps all byte values to their
/// corresponding equivalence class. The last mapping indicates the largest
/// equivalence class identifier (which is never bigger than 255).
pub(crate) fn byte_classes(&self) -> ByteClasses {
let mut classes = ByteClasses::empty();
let mut class = 0u8;
let mut b = 0u8;
loop {
classes.set(b, class);
if b == 255 {
break;
}
if self.0.contains(b) {
class = class.checked_add(1).unwrap();
}
b = b.checked_add(1).unwrap();
}
classes
}
}
/// A simple set of bytes that is reasonably cheap to copy and allocation free.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct ByteSet {
bits: BitSet,
}
/// The representation of a byte set. Split out so that we can define a
/// convenient Debug impl for it while keeping "ByteSet" in the output.
#[derive(Clone, Copy, Default, Eq, PartialEq)]
struct BitSet([u128; 2]);
impl ByteSet {
/// Create an empty set of bytes.
pub(crate) fn empty() -> ByteSet {
ByteSet { bits: BitSet([0; 2]) }
}
/// Add a byte to this set.
///
/// If the given byte already belongs to this set, then this is a no-op.
pub(crate) fn add(&mut self, byte: u8) {
let bucket = byte / 128;
let bit = byte % 128;
self.bits.0[usize::from(bucket)] |= 1 << bit;
}
/// Return true if and only if the given byte is in this set.
pub(crate) fn contains(&self, byte: u8) -> bool {
let bucket = byte / 128;
let bit = byte % 128;
self.bits.0[usize::from(bucket)] & (1 << bit) > 0
}
}
impl core::fmt::Debug for BitSet {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut fmtd = f.debug_set();
for b in 0u8..=255 {
if (ByteSet { bits: *self }).contains(b) {
fmtd.entry(&b);
}
}
fmtd.finish()
}
}
#[cfg(test)]
mod tests {
use alloc::{vec, vec::Vec};
use super::*;
#[test]
fn byte_classes() {
let mut set = ByteClassSet::empty();
set.set_range(b'a', b'z');
let classes = set.byte_classes();
assert_eq!(classes.get(0), 0);
assert_eq!(classes.get(1), 0);
assert_eq!(classes.get(2), 0);
assert_eq!(classes.get(b'a' - 1), 0);
assert_eq!(classes.get(b'a'), 1);
assert_eq!(classes.get(b'm'), 1);
assert_eq!(classes.get(b'z'), 1);
assert_eq!(classes.get(b'z' + 1), 2);
assert_eq!(classes.get(254), 2);
assert_eq!(classes.get(255), 2);
let mut set = ByteClassSet::empty();
set.set_range(0, 2);
set.set_range(4, 6);
let classes = set.byte_classes();
assert_eq!(classes.get(0), 0);
assert_eq!(classes.get(1), 0);
assert_eq!(classes.get(2), 0);
assert_eq!(classes.get(3), 1);
assert_eq!(classes.get(4), 2);
assert_eq!(classes.get(5), 2);
assert_eq!(classes.get(6), 2);
assert_eq!(classes.get(7), 3);
assert_eq!(classes.get(255), 3);
}
#[test]
fn full_byte_classes() {
let mut set = ByteClassSet::empty();
for b in 0u8..=255 {
set.set_range(b, b);
}
assert_eq!(set.byte_classes().alphabet_len(), 256);
}
#[test]
fn elements_typical() {
let mut set = ByteClassSet::empty();
set.set_range(b'b', b'd');
set.set_range(b'g', b'm');
set.set_range(b'z', b'z');
let classes = set.byte_classes();
// class 0: \x00-a
// class 1: b-d
// class 2: e-f
// class 3: g-m
// class 4: n-y
// class 5: z-z
// class 6: \x7B-\xFF
assert_eq!(classes.alphabet_len(), 7);
let elements = classes.elements(0).collect::<Vec<_>>();
assert_eq!(elements.len(), 98);
assert_eq!(elements[0], b'\x00');
assert_eq!(elements[97], b'a');
let elements = classes.elements(1).collect::<Vec<_>>();
assert_eq!(elements, vec![b'b', b'c', b'd'],);
let elements = classes.elements(2).collect::<Vec<_>>();
assert_eq!(elements, vec![b'e', b'f'],);
let elements = classes.elements(3).collect::<Vec<_>>();
assert_eq!(elements, vec![b'g', b'h', b'i', b'j', b'k', b'l', b'm',],);
let elements = classes.elements(4).collect::<Vec<_>>();
assert_eq!(elements.len(), 12);
assert_eq!(elements[0], b'n');
assert_eq!(elements[11], b'y');
let elements = classes.elements(5).collect::<Vec<_>>();
assert_eq!(elements, vec![b'z']);
let elements = classes.elements(6).collect::<Vec<_>>();
assert_eq!(elements.len(), 133);
assert_eq!(elements[0], b'\x7B');
assert_eq!(elements[132], b'\xFF');
}
#[test]
fn elements_singletons() {
let classes = ByteClasses::singletons();
assert_eq!(classes.alphabet_len(), 256);
let elements = classes.elements(b'a').collect::<Vec<_>>();
assert_eq!(elements, vec![b'a']);
}
#[test]
fn elements_empty() {
let classes = ByteClasses::empty();
assert_eq!(classes.alphabet_len(), 1);
let elements = classes.elements(0).collect::<Vec<_>>();
assert_eq!(elements.len(), 256);
assert_eq!(elements[0], b'\x00');
assert_eq!(elements[255], b'\xFF');
}
}

Some files were not shown because too many files have changed in this diff Show More