├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .ignore ├── .vim └── coc-settings.json ├── COPYING ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── UNLICENSE ├── benchmarks ├── definitions │ ├── memchr │ │ └── sherlock │ │ │ ├── common.toml │ │ │ ├── never.toml │ │ │ ├── rare.toml │ │ │ ├── uncommon.toml │ │ │ └── verycommon.toml │ └── memmem │ │ ├── byterank.toml │ │ ├── code.toml │ │ ├── pathological.toml │ │ ├── sliceslice.toml │ │ └── subtitles │ │ ├── common.toml │ │ ├── never.toml │ │ └── rare.toml ├── engines.toml ├── engines │ ├── .gitignore │ ├── libc │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-bytecount │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-jetscii │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-memchr │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-memchrold │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-sliceslice │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs │ ├── rust-std │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── main.rs │ └── stringzilla │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── main.rs ├── haystacks │ ├── README.md │ ├── code │ │ ├── README.md │ │ └── rust-library.rs │ ├── opensubtitles │ │ ├── README.md │ │ ├── en-huge.txt │ │ ├── en-medium.txt │ │ ├── en-small.txt │ │ ├── en-teeny.txt │ │ ├── en-tiny.txt │ │ ├── ru-huge.txt │ │ ├── ru-medium.txt │ │ ├── ru-small.txt │ │ ├── ru-teeny.txt │ │ ├── ru-tiny.txt │ │ ├── zh-huge.txt │ │ ├── zh-medium.txt │ │ ├── zh-small.txt │ │ ├── zh-teeny.txt │ │ └── zh-tiny.txt │ ├── pathological │ │ ├── README.md │ │ ├── defeat-simple-vector-freq.txt │ │ ├── defeat-simple-vector-repeated.txt │ │ ├── defeat-simple-vector.txt │ │ ├── md5-huge.txt │ │ ├── random-huge.txt │ │ ├── repeated-rare-huge.txt │ │ └── repeated-rare-small.txt │ ├── rg-13.0.0.txt │ ├── sherlock │ │ ├── README.md │ │ ├── huge.txt │ │ ├── small.txt │ │ └── tiny.txt │ └── sliceslice │ │ ├── README.md │ │ ├── haystack.txt │ │ ├── i386-notutf8.txt │ │ └── i386.txt ├── record │ ├── aarch64 │ │ ├── 2023-08-17.csv │ │ ├── 2023-08-20.csv │ │ ├── 2023-08-24.csv │ │ ├── 2023-08-24_2.csv │ │ ├── 2023-08-26.csv │ │ ├── 2023-08-27.csv │ │ └── 2023-12-29.csv │ └── x86_64 │ │ ├── 2023-07-31_baseline.csv │ │ ├── 2023-08-07.csv │ │ ├── 2023-08-09.csv │ │ ├── 2023-08-15.csv │ │ ├── 2023-08-17.csv │ │ ├── 2023-08-20.csv │ │ ├── 2023-08-23.csv │ │ ├── 2023-08-24.csv │ │ ├── 2023-08-24_2.csv │ │ ├── 2023-08-25.csv │ │ ├── 2023-08-26.csv │ │ ├── 2023-08-27.csv │ │ └── 2023-12-29.csv ├── regexes │ ├── sliceslice │ │ ├── README.md │ │ ├── words-by-length-desc.txt │ │ └── words.txt │ └── zero-zero-dd-dd.txt └── shared │ ├── Cargo.lock │ ├── Cargo.toml │ └── lib.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── memchr.rs │ ├── memchr2.rs │ ├── memchr3.rs │ ├── memmem.rs │ ├── memrchr.rs │ ├── memrchr2.rs │ ├── memrchr3.rs │ └── memrmem.rs ├── rustfmt.toml ├── scripts └── make-byte-frequency-table └── src ├── arch ├── aarch64 │ ├── memchr.rs │ ├── mod.rs │ └── neon │ │ ├── memchr.rs │ │ ├── mod.rs │ │ └── packedpair.rs ├── all │ ├── memchr.rs │ ├── mod.rs │ ├── packedpair │ │ ├── default_rank.rs │ │ └── mod.rs │ ├── rabinkarp.rs │ ├── shiftor.rs │ └── twoway.rs ├── generic │ ├── memchr.rs │ ├── mod.rs │ └── packedpair.rs ├── mod.rs ├── wasm32 │ ├── memchr.rs │ ├── mod.rs │ └── simd128 │ │ ├── memchr.rs │ │ ├── mod.rs │ │ └── packedpair.rs └── x86_64 │ ├── avx2 │ ├── memchr.rs │ ├── mod.rs │ └── packedpair.rs │ ├── memchr.rs │ ├── mod.rs │ └── sse2 │ ├── memchr.rs │ ├── mod.rs │ └── packedpair.rs ├── cow.rs ├── ext.rs ├── lib.rs ├── macros.rs ├── memchr.rs ├── memmem ├── mod.rs └── searcher.rs ├── tests ├── memchr │ ├── mod.rs │ ├── naive.rs │ └── prop.rs ├── mod.rs ├── packedpair.rs └── substring │ ├── mod.rs │ ├── naive.rs │ └── prop.rs └── vector.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [BurntSushi] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | doc 3 | tags 4 | examples/ss10pusa.csv 5 | build 6 | target 7 | Cargo.lock 8 | scratch* 9 | bench_large/huge 10 | tmp/ 11 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | !.github 2 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.allFeatures": false, 3 | "rust-analyzer.linkedProjects": [ 4 | "benchmarks/engines/libc/Cargo.toml", 5 | "benchmarks/engines/rust-bytecount/Cargo.toml", 6 | "benchmarks/engines/rust-jetscii/Cargo.toml", 7 | "benchmarks/engines/rust-memchr/Cargo.toml", 8 | "benchmarks/engines/rust-memchrold/Cargo.toml", 9 | "benchmarks/engines/rust-sliceslice/Cargo.toml", 10 | "benchmarks/engines/rust-std/Cargo.toml", 11 | "benchmarks/engines/stringzilla/Cargo.toml", 12 | "benchmarks/shared/Cargo.toml", 13 | "fuzz/Cargo.toml", 14 | "Cargo.toml" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This project is dual-licensed under the Unlicense and MIT licenses. 2 | 3 | You may use this code under the terms of either license. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memchr" 3 | version = "2.7.4" #:version 4 | authors = ["Andrew Gallant ", "bluss"] 5 | description = """ 6 | Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 7 | 1, 2 or 3 byte search and single substring search. 8 | """ 9 | documentation = "https://docs.rs/memchr/" 10 | homepage = "https://github.com/BurntSushi/memchr" 11 | repository = "https://github.com/BurntSushi/memchr" 12 | readme = "README.md" 13 | keywords = ["memchr", "memmem", "substring", "find", "search"] 14 | license = "Unlicense OR MIT" 15 | exclude = ["/.github", "/benchmarks", "/fuzz", "/scripts", "/tmp"] 16 | edition = "2021" 17 | rust-version = "1.61" 18 | 19 | [lib] 20 | name = "memchr" 21 | bench = false 22 | 23 | [features] 24 | default = ["std"] 25 | 26 | # The 'std' feature permits the memchr crate to use the standard library. This 27 | # permits this crate to use runtime CPU feature detection to automatically 28 | # accelerate searching via vector instructions. Without the standard library, 29 | # this automatic detection is not possible. 30 | std = ["alloc"] 31 | 32 | # The 'alloc' feature enables some APIs that require allocation, such as 33 | # 'Finder::into_owned'. Note that this feature does not enable runtime CPU 34 | # feature detection. That still requires 'std'. 35 | alloc = [] 36 | 37 | # When enabled (it's disabled by default), the `log` crate will be used to 38 | # emit a spattering of log messages. For the most part, the log messages are 39 | # meant to indicate what strategies are being employed. For example, whether 40 | # a vector or a scalar algorithm is used for substring search. This can be 41 | # useful when debugging performance problems. 42 | # 43 | # This is disabled by default. 44 | logging = ["dep:log"] 45 | 46 | # The 'use_std' feature is DEPRECATED. It will be removed in memchr 3. Until 47 | # then, it is alias for the 'std' feature. 48 | use_std = ["std"] 49 | 50 | # The 'libc' feature has been DEPRECATED and no longer has any effect. 51 | libc = [] 52 | 53 | # Internal feature, only used when building as part of libstd, not part of the 54 | # stable interface of this crate. 55 | rustc-dep-of-std = ['core', 'compiler_builtins'] 56 | 57 | [dependencies] 58 | # Only used when the `logging` feature is enabled (disabled by default). 59 | log = { version = "0.4.20", optional = true } 60 | # Internal feature, only used when building as part of libstd, not part of the 61 | # stable interface of this crate. 62 | core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' } 63 | compiler_builtins = { version = '0.1.2', optional = true } 64 | 65 | [dev-dependencies] 66 | quickcheck = { version = "1.0.3", default-features = false } 67 | 68 | [profile.release] 69 | debug = true 70 | 71 | [profile.bench] 72 | debug = true 73 | 74 | [profile.test] 75 | opt-level = 3 76 | debug = true 77 | 78 | [package.metadata.docs.rs] 79 | rustdoc-args = ["--generate-link-to-definition"] 80 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Gallant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /benchmarks/definitions/memchr/sherlock/common.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count-bytes" 3 | name = "huge1" 4 | regex = 'a' 5 | haystack = { path = "sherlock/huge.txt" } 6 | count = 35_301 7 | engines = [ 8 | "libc/memchr/oneshot", 9 | "rust/bytecount/memchr/oneshot", 10 | "rust/bytecount/memchr/oneshot/wasm32", 11 | "rust/memchr/memchr/onlycount", 12 | "rust/memchr/memchr/onlycount/wasm32", 13 | "rust/memchr/memchr/oneshot", 14 | "rust/memchr/memchr/prebuilt", 15 | "rust/memchr/memchr/fallback", 16 | "rust/memchr/memchr/naive", 17 | "rust/memchr/memchr/wasm32", 18 | "rust/memchr/memrchr", 19 | "rust/memchrold/memchr/oneshot", 20 | "rust/memchrold/memchr/prebuilt", 21 | "rust/memchrold/memchr/naive", 22 | "rust/memchrold/memrchr", 23 | ] 24 | 25 | [[bench]] 26 | model = "count-bytes" 27 | name = "huge2" 28 | regex = ['a', 't'] 29 | haystack = { path = "sherlock/huge.txt" } 30 | count = 74_569 31 | engines = [ 32 | "rust/memchr/memchr2", 33 | "rust/memchr/memchr2/fallback", 34 | "rust/memchr/memchr2/naive", 35 | "rust/memchr/memchr2/wasm32", 36 | "rust/memchr/memrchr2", 37 | "rust/memchrold/memchr2", 38 | "rust/memchrold/memrchr2", 39 | ] 40 | 41 | [[bench]] 42 | model = "count-bytes" 43 | name = "huge3" 44 | regex = ['a', 't', 'o'] 45 | haystack = { path = "sherlock/huge.txt" } 46 | count = 109_064 47 | engines = [ 48 | "rust/memchr/memchr3", 49 | "rust/memchr/memchr3/fallback", 50 | "rust/memchr/memchr3/naive", 51 | "rust/memchr/memchr3/wasm32", 52 | "rust/memchr/memrchr3", 53 | "rust/memchrold/memchr3", 54 | "rust/memchrold/memrchr3", 55 | ] 56 | 57 | [[bench]] 58 | model = "count-bytes" 59 | name = "small1" 60 | regex = 'a' 61 | haystack = { path = "sherlock/small.txt" } 62 | count = 44 63 | engines = [ 64 | "libc/memchr/oneshot", 65 | "rust/bytecount/memchr/oneshot", 66 | "rust/bytecount/memchr/oneshot/wasm32", 67 | "rust/memchr/memchr/onlycount", 68 | "rust/memchr/memchr/onlycount/wasm32", 69 | "rust/memchr/memchr/oneshot", 70 | "rust/memchr/memchr/prebuilt", 71 | "rust/memchr/memchr/fallback", 72 | "rust/memchr/memchr/naive", 73 | "rust/memchr/memchr/wasm32", 74 | "rust/memchr/memrchr", 75 | "rust/memchrold/memchr/oneshot", 76 | "rust/memchrold/memchr/prebuilt", 77 | "rust/memchrold/memchr/naive", 78 | "rust/memchrold/memrchr", 79 | ] 80 | 81 | [[bench]] 82 | model = "count-bytes" 83 | name = "small2" 84 | regex = ['a', 'h'] 85 | haystack = { path = "sherlock/small.txt" } 86 | count = 78 87 | engines = [ 88 | "rust/memchr/memchr2", 89 | "rust/memchr/memchr2/fallback", 90 | "rust/memchr/memchr2/naive", 91 | "rust/memchr/memchr2/wasm32", 92 | "rust/memchr/memrchr2", 93 | "rust/memchrold/memchr2", 94 | "rust/memchrold/memrchr2", 95 | ] 96 | 97 | [[bench]] 98 | model = "count-bytes" 99 | name = "small3" 100 | regex = ['a', 'h', 'i'] 101 | haystack = { path = "sherlock/small.txt" } 102 | count = 113 103 | engines = [ 104 | "rust/memchr/memchr3", 105 | "rust/memchr/memchr3/fallback", 106 | "rust/memchr/memchr3/naive", 107 | "rust/memchr/memchr3/wasm32", 108 | "rust/memchr/memrchr3", 109 | "rust/memchrold/memchr3", 110 | "rust/memchrold/memrchr3", 111 | ] 112 | 113 | [[bench]] 114 | model = "count-bytes" 115 | name = "tiny1" 116 | regex = ' ' 117 | haystack = { path = "sherlock/tiny.txt" } 118 | count = 11 119 | engines = [ 120 | "libc/memchr/oneshot", 121 | "rust/bytecount/memchr/oneshot", 122 | "rust/bytecount/memchr/oneshot/wasm32", 123 | "rust/memchr/memchr/onlycount", 124 | "rust/memchr/memchr/onlycount/wasm32", 125 | "rust/memchr/memchr/oneshot", 126 | "rust/memchr/memchr/prebuilt", 127 | "rust/memchr/memchr/fallback", 128 | "rust/memchr/memchr/naive", 129 | "rust/memchr/memchr/wasm32", 130 | "rust/memchr/memrchr", 131 | "rust/memchrold/memchr/oneshot", 132 | "rust/memchrold/memchr/prebuilt", 133 | "rust/memchrold/memchr/naive", 134 | "rust/memchrold/memrchr", 135 | ] 136 | -------------------------------------------------------------------------------- /benchmarks/definitions/memchr/sherlock/never.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count-bytes" 3 | name = "huge1" 4 | regex = '<' 5 | haystack = { path = "sherlock/huge.txt" } 6 | count = 0 7 | engines = [ 8 | "libc/memchr/oneshot", 9 | "rust/bytecount/memchr/oneshot", 10 | "rust/bytecount/memchr/oneshot/wasm32", 11 | "rust/memchr/memchr/onlycount", 12 | "rust/memchr/memchr/onlycount/wasm32", 13 | "rust/memchr/memchr/oneshot", 14 | "rust/memchr/memchr/prebuilt", 15 | "rust/memchr/memchr/fallback", 16 | "rust/memchr/memchr/naive", 17 | "rust/memchr/memchr/wasm32", 18 | "rust/memchr/memrchr", 19 | "rust/memchrold/memchr/oneshot", 20 | "rust/memchrold/memchr/prebuilt", 21 | "rust/memchrold/memchr/naive", 22 | "rust/memchrold/memrchr", 23 | ] 24 | 25 | [[bench]] 26 | model = "count-bytes" 27 | name = "huge2" 28 | regex = ['<', '>'] 29 | haystack = { path = "sherlock/huge.txt" } 30 | count = 0 31 | engines = [ 32 | "rust/memchr/memchr2", 33 | "rust/memchr/memchr2/fallback", 34 | "rust/memchr/memchr2/naive", 35 | "rust/memchr/memchr2/wasm32", 36 | "rust/memchr/memrchr2", 37 | "rust/memchrold/memchr2", 38 | "rust/memchrold/memrchr2", 39 | ] 40 | 41 | [[bench]] 42 | model = "count-bytes" 43 | name = "huge3" 44 | regex = ['<', '>', '='] 45 | haystack = { path = "sherlock/huge.txt" } 46 | count = 0 47 | engines = [ 48 | "rust/memchr/memchr3", 49 | "rust/memchr/memchr3/fallback", 50 | "rust/memchr/memchr3/naive", 51 | "rust/memchr/memchr3/wasm32", 52 | "rust/memchr/memrchr3", 53 | "rust/memchrold/memchr3", 54 | "rust/memchrold/memrchr3", 55 | ] 56 | 57 | [[bench]] 58 | model = "count-bytes" 59 | name = "small1" 60 | regex = '<' 61 | haystack = { path = "sherlock/small.txt" } 62 | count = 0 63 | engines = [ 64 | "libc/memchr/oneshot", 65 | "rust/bytecount/memchr/oneshot", 66 | "rust/bytecount/memchr/oneshot/wasm32", 67 | "rust/memchr/memchr/onlycount", 68 | "rust/memchr/memchr/onlycount/wasm32", 69 | "rust/memchr/memchr/oneshot", 70 | "rust/memchr/memchr/prebuilt", 71 | "rust/memchr/memchr/fallback", 72 | "rust/memchr/memchr/naive", 73 | "rust/memchr/memchr/wasm32", 74 | "rust/memchr/memrchr", 75 | "rust/memchrold/memchr/oneshot", 76 | "rust/memchrold/memchr/prebuilt", 77 | "rust/memchrold/memchr/naive", 78 | "rust/memchrold/memrchr", 79 | ] 80 | 81 | [[bench]] 82 | model = "count-bytes" 83 | name = "small2" 84 | regex = ['<', '>'] 85 | haystack = { path = "sherlock/small.txt" } 86 | count = 0 87 | engines = [ 88 | "rust/memchr/memchr2", 89 | "rust/memchr/memchr2/fallback", 90 | "rust/memchr/memchr2/naive", 91 | "rust/memchr/memchr2/wasm32", 92 | "rust/memchr/memrchr2", 93 | "rust/memchrold/memchr2", 94 | "rust/memchrold/memrchr2", 95 | ] 96 | 97 | [[bench]] 98 | model = "count-bytes" 99 | name = "small3" 100 | regex = ['<', '>', '='] 101 | haystack = { path = "sherlock/small.txt" } 102 | count = 0 103 | engines = [ 104 | "rust/memchr/memchr3", 105 | "rust/memchr/memchr3/fallback", 106 | "rust/memchr/memchr3/naive", 107 | "rust/memchr/memchr3/wasm32", 108 | "rust/memchr/memrchr3", 109 | "rust/memchrold/memchr3", 110 | "rust/memchrold/memrchr3", 111 | ] 112 | 113 | [[bench]] 114 | model = "count-bytes" 115 | name = "tiny1" 116 | regex = '<' 117 | haystack = { path = "sherlock/tiny.txt" } 118 | count = 0 119 | engines = [ 120 | "libc/memchr/oneshot", 121 | "rust/bytecount/memchr/oneshot", 122 | "rust/bytecount/memchr/oneshot/wasm32", 123 | "rust/memchr/memchr/onlycount", 124 | "rust/memchr/memchr/onlycount/wasm32", 125 | "rust/memchr/memchr/oneshot", 126 | "rust/memchr/memchr/prebuilt", 127 | "rust/memchr/memchr/fallback", 128 | "rust/memchr/memchr/naive", 129 | "rust/memchr/memchr/wasm32", 130 | "rust/memchr/memrchr", 131 | "rust/memchrold/memchr/oneshot", 132 | "rust/memchrold/memchr/prebuilt", 133 | "rust/memchrold/memchr/naive", 134 | "rust/memchrold/memrchr", 135 | ] 136 | 137 | [[bench]] 138 | model = "count-bytes" 139 | name = "tiny2" 140 | regex = ['<', '>'] 141 | haystack = { path = "sherlock/tiny.txt" } 142 | count = 0 143 | engines = [ 144 | "rust/memchr/memchr2", 145 | "rust/memchr/memchr2/fallback", 146 | "rust/memchr/memchr2/naive", 147 | "rust/memchr/memchr2/wasm32", 148 | "rust/memchr/memrchr2", 149 | "rust/memchrold/memchr2", 150 | "rust/memchrold/memrchr2", 151 | ] 152 | 153 | [[bench]] 154 | model = "count-bytes" 155 | name = "tiny3" 156 | regex = ['<', '>', '='] 157 | haystack = { path = "sherlock/tiny.txt" } 158 | count = 0 159 | engines = [ 160 | "rust/memchr/memchr3", 161 | "rust/memchr/memchr3/fallback", 162 | "rust/memchr/memchr3/naive", 163 | "rust/memchr/memchr3/wasm32", 164 | "rust/memchr/memrchr3", 165 | "rust/memchrold/memchr3", 166 | "rust/memchrold/memrchr3", 167 | ] 168 | 169 | [[bench]] 170 | model = "count-bytes" 171 | name = "empty1" 172 | regex = '<' 173 | haystack = '' 174 | count = 0 175 | engines = [ 176 | "libc/memchr/oneshot", 177 | "rust/bytecount/memchr/oneshot", 178 | "rust/bytecount/memchr/oneshot/wasm32", 179 | "rust/memchr/memchr/onlycount", 180 | "rust/memchr/memchr/onlycount/wasm32", 181 | "rust/memchr/memchr/oneshot", 182 | "rust/memchr/memchr/prebuilt", 183 | "rust/memchr/memchr/fallback", 184 | "rust/memchr/memchr/naive", 185 | "rust/memchr/memchr/wasm32", 186 | "rust/memchr/memrchr", 187 | "rust/memchrold/memchr/oneshot", 188 | "rust/memchrold/memchr/prebuilt", 189 | "rust/memchrold/memchr/naive", 190 | "rust/memchrold/memrchr", 191 | ] 192 | 193 | [[bench]] 194 | model = "count-bytes" 195 | name = "empty2" 196 | regex = ['<', '>'] 197 | haystack = '' 198 | count = 0 199 | engines = [ 200 | "rust/memchr/memchr2", 201 | "rust/memchr/memchr2/fallback", 202 | "rust/memchr/memchr2/naive", 203 | "rust/memchr/memchr2/wasm32", 204 | "rust/memchr/memrchr2", 205 | "rust/memchrold/memchr2", 206 | "rust/memchrold/memrchr2", 207 | ] 208 | 209 | [[bench]] 210 | model = "count-bytes" 211 | name = "empty3" 212 | regex = ['<', '>', '='] 213 | haystack = '' 214 | count = 0 215 | engines = [ 216 | "rust/memchr/memchr3", 217 | "rust/memchr/memchr3/fallback", 218 | "rust/memchr/memchr3/naive", 219 | "rust/memchr/memchr3/wasm32", 220 | "rust/memchr/memrchr3", 221 | "rust/memchrold/memchr3", 222 | "rust/memchrold/memrchr3", 223 | ] 224 | -------------------------------------------------------------------------------- /benchmarks/definitions/memchr/sherlock/rare.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count-bytes" 3 | name = "huge1" 4 | regex = 'z' 5 | haystack = { path = "sherlock/huge.txt" } 6 | count = 151 7 | engines = [ 8 | "libc/memchr/oneshot", 9 | "rust/bytecount/memchr/oneshot", 10 | "rust/bytecount/memchr/oneshot/wasm32", 11 | "rust/memchr/memchr/onlycount", 12 | "rust/memchr/memchr/onlycount/wasm32", 13 | "rust/memchr/memchr/oneshot", 14 | "rust/memchr/memchr/prebuilt", 15 | "rust/memchr/memchr/fallback", 16 | "rust/memchr/memchr/naive", 17 | "rust/memchr/memchr/wasm32", 18 | "rust/memchr/memrchr", 19 | "rust/memchrold/memchr/oneshot", 20 | "rust/memchrold/memchr/prebuilt", 21 | "rust/memchrold/memrchr", 22 | ] 23 | 24 | [[bench]] 25 | model = "count-bytes" 26 | name = "huge2" 27 | regex = ['z', 'R'] 28 | haystack = { path = "sherlock/huge.txt" } 29 | count = 426 30 | engines = [ 31 | "rust/memchr/memchr2", 32 | "rust/memchr/memchr2/fallback", 33 | "rust/memchr/memchr2/naive", 34 | "rust/memchr/memchr2/wasm32", 35 | "rust/memchr/memrchr2", 36 | "rust/memchrold/memchr2", 37 | "rust/memchrold/memrchr2", 38 | ] 39 | 40 | [[bench]] 41 | model = "count-bytes" 42 | name = "huge3" 43 | regex = ['z', 'R', 'J'] 44 | haystack = { path = "sherlock/huge.txt" } 45 | count = 546 46 | engines = [ 47 | "rust/memchr/memchr3", 48 | "rust/memchr/memchr3/fallback", 49 | "rust/memchr/memchr3/naive", 50 | "rust/memchr/memchr3/wasm32", 51 | "rust/memchr/memrchr3", 52 | "rust/memchrold/memchr3", 53 | "rust/memchrold/memrchr3", 54 | ] 55 | 56 | [[bench]] 57 | model = "count-bytes" 58 | name = "small1" 59 | regex = 'R' 60 | haystack = { path = "sherlock/small.txt" } 61 | count = 1 62 | engines = [ 63 | "libc/memchr/oneshot", 64 | "rust/bytecount/memchr/oneshot", 65 | "rust/bytecount/memchr/oneshot/wasm32", 66 | "rust/memchr/memchr/onlycount", 67 | "rust/memchr/memchr/onlycount/wasm32", 68 | "rust/memchr/memchr/oneshot", 69 | "rust/memchr/memchr/prebuilt", 70 | "rust/memchr/memchr/fallback", 71 | "rust/memchr/memchr/naive", 72 | "rust/memchr/memchr/wasm32", 73 | "rust/memchr/memrchr", 74 | "rust/memchrold/memchr/oneshot", 75 | "rust/memchrold/memchr/prebuilt", 76 | "rust/memchrold/memchr/naive", 77 | "rust/memchrold/memrchr", 78 | ] 79 | 80 | [[bench]] 81 | model = "count-bytes" 82 | name = "small2" 83 | regex = ['R', 'P'] 84 | haystack = { path = "sherlock/small.txt" } 85 | count = 2 86 | engines = [ 87 | "rust/memchr/memchr2", 88 | "rust/memchr/memchr2/fallback", 89 | "rust/memchr/memchr2/naive", 90 | "rust/memchr/memchr2/wasm32", 91 | "rust/memchr/memrchr2", 92 | "rust/memchrold/memchr2", 93 | "rust/memchrold/memrchr2", 94 | ] 95 | 96 | [[bench]] 97 | model = "count-bytes" 98 | name = "small3" 99 | regex = ['R', 'P', 'T'] 100 | haystack = { path = "sherlock/small.txt" } 101 | count = 3 102 | engines = [ 103 | "rust/memchr/memchr3", 104 | "rust/memchr/memchr3/fallback", 105 | "rust/memchr/memchr3/naive", 106 | "rust/memchr/memchr3/wasm32", 107 | "rust/memchr/memrchr3", 108 | "rust/memchrold/memchr3", 109 | "rust/memchrold/memrchr3", 110 | ] 111 | 112 | [[bench]] 113 | model = "count-bytes" 114 | name = "tiny1" 115 | regex = '.' 116 | haystack = { path = "sherlock/tiny.txt" } 117 | count = 1 118 | engines = [ 119 | "libc/memchr/oneshot", 120 | "rust/bytecount/memchr/oneshot", 121 | "rust/bytecount/memchr/oneshot/wasm32", 122 | "rust/memchr/memchr/onlycount", 123 | "rust/memchr/memchr/onlycount/wasm32", 124 | "rust/memchr/memchr/oneshot", 125 | "rust/memchr/memchr/prebuilt", 126 | "rust/memchr/memchr/fallback", 127 | "rust/memchr/memchr/naive", 128 | "rust/memchr/memchr/wasm32", 129 | "rust/memchr/memrchr", 130 | "rust/memchrold/memchr/oneshot", 131 | "rust/memchrold/memchr/prebuilt", 132 | "rust/memchrold/memchr/naive", 133 | "rust/memchrold/memrchr", 134 | ] 135 | 136 | [[bench]] 137 | model = "count-bytes" 138 | name = "tiny2" 139 | regex = ['.', 'H'] 140 | haystack = { path = "sherlock/tiny.txt" } 141 | count = 2 142 | engines = [ 143 | "rust/memchr/memchr2", 144 | "rust/memchr/memchr2/fallback", 145 | "rust/memchr/memchr2/naive", 146 | "rust/memchr/memchr2/wasm32", 147 | "rust/memchr/memrchr2", 148 | "rust/memchrold/memchr2", 149 | "rust/memchrold/memrchr2", 150 | ] 151 | 152 | [[bench]] 153 | model = "count-bytes" 154 | name = "tiny3" 155 | regex = ['.', 'H', 'M'] 156 | haystack = { path = "sherlock/tiny.txt" } 157 | count = 3 158 | engines = [ 159 | "rust/memchr/memchr3", 160 | "rust/memchr/memchr3/fallback", 161 | "rust/memchr/memchr3/naive", 162 | "rust/memchr/memchr3/wasm32", 163 | "rust/memchr/memrchr3", 164 | "rust/memchrold/memchr3", 165 | "rust/memchrold/memrchr3", 166 | ] 167 | -------------------------------------------------------------------------------- /benchmarks/definitions/memchr/sherlock/uncommon.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count-bytes" 3 | name = "huge1" 4 | regex = 'b' 5 | haystack = { path = "sherlock/huge.txt" } 6 | count = 6124 7 | engines = [ 8 | "libc/memchr/oneshot", 9 | "rust/bytecount/memchr/oneshot", 10 | "rust/bytecount/memchr/oneshot/wasm32", 11 | "rust/memchr/memchr/onlycount", 12 | "rust/memchr/memchr/onlycount/wasm32", 13 | "rust/memchr/memchr/oneshot", 14 | "rust/memchr/memchr/prebuilt", 15 | "rust/memchr/memchr/fallback", 16 | "rust/memchr/memchr/naive", 17 | "rust/memchr/memchr/wasm32", 18 | "rust/memchr/memrchr", 19 | "rust/memchrold/memchr/oneshot", 20 | "rust/memchrold/memchr/prebuilt", 21 | "rust/memchrold/memchr/naive", 22 | "rust/memchrold/memrchr", 23 | ] 24 | 25 | [[bench]] 26 | model = "count-bytes" 27 | name = "huge2" 28 | regex = ['b', 'p'] 29 | haystack = { path = "sherlock/huge.txt" } 30 | count = 13_113 31 | engines = [ 32 | "rust/memchr/memchr2", 33 | "rust/memchr/memchr2/fallback", 34 | "rust/memchr/memchr2/naive", 35 | "rust/memchr/memchr2/wasm32", 36 | "rust/memchr/memrchr2", 37 | "rust/memchrold/memchr2", 38 | "rust/memchrold/memrchr2", 39 | ] 40 | 41 | [[bench]] 42 | model = "count-bytes" 43 | name = "huge3" 44 | regex = ['b', 'p', '.'] 45 | haystack = { path = "sherlock/huge.txt" } 46 | count = 19_538 47 | engines = [ 48 | "rust/memchr/memchr3", 49 | "rust/memchr/memchr3/fallback", 50 | "rust/memchr/memchr3/naive", 51 | "rust/memchr/memchr3/wasm32", 52 | "rust/memchr/memrchr3", 53 | "rust/memchrold/memchr3", 54 | "rust/memchrold/memrchr3", 55 | ] 56 | 57 | [[bench]] 58 | model = "count-bytes" 59 | name = "small1" 60 | regex = 'b' 61 | haystack = { path = "sherlock/small.txt" } 62 | count = 8 63 | engines = [ 64 | "libc/memchr/oneshot", 65 | "rust/bytecount/memchr/oneshot", 66 | "rust/bytecount/memchr/oneshot/wasm32", 67 | "rust/memchr/memchr/onlycount", 68 | "rust/memchr/memchr/onlycount/wasm32", 69 | "rust/memchr/memchr/oneshot", 70 | "rust/memchr/memchr/prebuilt", 71 | "rust/memchr/memchr/fallback", 72 | "rust/memchr/memchr/naive", 73 | "rust/memchr/memchr/wasm32", 74 | "rust/memchr/memrchr", 75 | "rust/memchrold/memchr/oneshot", 76 | "rust/memchrold/memchr/prebuilt", 77 | "rust/memchrold/memchr/naive", 78 | "rust/memchrold/memrchr", 79 | ] 80 | 81 | [[bench]] 82 | model = "count-bytes" 83 | name = "small2" 84 | regex = ['b', 'g'] 85 | haystack = { path = "sherlock/small.txt" } 86 | count = 16 87 | engines = [ 88 | "rust/memchr/memchr2", 89 | "rust/memchr/memchr2/fallback", 90 | "rust/memchr/memchr2/naive", 91 | "rust/memchr/memchr2/wasm32", 92 | "rust/memchr/memrchr2", 93 | "rust/memchrold/memchr2", 94 | "rust/memchrold/memrchr2", 95 | ] 96 | 97 | [[bench]] 98 | model = "count-bytes" 99 | name = "small3" 100 | regex = ['b', 'g', 'p'] 101 | haystack = { path = "sherlock/small.txt" } 102 | count = 24 103 | engines = [ 104 | "rust/memchr/memchr3", 105 | "rust/memchr/memchr3/fallback", 106 | "rust/memchr/memchr3/naive", 107 | "rust/memchr/memchr3/wasm32", 108 | "rust/memchr/memrchr3", 109 | "rust/memchrold/memchr3", 110 | "rust/memchrold/memrchr3", 111 | ] 112 | 113 | [[bench]] 114 | model = "count-bytes" 115 | name = "tiny1" 116 | regex = 'l' 117 | haystack = { path = "sherlock/tiny.txt" } 118 | count = 5 119 | engines = [ 120 | "libc/memchr/oneshot", 121 | "rust/bytecount/memchr/oneshot", 122 | "rust/bytecount/memchr/oneshot/wasm32", 123 | "rust/memchr/memchr/onlycount", 124 | "rust/memchr/memchr/onlycount/wasm32", 125 | "rust/memchr/memchr/oneshot", 126 | "rust/memchr/memchr/prebuilt", 127 | "rust/memchr/memchr/fallback", 128 | "rust/memchr/memchr/naive", 129 | "rust/memchr/memchr/wasm32", 130 | "rust/memchr/memrchr", 131 | "rust/memchrold/memchr/oneshot", 132 | "rust/memchrold/memchr/prebuilt", 133 | "rust/memchrold/memchr/naive", 134 | "rust/memchrold/memrchr", 135 | ] 136 | 137 | [[bench]] 138 | model = "count-bytes" 139 | name = "tiny2" 140 | regex = ['l', 's'] 141 | haystack = { path = "sherlock/tiny.txt" } 142 | count = 10 143 | engines = [ 144 | "rust/memchr/memchr2", 145 | "rust/memchr/memchr2/fallback", 146 | "rust/memchr/memchr2/naive", 147 | "rust/memchr/memchr2/wasm32", 148 | "rust/memchr/memrchr2", 149 | "rust/memchrold/memchr2", 150 | "rust/memchrold/memrchr2", 151 | ] 152 | 153 | [[bench]] 154 | model = "count-bytes" 155 | name = "tiny3" 156 | regex = ['l', 's', 'e'] 157 | haystack = { path = "sherlock/tiny.txt" } 158 | count = 16 159 | engines = [ 160 | "rust/memchr/memchr3", 161 | "rust/memchr/memchr3/fallback", 162 | "rust/memchr/memchr3/naive", 163 | "rust/memchr/memchr3/wasm32", 164 | "rust/memchr/memrchr3", 165 | "rust/memchrold/memchr3", 166 | "rust/memchrold/memrchr3", 167 | ] 168 | -------------------------------------------------------------------------------- /benchmarks/definitions/memchr/sherlock/verycommon.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count-bytes" 3 | name = "huge1" 4 | regex = ' ' 5 | haystack = { path = "sherlock/huge.txt" } 6 | count = 97_626 7 | engines = [ 8 | "libc/memchr/oneshot", 9 | "rust/bytecount/memchr/oneshot", 10 | "rust/bytecount/memchr/oneshot/wasm32", 11 | "rust/memchr/memchr/onlycount", 12 | "rust/memchr/memchr/onlycount/wasm32", 13 | "rust/memchr/memchr/oneshot", 14 | "rust/memchr/memchr/prebuilt", 15 | "rust/memchr/memchr/fallback", 16 | "rust/memchr/memchr/naive", 17 | "rust/memchr/memchr/wasm32", 18 | "rust/memchr/memrchr", 19 | "rust/memchrold/memchr/oneshot", 20 | "rust/memchrold/memchr/prebuilt", 21 | "rust/memchrold/memchr/naive", 22 | "rust/memchrold/memrchr", 23 | ] 24 | 25 | [[bench]] 26 | model = "count-bytes" 27 | name = "small1" 28 | regex = ' ' 29 | haystack = { path = "sherlock/small.txt" } 30 | count = 106 31 | engines = [ 32 | "libc/memchr/oneshot", 33 | "rust/bytecount/memchr/oneshot", 34 | "rust/bytecount/memchr/oneshot/wasm32", 35 | "rust/memchr/memchr/onlycount", 36 | "rust/memchr/memchr/onlycount/wasm32", 37 | "rust/memchr/memchr/oneshot", 38 | "rust/memchr/memchr/prebuilt", 39 | "rust/memchr/memchr/fallback", 40 | "rust/memchr/memchr/naive", 41 | "rust/memchr/memchr/wasm32", 42 | "rust/memchr/memrchr", 43 | "rust/memchrold/memchr/oneshot", 44 | "rust/memchrold/memchr/prebuilt", 45 | "rust/memchrold/memchr/naive", 46 | "rust/memchrold/memrchr", 47 | ] 48 | -------------------------------------------------------------------------------- /benchmarks/definitions/memmem/byterank.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count" 3 | name = "binary" 4 | regex = '\x00\x00\xDD\xDD\x00' 5 | haystack = { path = "rg-13.0.0.txt" } 6 | count = 1 7 | engines = [ 8 | "libc/memmem/oneshot", 9 | "rust/jetscii/memmem/oneshot", 10 | "rust/jetscii/memmem/prebuilt", 11 | "rust/memchr/memmem/oneshot", 12 | "rust/memchr/memmem/prebuilt", 13 | "rust/memchr/memmem/binary", 14 | "rust/memchr/memmem/twoway", 15 | "rust/memchr/memmem/rabinkarp", 16 | "rust/memchr/memmem/shiftor", 17 | "rust/memchr/memmem/wasm32", 18 | "rust/memchrold/memmem/oneshot", 19 | "rust/memchrold/memmem/prebuilt", 20 | "rust/sliceslice/memmem/oneshot", 21 | "rust/sliceslice/memmem/prebuilt", 22 | "stringzilla/memmem/oneshot", 23 | ] 24 | analysis = ''' 25 | This benchmark demonstrates the utility of using one's own heuristic ranking 26 | function for the background frequency of bytes. In this case, the needle has 27 | NUL bytes which are quite uncommon in plain text but common in binary data. 28 | Since the heuristics in the `memchr` crate are more or less targeted at plain 29 | text, the NUL byte is chosen as a predicate because it is believed to be rare. 30 | But since the haystack is an executable, i.e. binary data, this choice is poor. 31 | 32 | So this measures a separate engine, `rust/memchr/memmem/binary`, which is like 33 | `rust/memchr/memmem/prebuilt`, except it uses a ranking function tuned from 34 | binary data. In this case, the NUL byte is considered common and thus not used 35 | as a predicate. This leads to a dramatic improvement in throughput. 36 | ''' 37 | -------------------------------------------------------------------------------- /benchmarks/definitions/memmem/code.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count" 3 | name = "rust-library-never-fn-strength" 4 | regex = 'fn strength' 5 | haystack = { path = "code/rust-library.rs" } 6 | count = 0 7 | engines = [ 8 | "libc/memmem/oneshot", 9 | "rust/jetscii/memmem/oneshot", 10 | "rust/jetscii/memmem/prebuilt", 11 | "rust/memchr/memmem/oneshot", 12 | "rust/memchr/memmem/prebuilt", 13 | "rust/memchr/memmem/twoway", 14 | "rust/memchr/memmem/rabinkarp", 15 | "rust/memchr/memmem/shiftor", 16 | "rust/memchr/memmem/wasm32", 17 | "rust/memchrold/memmem/oneshot", 18 | "rust/memchrold/memmem/prebuilt", 19 | "rust/sliceslice/memmem/oneshot", 20 | "rust/sliceslice/memmem/prebuilt", 21 | "rust/std/memmem/oneshot", 22 | "rust/std/memmem/prebuilt", 23 | "stringzilla/memmem/oneshot", 24 | ] 25 | 26 | [[bench]] 27 | model = "count" 28 | name = "rust-library-never-fn-strength-paren" 29 | regex = 'fn strength(' 30 | haystack = { path = "code/rust-library.rs" } 31 | count = 0 32 | engines = [ 33 | "libc/memmem/oneshot", 34 | "rust/jetscii/memmem/oneshot", 35 | "rust/jetscii/memmem/prebuilt", 36 | "rust/memchr/memmem/oneshot", 37 | "rust/memchr/memmem/prebuilt", 38 | "rust/memchr/memmem/twoway", 39 | "rust/memchr/memmem/rabinkarp", 40 | "rust/memchr/memmem/shiftor", 41 | "rust/memchr/memmem/wasm32", 42 | "rust/memchrold/memmem/oneshot", 43 | "rust/memchrold/memmem/prebuilt", 44 | "rust/sliceslice/memmem/oneshot", 45 | "rust/sliceslice/memmem/prebuilt", 46 | "rust/std/memmem/oneshot", 47 | "rust/std/memmem/prebuilt", 48 | "stringzilla/memmem/oneshot", 49 | ] 50 | 51 | [[bench]] 52 | model = "count" 53 | name = "rust-library-never-fn-quux" 54 | regex = 'fn quux(' 55 | haystack = { path = "code/rust-library.rs" } 56 | count = 0 57 | engines = [ 58 | "libc/memmem/oneshot", 59 | "rust/jetscii/memmem/oneshot", 60 | "rust/jetscii/memmem/prebuilt", 61 | "rust/memchr/memmem/oneshot", 62 | "rust/memchr/memmem/prebuilt", 63 | "rust/memchr/memmem/twoway", 64 | "rust/memchr/memmem/rabinkarp", 65 | "rust/memchr/memmem/shiftor", 66 | "rust/memchr/memmem/wasm32", 67 | "rust/memchrold/memmem/oneshot", 68 | "rust/memchrold/memmem/prebuilt", 69 | "rust/sliceslice/memmem/oneshot", 70 | "rust/sliceslice/memmem/prebuilt", 71 | "rust/std/memmem/oneshot", 72 | "rust/std/memmem/prebuilt", 73 | "stringzilla/memmem/oneshot", 74 | ] 75 | 76 | [[bench]] 77 | model = "count" 78 | name = "rust-library-rare-fn-from-str" 79 | regex = 'pub fn from_str(' 80 | haystack = { path = "code/rust-library.rs" } 81 | count = 1 82 | engines = [ 83 | "libc/memmem/oneshot", 84 | "rust/jetscii/memmem/oneshot", 85 | "rust/jetscii/memmem/prebuilt", 86 | "rust/memchr/memmem/oneshot", 87 | "rust/memchr/memmem/prebuilt", 88 | "rust/memchr/memmem/twoway", 89 | "rust/memchr/memmem/rabinkarp", 90 | "rust/memchr/memmem/wasm32", 91 | "rust/memchrold/memmem/oneshot", 92 | "rust/memchrold/memmem/prebuilt", 93 | "rust/sliceslice/memmem/oneshot", 94 | "rust/sliceslice/memmem/prebuilt", 95 | "rust/std/memmem/oneshot", 96 | "rust/std/memmem/prebuilt", 97 | "stringzilla/memmem/oneshot", 98 | ] 99 | 100 | [[bench]] 101 | model = "count" 102 | name = "rust-library-common-fn-is-empty" 103 | regex = 'fn is_empty' 104 | haystack = { path = "code/rust-library.rs" } 105 | count = 17 106 | engines = [ 107 | "libc/memmem/oneshot", 108 | "rust/jetscii/memmem/oneshot", 109 | "rust/jetscii/memmem/prebuilt", 110 | "rust/memchr/memmem/oneshot", 111 | "rust/memchr/memmem/prebuilt", 112 | "rust/memchr/memmem/twoway", 113 | "rust/memchr/memmem/rabinkarp", 114 | "rust/memchr/memmem/shiftor", 115 | "rust/memchr/memmem/wasm32", 116 | "rust/memchrold/memmem/oneshot", 117 | "rust/memchrold/memmem/prebuilt", 118 | "rust/std/memmem/oneshot", 119 | "rust/std/memmem/prebuilt", 120 | "stringzilla/memmem/oneshot", 121 | ] 122 | 123 | [[bench]] 124 | model = "count" 125 | name = "rust-library-common-fn" 126 | regex = 'fn' 127 | haystack = { path = "code/rust-library.rs" } 128 | count = 2985 129 | engines = [ 130 | "libc/memmem/oneshot", 131 | "rust/jetscii/memmem/oneshot", 132 | "rust/jetscii/memmem/prebuilt", 133 | "rust/memchr/memmem/oneshot", 134 | "rust/memchr/memmem/prebuilt", 135 | "rust/memchr/memmem/twoway", 136 | "rust/memchr/memmem/rabinkarp", 137 | "rust/memchr/memmem/shiftor", 138 | "rust/memchr/memmem/wasm32", 139 | "rust/memchrold/memmem/oneshot", 140 | "rust/memchrold/memmem/prebuilt", 141 | "rust/std/memmem/oneshot", 142 | "rust/std/memmem/prebuilt", 143 | "stringzilla/memmem/oneshot", 144 | ] 145 | 146 | [[bench]] 147 | model = "count" 148 | name = "rust-library-common-paren" 149 | regex = '(' 150 | haystack = { path = "code/rust-library.rs" } 151 | count = 30_193 152 | engines = [ 153 | "libc/memmem/oneshot", 154 | "rust/jetscii/memmem/oneshot", 155 | "rust/jetscii/memmem/prebuilt", 156 | "rust/memchr/memmem/oneshot", 157 | "rust/memchr/memmem/prebuilt", 158 | "rust/memchr/memmem/twoway", 159 | "rust/memchr/memmem/rabinkarp", 160 | "rust/memchr/memmem/shiftor", 161 | "rust/memchr/memmem/wasm32", 162 | "rust/memchrold/memmem/oneshot", 163 | "rust/memchrold/memmem/prebuilt", 164 | "rust/std/memmem/oneshot", 165 | "rust/std/memmem/prebuilt", 166 | "stringzilla/memmem/oneshot", 167 | ] 168 | 169 | [[bench]] 170 | model = "count" 171 | name = "rust-library-common-let" 172 | regex = 'let' 173 | haystack = { path = "code/rust-library.rs" } 174 | count = 4737 175 | engines = [ 176 | "libc/memmem/oneshot", 177 | "rust/jetscii/memmem/oneshot", 178 | "rust/jetscii/memmem/prebuilt", 179 | "rust/memchr/memmem/oneshot", 180 | "rust/memchr/memmem/prebuilt", 181 | "rust/memchr/memmem/twoway", 182 | "rust/memchr/memmem/rabinkarp", 183 | "rust/memchr/memmem/shiftor", 184 | "rust/memchr/memmem/wasm32", 185 | "rust/memchrold/memmem/oneshot", 186 | "rust/memchrold/memmem/prebuilt", 187 | "rust/std/memmem/oneshot", 188 | "rust/std/memmem/prebuilt", 189 | "stringzilla/memmem/oneshot", 190 | ] 191 | -------------------------------------------------------------------------------- /benchmarks/definitions/memmem/pathological.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count" 3 | name = "md5-huge-no-hash" 4 | regex = '61a1a40effcf97de24505f154a306597' 5 | haystack = { path = "pathological/md5-huge.txt" } 6 | count = 0 7 | engines = [ 8 | "libc/memmem/oneshot", 9 | "rust/jetscii/memmem/oneshot", 10 | "rust/jetscii/memmem/prebuilt", 11 | "rust/memchr/memmem/oneshot", 12 | "rust/memchr/memmem/prebuilt", 13 | "rust/memchr/memmem/twoway", 14 | "rust/memchr/memmem/rabinkarp", 15 | "rust/memchr/memmem/wasm32", 16 | "rust/memchrold/memmem/oneshot", 17 | "rust/memchrold/memmem/prebuilt", 18 | "rust/sliceslice/memmem/oneshot", 19 | "rust/sliceslice/memmem/prebuilt", 20 | "rust/std/memmem/oneshot", 21 | "rust/std/memmem/prebuilt", 22 | "stringzilla/memmem/oneshot", 23 | ] 24 | 25 | [[bench]] 26 | model = "count" 27 | name = "md5-huge-last-hash" 28 | regex = '831df319d8597f5bc793d690f08b159b' 29 | haystack = { path = "pathological/md5-huge.txt" } 30 | count = 1 31 | engines = [ 32 | "libc/memmem/oneshot", 33 | "rust/jetscii/memmem/oneshot", 34 | "rust/jetscii/memmem/prebuilt", 35 | "rust/memchr/memmem/oneshot", 36 | "rust/memchr/memmem/prebuilt", 37 | "rust/memchr/memmem/twoway", 38 | "rust/memchr/memmem/rabinkarp", 39 | "rust/memchr/memmem/wasm32", 40 | "rust/memchrold/memmem/oneshot", 41 | "rust/memchrold/memmem/prebuilt", 42 | "rust/sliceslice/memmem/oneshot", 43 | "rust/sliceslice/memmem/prebuilt", 44 | "rust/std/memmem/oneshot", 45 | "rust/std/memmem/prebuilt", 46 | "stringzilla/memmem/oneshot", 47 | ] 48 | 49 | [[bench]] 50 | model = "count" 51 | name = "rare-repeated-huge-tricky" 52 | regex = 'abczdef' 53 | haystack = { path = "pathological/repeated-rare-huge.txt" } 54 | count = 0 55 | engines = [ 56 | "libc/memmem/oneshot", 57 | "rust/jetscii/memmem/oneshot", 58 | "rust/jetscii/memmem/prebuilt", 59 | "rust/memchr/memmem/oneshot", 60 | "rust/memchr/memmem/prebuilt", 61 | "rust/memchr/memmem/twoway", 62 | "rust/memchr/memmem/rabinkarp", 63 | "rust/memchr/memmem/shiftor", 64 | "rust/memchr/memmem/wasm32", 65 | "rust/memchrold/memmem/oneshot", 66 | "rust/memchrold/memmem/prebuilt", 67 | "rust/sliceslice/memmem/oneshot", 68 | "rust/sliceslice/memmem/prebuilt", 69 | "rust/std/memmem/oneshot", 70 | "rust/std/memmem/prebuilt", 71 | "stringzilla/memmem/oneshot", 72 | ] 73 | 74 | [[bench]] 75 | model = "count" 76 | name = "rare-repeated-huge-match" 77 | regex = 'zzzzzzzzzz' 78 | haystack = { path = "pathological/repeated-rare-huge.txt" } 79 | count = 50_010 80 | engines = [ 81 | "libc/memmem/oneshot", 82 | "rust/jetscii/memmem/oneshot", 83 | "rust/jetscii/memmem/prebuilt", 84 | "rust/memchr/memmem/oneshot", 85 | "rust/memchr/memmem/prebuilt", 86 | "rust/memchr/memmem/twoway", 87 | "rust/memchr/memmem/rabinkarp", 88 | "rust/memchr/memmem/shiftor", 89 | "rust/memchr/memmem/wasm32", 90 | "rust/memchrold/memmem/oneshot", 91 | "rust/memchrold/memmem/prebuilt", 92 | "rust/std/memmem/oneshot", 93 | "rust/std/memmem/prebuilt", 94 | "stringzilla/memmem/oneshot", 95 | ] 96 | 97 | [[bench]] 98 | model = "count" 99 | name = "rare-repeated-small-tricky" 100 | regex = 'abczdef' 101 | haystack = { path = "pathological/repeated-rare-small.txt" } 102 | count = 0 103 | engines = [ 104 | "libc/memmem/oneshot", 105 | "rust/jetscii/memmem/oneshot", 106 | "rust/jetscii/memmem/prebuilt", 107 | "rust/memchr/memmem/oneshot", 108 | "rust/memchr/memmem/prebuilt", 109 | "rust/memchr/memmem/twoway", 110 | "rust/memchr/memmem/rabinkarp", 111 | "rust/memchr/memmem/shiftor", 112 | "rust/memchr/memmem/wasm32", 113 | "rust/memchrold/memmem/oneshot", 114 | "rust/memchrold/memmem/prebuilt", 115 | "rust/sliceslice/memmem/oneshot", 116 | "rust/sliceslice/memmem/prebuilt", 117 | "rust/std/memmem/oneshot", 118 | "rust/std/memmem/prebuilt", 119 | "stringzilla/memmem/oneshot", 120 | ] 121 | 122 | [[bench]] 123 | model = "count" 124 | name = "rare-repeated-small-match" 125 | regex = 'zzzzzzzzzz' 126 | haystack = { path = "pathological/repeated-rare-small.txt" } 127 | count = 100 128 | engines = [ 129 | "libc/memmem/oneshot", 130 | "rust/jetscii/memmem/oneshot", 131 | "rust/jetscii/memmem/prebuilt", 132 | "rust/memchr/memmem/oneshot", 133 | "rust/memchr/memmem/prebuilt", 134 | "rust/memchr/memmem/twoway", 135 | "rust/memchr/memmem/rabinkarp", 136 | "rust/memchr/memmem/shiftor", 137 | "rust/memchr/memmem/wasm32", 138 | "rust/memchrold/memmem/oneshot", 139 | "rust/memchrold/memmem/prebuilt", 140 | "rust/std/memmem/oneshot", 141 | "rust/std/memmem/prebuilt", 142 | "stringzilla/memmem/oneshot", 143 | ] 144 | 145 | [[bench]] 146 | model = "count" 147 | name = "defeat-simple-vector-alphabet" 148 | regex = 'qbz' 149 | haystack = { path = "pathological/defeat-simple-vector.txt" } 150 | count = 1 151 | engines = [ 152 | "libc/memmem/oneshot", 153 | "rust/jetscii/memmem/oneshot", 154 | "rust/jetscii/memmem/prebuilt", 155 | "rust/memchr/memmem/oneshot", 156 | "rust/memchr/memmem/prebuilt", 157 | "rust/memchr/memmem/twoway", 158 | "rust/memchr/memmem/rabinkarp", 159 | "rust/memchr/memmem/shiftor", 160 | "rust/memchr/memmem/wasm32", 161 | "rust/memchrold/memmem/oneshot", 162 | "rust/memchrold/memmem/prebuilt", 163 | "rust/sliceslice/memmem/oneshot", 164 | "rust/sliceslice/memmem/prebuilt", 165 | "rust/std/memmem/oneshot", 166 | "rust/std/memmem/prebuilt", 167 | "stringzilla/memmem/oneshot", 168 | ] 169 | 170 | [[bench]] 171 | model = "count" 172 | name = "defeat-simple-vector-freq-alphabet" 173 | regex = 'qjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz' 174 | haystack = { path = "pathological/defeat-simple-vector-freq.txt" } 175 | count = 1 176 | engines = [ 177 | "libc/memmem/oneshot", 178 | "rust/jetscii/memmem/oneshot", 179 | "rust/jetscii/memmem/prebuilt", 180 | "rust/memchr/memmem/oneshot", 181 | "rust/memchr/memmem/prebuilt", 182 | "rust/memchr/memmem/twoway", 183 | "rust/memchr/memmem/rabinkarp", 184 | "rust/memchr/memmem/wasm32", 185 | "rust/memchrold/memmem/oneshot", 186 | "rust/memchrold/memmem/prebuilt", 187 | "rust/sliceslice/memmem/oneshot", 188 | "rust/sliceslice/memmem/prebuilt", 189 | "rust/std/memmem/oneshot", 190 | "rust/std/memmem/prebuilt", 191 | "stringzilla/memmem/oneshot", 192 | ] 193 | 194 | [[bench]] 195 | model = "count" 196 | name = "defeat-simple-vector-repeated-alphabet" 197 | regex = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzaz' 198 | haystack = { path = "pathological/defeat-simple-vector-repeated.txt" } 199 | count = 1 200 | engines = [ 201 | "libc/memmem/oneshot", 202 | "rust/jetscii/memmem/oneshot", 203 | "rust/jetscii/memmem/prebuilt", 204 | "rust/memchr/memmem/oneshot", 205 | "rust/memchr/memmem/prebuilt", 206 | "rust/memchr/memmem/twoway", 207 | "rust/memchr/memmem/rabinkarp", 208 | "rust/memchr/memmem/wasm32", 209 | "rust/memchrold/memmem/oneshot", 210 | "rust/memchrold/memmem/prebuilt", 211 | "rust/sliceslice/memmem/oneshot", 212 | "rust/sliceslice/memmem/prebuilt", 213 | "rust/std/memmem/oneshot", 214 | "rust/std/memmem/prebuilt", 215 | "stringzilla/memmem/oneshot", 216 | ] 217 | -------------------------------------------------------------------------------- /benchmarks/definitions/memmem/sliceslice.toml: -------------------------------------------------------------------------------- 1 | analysis = ''' 2 | These benchmarks were lifted almost verbtaim out of the sliceslice crate. The 3 | reason why we have these benchmarks is because they were the primary thing 4 | that motivated me to write the memmem implementation in the `memchr` crate. In 5 | particular, my existing substring search implementation in the `bstr` crate 6 | did quite poorly on these particular benchmarks. Moreover, while the benchmark 7 | setup is a little weird, these benchmarks do reflect cases that I think are 8 | somewhat common: 9 | 10 | * In the sliceslice-words/words case, the benchmark is primarily about 11 | searching very short haystacks using common English words. 12 | * In the sliceslice-words/i386 case, the benchmark is primarily about searching 13 | a longer haystack with common English words. 14 | 15 | N.B. In the sliceslice crate, the benchmarks are called "short" and "long." 16 | Here, we call them sliceslice-words/words and sliceslice-i386/words, 17 | respectively. The name change was made to be consistent with the naming 18 | convention used for other benchmarks. 19 | 20 | The main thing that's "weird" about these benchmarks is that each iteration 21 | involves a lot of work. All of the other benchmarks in this crate focus on one 22 | specific needle with one specific haystack, and each iteration is a single 23 | search or iteration. But in these benchmarks, each iteration involves searching 24 | with many needles against potentially many haystacks. Nevertheless, these have 25 | proven useful targets for optimization. 26 | ''' 27 | 28 | [[bench]] 29 | model = "needles-in-needles" 30 | name = "short" 31 | regex = { path = "sliceslice/words-by-length-desc.txt", per-line = "pattern" } 32 | haystack = '' # not used in this model 33 | count = 4585 34 | engines = [ 35 | "rust/memchr/memmem/prebuilt", 36 | "rust/memchrold/memmem/prebuilt", 37 | "rust/sliceslice/memmem/prebuilt", 38 | ] 39 | 40 | [[bench]] 41 | model = "needles-in-haystack" 42 | name = "seemingly-random" 43 | regex = { path = "sliceslice/words-by-length-desc.txt", per-line = "pattern" } 44 | haystack = { path = "sliceslice/haystack.txt" } 45 | count = 106 46 | engines = [ 47 | "rust/memchr/memmem/prebuilt", 48 | "rust/memchrold/memmem/prebuilt", 49 | "rust/sliceslice/memmem/prebuilt", 50 | ] 51 | 52 | [[bench]] 53 | model = "needles-in-haystack" 54 | name = "i386" 55 | regex = { path = "sliceslice/words-by-length-desc.txt", per-line = "pattern" } 56 | haystack = { path = "sliceslice/i386.txt" } 57 | count = 4585 58 | engines = [ 59 | "rust/memchr/memmem/prebuilt", 60 | "rust/memchrold/memmem/prebuilt", 61 | "rust/sliceslice/memmem/prebuilt", 62 | ] 63 | -------------------------------------------------------------------------------- /benchmarks/definitions/memmem/subtitles/common.toml: -------------------------------------------------------------------------------- 1 | [[bench]] 2 | model = "count" 3 | name = "huge-en-that" 4 | regex = 'that' 5 | haystack = { path = "opensubtitles/en-huge.txt" } 6 | count = 865 7 | engines = [ 8 | "libc/memmem/oneshot", 9 | "rust/jetscii/memmem/oneshot", 10 | "rust/jetscii/memmem/prebuilt", 11 | "rust/memchr/memmem/oneshot", 12 | "rust/memchr/memmem/prebuilt", 13 | "rust/memchr/memmem/twoway", 14 | "rust/memchr/memmem/rabinkarp", 15 | "rust/memchr/memmem/shiftor", 16 | "rust/memchr/memmem/wasm32", 17 | "rust/memchrold/memmem/oneshot", 18 | "rust/memchrold/memmem/prebuilt", 19 | "rust/std/memmem/oneshot", 20 | "rust/std/memmem/prebuilt", 21 | "stringzilla/memmem/oneshot", 22 | ] 23 | 24 | [[bench]] 25 | model = "count" 26 | name = "huge-en-you" 27 | regex = 'you' 28 | haystack = { path = "opensubtitles/en-huge.txt" } 29 | count = 5009 30 | engines = [ 31 | "libc/memmem/oneshot", 32 | "rust/jetscii/memmem/oneshot", 33 | "rust/jetscii/memmem/prebuilt", 34 | "rust/memchr/memmem/oneshot", 35 | "rust/memchr/memmem/prebuilt", 36 | "rust/memchr/memmem/twoway", 37 | "rust/memchr/memmem/rabinkarp", 38 | "rust/memchr/memmem/shiftor", 39 | "rust/memchr/memmem/wasm32", 40 | "rust/memchrold/memmem/oneshot", 41 | "rust/memchrold/memmem/prebuilt", 42 | "rust/std/memmem/oneshot", 43 | "rust/std/memmem/prebuilt", 44 | "stringzilla/memmem/oneshot", 45 | ] 46 | 47 | [[bench]] 48 | model = "count" 49 | name = "huge-en-one-space" 50 | regex = ' ' 51 | haystack = { path = "opensubtitles/en-huge.txt" } 52 | count = 96_606 53 | engines = [ 54 | "libc/memmem/oneshot", 55 | "rust/jetscii/memmem/oneshot", 56 | "rust/jetscii/memmem/prebuilt", 57 | "rust/memchr/memmem/oneshot", 58 | "rust/memchr/memmem/prebuilt", 59 | "rust/memchr/memmem/twoway", 60 | "rust/memchr/memmem/rabinkarp", 61 | "rust/memchr/memmem/shiftor", 62 | "rust/memchr/memmem/wasm32", 63 | "rust/memchrold/memmem/oneshot", 64 | "rust/memchrold/memmem/prebuilt", 65 | "rust/std/memmem/oneshot", 66 | "rust/std/memmem/prebuilt", 67 | "stringzilla/memmem/oneshot", 68 | ] 69 | 70 | [[bench]] 71 | model = "count" 72 | name = "huge-ru-that" 73 | regex = 'что' 74 | haystack = { path = "opensubtitles/ru-huge.txt" } 75 | count = 998 76 | engines = [ 77 | "libc/memmem/oneshot", 78 | "rust/jetscii/memmem/oneshot", 79 | "rust/jetscii/memmem/prebuilt", 80 | "rust/memchr/memmem/oneshot", 81 | "rust/memchr/memmem/prebuilt", 82 | "rust/memchr/memmem/twoway", 83 | "rust/memchr/memmem/rabinkarp", 84 | "rust/memchr/memmem/shiftor", 85 | "rust/memchr/memmem/wasm32", 86 | "rust/memchrold/memmem/oneshot", 87 | "rust/memchrold/memmem/prebuilt", 88 | "rust/std/memmem/oneshot", 89 | "rust/std/memmem/prebuilt", 90 | "stringzilla/memmem/oneshot", 91 | ] 92 | 93 | [[bench]] 94 | model = "count" 95 | name = "huge-ru-not" 96 | regex = 'не' 97 | haystack = { path = "opensubtitles/ru-huge.txt" } 98 | count = 3092 99 | engines = [ 100 | "libc/memmem/oneshot", 101 | "rust/jetscii/memmem/oneshot", 102 | "rust/jetscii/memmem/prebuilt", 103 | "rust/memchr/memmem/oneshot", 104 | "rust/memchr/memmem/prebuilt", 105 | "rust/memchr/memmem/twoway", 106 | "rust/memchr/memmem/rabinkarp", 107 | "rust/memchr/memmem/shiftor", 108 | "rust/memchr/memmem/wasm32", 109 | "rust/memchrold/memmem/oneshot", 110 | "rust/memchrold/memmem/prebuilt", 111 | "rust/std/memmem/oneshot", 112 | "rust/std/memmem/prebuilt", 113 | "stringzilla/memmem/oneshot", 114 | ] 115 | 116 | [[bench]] 117 | model = "count" 118 | name = "huge-ru-one-space" 119 | regex = ' ' 120 | haystack = { path = "opensubtitles/ru-huge.txt" } 121 | count = 46_941 122 | engines = [ 123 | "libc/memmem/oneshot", 124 | "rust/jetscii/memmem/oneshot", 125 | "rust/jetscii/memmem/prebuilt", 126 | "rust/memchr/memmem/oneshot", 127 | "rust/memchr/memmem/prebuilt", 128 | "rust/memchr/memmem/twoway", 129 | "rust/memchr/memmem/rabinkarp", 130 | "rust/memchr/memmem/shiftor", 131 | "rust/memchr/memmem/wasm32", 132 | "rust/memchrold/memmem/oneshot", 133 | "rust/memchrold/memmem/prebuilt", 134 | "rust/std/memmem/oneshot", 135 | "rust/std/memmem/prebuilt", 136 | "stringzilla/memmem/oneshot", 137 | ] 138 | 139 | [[bench]] 140 | model = "count" 141 | name = "huge-zh-that" 142 | regex = '那' 143 | haystack = { path = "opensubtitles/zh-huge.txt" } 144 | count = 1056 145 | engines = [ 146 | "libc/memmem/oneshot", 147 | "rust/jetscii/memmem/oneshot", 148 | "rust/jetscii/memmem/prebuilt", 149 | "rust/memchr/memmem/oneshot", 150 | "rust/memchr/memmem/prebuilt", 151 | "rust/memchr/memmem/twoway", 152 | "rust/memchr/memmem/rabinkarp", 153 | "rust/memchr/memmem/shiftor", 154 | "rust/memchr/memmem/wasm32", 155 | "rust/memchrold/memmem/oneshot", 156 | "rust/memchrold/memmem/prebuilt", 157 | "rust/std/memmem/oneshot", 158 | "rust/std/memmem/prebuilt", 159 | "stringzilla/memmem/oneshot", 160 | ] 161 | 162 | [[bench]] 163 | model = "count" 164 | name = "huge-zh-do-not" 165 | regex = '不' 166 | haystack = { path = "opensubtitles/zh-huge.txt" } 167 | count = 2751 168 | engines = [ 169 | "libc/memmem/oneshot", 170 | "rust/jetscii/memmem/oneshot", 171 | "rust/jetscii/memmem/prebuilt", 172 | "rust/memchr/memmem/oneshot", 173 | "rust/memchr/memmem/prebuilt", 174 | "rust/memchr/memmem/twoway", 175 | "rust/memchr/memmem/rabinkarp", 176 | "rust/memchr/memmem/shiftor", 177 | "rust/memchr/memmem/wasm32", 178 | "rust/memchrold/memmem/oneshot", 179 | "rust/memchrold/memmem/prebuilt", 180 | "rust/std/memmem/oneshot", 181 | "rust/std/memmem/prebuilt", 182 | "stringzilla/memmem/oneshot", 183 | ] 184 | 185 | [[bench]] 186 | model = "count" 187 | name = "huge-zh-one-space" 188 | regex = ' ' 189 | haystack = { path = "opensubtitles/zh-huge.txt" } 190 | count = 17_232 191 | engines = [ 192 | "libc/memmem/oneshot", 193 | "rust/jetscii/memmem/oneshot", 194 | "rust/jetscii/memmem/prebuilt", 195 | "rust/memchr/memmem/oneshot", 196 | "rust/memchr/memmem/prebuilt", 197 | "rust/memchr/memmem/twoway", 198 | "rust/memchr/memmem/rabinkarp", 199 | "rust/memchr/memmem/shiftor", 200 | "rust/memchr/memmem/wasm32", 201 | "rust/memchrold/memmem/oneshot", 202 | "rust/memchrold/memmem/prebuilt", 203 | "rust/std/memmem/oneshot", 204 | "rust/std/memmem/prebuilt", 205 | "stringzilla/memmem/oneshot", 206 | ] 207 | -------------------------------------------------------------------------------- /benchmarks/engines/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /benchmarks/engines/libc/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.9.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "libc" 23 | version = "0.2.155" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 26 | 27 | [[package]] 28 | name = "main" 29 | version = "0.0.0" 30 | dependencies = [ 31 | "anyhow", 32 | "libc", 33 | "shared", 34 | ] 35 | 36 | [[package]] 37 | name = "memchr" 38 | version = "2.7.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 41 | 42 | [[package]] 43 | name = "proc-macro2" 44 | version = "1.0.85" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 47 | dependencies = [ 48 | "unicode-ident", 49 | ] 50 | 51 | [[package]] 52 | name = "quote" 53 | version = "1.0.36" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 56 | dependencies = [ 57 | "proc-macro2", 58 | ] 59 | 60 | [[package]] 61 | name = "serde" 62 | version = "1.0.203" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 65 | dependencies = [ 66 | "serde_derive", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_derive" 71 | version = "1.0.203" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "shared" 82 | version = "0.1.0" 83 | dependencies = [ 84 | "anyhow", 85 | "bstr", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "2.0.66" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.12" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 104 | -------------------------------------------------------------------------------- /benchmarks/engines/libc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "0.0.0" # unclear how to get libc version in portable way 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | libc = "0.2.147" 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/libc/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let Some(arg) = std::env::args_os().nth(1) else { 7 | anyhow::bail!("Usage: runner ( | --version)") 8 | }; 9 | let Ok(arg) = arg.into_string() else { 10 | anyhow::bail!("argument given is not valid UTF-8") 11 | }; 12 | if arg == "--version" { 13 | // FIXME: How do we get libc version? 14 | writeln!(std::io::stdout(), "unknown")?; 15 | return Ok(()); 16 | } 17 | let engine = arg; 18 | let b = Benchmark::from_stdin()?; 19 | let samples = match (&*engine, &*b.model) { 20 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 21 | ("memchr-oneshot", "count-bytes") => memchr_oneshot_count(&b)?, 22 | (engine, model) => { 23 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 24 | } 25 | }; 26 | let mut stdout = std::io::stdout().lock(); 27 | for s in samples.iter() { 28 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 29 | } 30 | Ok(()) 31 | } 32 | 33 | fn memchr_oneshot_count(b: &Benchmark) -> anyhow::Result> { 34 | let haystack = &b.haystack; 35 | let needle = b.one_needle_byte()?; 36 | shared::run(b, || Ok(shared::count_memchr(haystack, needle, libc_memchr))) 37 | } 38 | 39 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 40 | let haystack = &b.haystack; 41 | let needle = b.one_needle()?; 42 | shared::run(b, || Ok(shared::count_memmem(haystack, needle, libc_memmem))) 43 | } 44 | 45 | /// A safe wrapper around libc's `memchr` function. In particular, this 46 | /// converts memchr's pointer return to an index offset into `haystack`. 47 | fn libc_memchr(haystack: &[u8], needle: u8) -> Option { 48 | // SAFETY: This is safe to call since all pointers are valid. 49 | let p = unsafe { 50 | libc::memchr( 51 | haystack.as_ptr().cast(), 52 | needle as libc::c_int, 53 | haystack.len(), 54 | ) 55 | }; 56 | if p.is_null() { 57 | None 58 | } else { 59 | Some(p as usize - (haystack.as_ptr() as usize)) 60 | } 61 | } 62 | 63 | /// A safe wrapper around libc's `memmem` function. In particular, this 64 | /// converts memmem's pointer return to an index offset into `haystack`. 65 | fn libc_memmem(haystack: &[u8], needle: &[u8]) -> Option { 66 | // SAFETY: We know that both our haystack and needle pointers are valid and 67 | // non-null, and we also know that the lengths of each corresponds to the 68 | // number of bytes at that memory region. 69 | let p = unsafe { 70 | libc::memmem( 71 | haystack.as_ptr().cast(), 72 | haystack.len(), 73 | needle.as_ptr().cast(), 74 | needle.len(), 75 | ) 76 | }; 77 | if p.is_null() { 78 | None 79 | } else { 80 | let start = (p as isize) - (haystack.as_ptr() as isize); 81 | Some(start as usize) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-bytecount/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.9.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "bytecount" 23 | version = "0.6.4" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" 26 | 27 | [[package]] 28 | name = "main" 29 | version = "0.6.4" 30 | dependencies = [ 31 | "anyhow", 32 | "bytecount", 33 | "shared", 34 | ] 35 | 36 | [[package]] 37 | name = "memchr" 38 | version = "2.7.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 41 | 42 | [[package]] 43 | name = "proc-macro2" 44 | version = "1.0.85" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 47 | dependencies = [ 48 | "unicode-ident", 49 | ] 50 | 51 | [[package]] 52 | name = "quote" 53 | version = "1.0.36" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 56 | dependencies = [ 57 | "proc-macro2", 58 | ] 59 | 60 | [[package]] 61 | name = "serde" 62 | version = "1.0.203" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 65 | dependencies = [ 66 | "serde_derive", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_derive" 71 | version = "1.0.203" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "shared" 82 | version = "0.1.0" 83 | dependencies = [ 84 | "anyhow", 85 | "bstr", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "2.0.66" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.12" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 104 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-bytecount/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "0.6.4" 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | # Note that with bytecount 0.6.7, it seems to fail on wasm32: 12 | # 13 | # $ rebar measure -f '^memchr/sherlock/rare/huge1$' -e '^rust/bytecount/memchr/oneshot/wasm32$' -t 14 | # Error: failed to run main module `./target/wasm32-wasi/release/main.wasm` 15 | # 16 | # Caused by: 17 | # 0: failed to invoke command default 18 | # 1: error while executing at wasm backtrace: 19 | # 0: 0x7683 - !bytecount::count::he2da8fc82662651b 20 | # 1: 0x4887 - !main::main::he4a1bcea0da067f8 21 | # 2: 0x4fe - !std::sys_common::backtrace::__rust_begin_short_backtrace::h3d2add2c8c259792 22 | # 3: 0x7f2f - !__main_void 23 | # 4: 0x4d9 - !_start 24 | # note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information 25 | # 2: memory fault at wasm address 0x3a0000 in linear memory of size 0x3a0000 26 | # 3: wasm trap: out of bounds memory access 27 | # memchr/sherlock/rare/huge1,count-bytes,rust/bytecount/memchr/oneshot/wasm32,0.6.7,failed to run command for 'rust/bytecount/memchr/oneshot/wasm32' 28 | # some benchmarks failed 29 | # 30 | # It's not clear what's happening here, but 0.6.4 works. 31 | bytecount = "=0.6.4" 32 | 33 | [dependencies.shared] 34 | path = "../../shared" 35 | 36 | [[bin]] 37 | name = "main" 38 | path = "main.rs" 39 | 40 | [profile.release] 41 | debug = true 42 | codegen-units = 1 43 | lto = "fat" 44 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-bytecount/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let Some(arg) = std::env::args_os().nth(1) else { 7 | anyhow::bail!("Usage: runner ( | --version)") 8 | }; 9 | let Ok(arg) = arg.into_string() else { 10 | anyhow::bail!("argument given is not valid UTF-8") 11 | }; 12 | if arg == "--version" { 13 | writeln!(std::io::stdout(), env!("CARGO_PKG_VERSION"))?; 14 | return Ok(()); 15 | } 16 | let engine = arg; 17 | let b = Benchmark::from_stdin()?; 18 | let samples = match (&*engine, &*b.model) { 19 | ("memchr-oneshot", "count-bytes") => memchr_oneshot_count(&b)?, 20 | (engine, model) => { 21 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 22 | } 23 | }; 24 | let mut stdout = std::io::stdout().lock(); 25 | for s in samples.iter() { 26 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 27 | } 28 | Ok(()) 29 | } 30 | 31 | fn memchr_oneshot_count(b: &Benchmark) -> anyhow::Result> { 32 | let haystack = &b.haystack; 33 | let needle = b.one_needle_byte()?; 34 | shared::run(b, || Ok(bytecount::count(haystack, needle))) 35 | } 36 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-jetscii/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.9.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "jetscii" 23 | version = "0.5.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" 26 | 27 | [[package]] 28 | name = "main" 29 | version = "0.5.3" 30 | dependencies = [ 31 | "anyhow", 32 | "jetscii", 33 | "shared", 34 | ] 35 | 36 | [[package]] 37 | name = "memchr" 38 | version = "2.7.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 41 | 42 | [[package]] 43 | name = "proc-macro2" 44 | version = "1.0.85" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 47 | dependencies = [ 48 | "unicode-ident", 49 | ] 50 | 51 | [[package]] 52 | name = "quote" 53 | version = "1.0.36" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 56 | dependencies = [ 57 | "proc-macro2", 58 | ] 59 | 60 | [[package]] 61 | name = "serde" 62 | version = "1.0.203" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 65 | dependencies = [ 66 | "serde_derive", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_derive" 71 | version = "1.0.203" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "shared" 82 | version = "0.1.0" 83 | dependencies = [ 84 | "anyhow", 85 | "bstr", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "2.0.66" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.12" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 104 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-jetscii/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "0.5.3" 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | jetscii = "=0.5.3" 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-jetscii/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let Some(arg) = std::env::args_os().nth(1) else { 7 | anyhow::bail!("Usage: runner ( | --version)") 8 | }; 9 | let Ok(arg) = arg.into_string() else { 10 | anyhow::bail!("argument given is not valid UTF-8") 11 | }; 12 | if arg == "--version" { 13 | writeln!(std::io::stdout(), env!("CARGO_PKG_VERSION"))?; 14 | return Ok(()); 15 | } 16 | let engine = arg; 17 | let b = Benchmark::from_stdin()?; 18 | let samples = match (&*engine, &*b.model) { 19 | ("memmem-prebuilt", "count") => memmem_prebuilt_count(&b)?, 20 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 21 | (engine, model) => { 22 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 23 | } 24 | }; 25 | let mut stdout = std::io::stdout().lock(); 26 | for s in samples.iter() { 27 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 28 | } 29 | Ok(()) 30 | } 31 | 32 | fn memmem_prebuilt_count(b: &Benchmark) -> anyhow::Result> { 33 | let haystack = &b.haystack; 34 | let needle = b.one_needle()?; 35 | let finder = jetscii::ByteSubstring::new(needle); 36 | shared::run(b, || { 37 | Ok(shared::count_memmem(haystack, needle, |h, _| finder.find(h))) 38 | }) 39 | } 40 | 41 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 42 | let haystack = &b.haystack; 43 | let needle = b.one_needle()?; 44 | shared::run(b, || { 45 | Ok(shared::count_memmem(haystack, needle, |h, n| { 46 | jetscii::ByteSubstring::new(n).find(h) 47 | })) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-memchr/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.97" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.11.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 16 | dependencies = [ 17 | "memchr 2.7.4 (registry+https://github.com/rust-lang/crates.io-index)", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "main" 23 | version = "2.7.4" 24 | dependencies = [ 25 | "anyhow", 26 | "memchr 2.7.4", 27 | "shared", 28 | ] 29 | 30 | [[package]] 31 | name = "memchr" 32 | version = "2.7.4" 33 | 34 | [[package]] 35 | name = "memchr" 36 | version = "2.7.4" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 39 | 40 | [[package]] 41 | name = "proc-macro2" 42 | version = "1.0.94" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 45 | dependencies = [ 46 | "unicode-ident", 47 | ] 48 | 49 | [[package]] 50 | name = "quote" 51 | version = "1.0.40" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 54 | dependencies = [ 55 | "proc-macro2", 56 | ] 57 | 58 | [[package]] 59 | name = "serde" 60 | version = "1.0.219" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 63 | dependencies = [ 64 | "serde_derive", 65 | ] 66 | 67 | [[package]] 68 | name = "serde_derive" 69 | version = "1.0.219" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 72 | dependencies = [ 73 | "proc-macro2", 74 | "quote", 75 | "syn", 76 | ] 77 | 78 | [[package]] 79 | name = "shared" 80 | version = "0.1.0" 81 | dependencies = [ 82 | "anyhow", 83 | "bstr", 84 | ] 85 | 86 | [[package]] 87 | name = "syn" 88 | version = "2.0.100" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 91 | dependencies = [ 92 | "proc-macro2", 93 | "quote", 94 | "unicode-ident", 95 | ] 96 | 97 | [[package]] 98 | name = "unicode-ident" 99 | version = "1.0.18" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 102 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-memchr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "2.7.4" # should match current 'memchr' version 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | memchr = { version = "*", path = "../../../" } 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-memchrold/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.6.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "main" 23 | version = "2.5.0" 24 | dependencies = [ 25 | "anyhow", 26 | "memchr", 27 | "shared", 28 | ] 29 | 30 | [[package]] 31 | name = "memchr" 32 | version = "2.5.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 35 | 36 | [[package]] 37 | name = "proc-macro2" 38 | version = "1.0.85" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 41 | dependencies = [ 42 | "unicode-ident", 43 | ] 44 | 45 | [[package]] 46 | name = "quote" 47 | version = "1.0.36" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 50 | dependencies = [ 51 | "proc-macro2", 52 | ] 53 | 54 | [[package]] 55 | name = "serde" 56 | version = "1.0.203" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 59 | dependencies = [ 60 | "serde_derive", 61 | ] 62 | 63 | [[package]] 64 | name = "serde_derive" 65 | version = "1.0.203" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 68 | dependencies = [ 69 | "proc-macro2", 70 | "quote", 71 | "syn", 72 | ] 73 | 74 | [[package]] 75 | name = "shared" 76 | version = "0.1.0" 77 | dependencies = [ 78 | "anyhow", 79 | "bstr", 80 | ] 81 | 82 | [[package]] 83 | name = "syn" 84 | version = "2.0.66" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 87 | dependencies = [ 88 | "proc-macro2", 89 | "quote", 90 | "unicode-ident", 91 | ] 92 | 93 | [[package]] 94 | name = "unicode-ident" 95 | version = "1.0.12" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 98 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-memchrold/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "2.5.0" # should match 'memchr' version below 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | memchr = "=2.5.0" 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-memchrold/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let mut args = vec![]; 7 | for osarg in std::env::args_os().skip(1) { 8 | let Ok(arg) = osarg.into_string() else { 9 | anyhow::bail!("all arguments must be valid UTF-8") 10 | }; 11 | args.push(arg); 12 | } 13 | anyhow::ensure!( 14 | !args.is_empty(), 15 | "Usage: runner [--quiet] ( | --version)" 16 | ); 17 | if args.iter().any(|a| a == "--version") { 18 | writeln!(std::io::stdout(), env!("CARGO_PKG_VERSION"))?; 19 | return Ok(()); 20 | } 21 | let quiet = args.iter().any(|a| a == "--quiet"); 22 | let engine = &**args.last().unwrap(); 23 | let b = Benchmark::from_stdin()?; 24 | let samples = match (&*engine, &*b.model) { 25 | ("memchr-oneshot", "count-bytes") => memchr_oneshot_count(&b)?, 26 | ("memchr-prebuilt", "count-bytes") => memchr_prebuilt_count(&b)?, 27 | ("memchr-naive", "count-bytes") => memchr_naive_count(&b)?, 28 | ("memchr2", "count-bytes") => memchr2_count(&b)?, 29 | ("memchr3", "count-bytes") => memchr3_count(&b)?, 30 | ("memrchr", "count-bytes") => memrchr_count(&b)?, 31 | ("memrchr2", "count-bytes") => memrchr2_count(&b)?, 32 | ("memrchr3", "count-bytes") => memrchr3_count(&b)?, 33 | ("memmem-prebuilt", "count") => memmem_prebuilt_count(&b)?, 34 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 35 | ("memmem-prebuilt", "needles-in-needles") => { 36 | memmem_prebuilt_needles(&b)? 37 | } 38 | ("memmem-prebuilt", "needles-in-haystack") => { 39 | memmem_prebuilt_haystack(&b)? 40 | } 41 | (engine, model) => { 42 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 43 | } 44 | }; 45 | if !quiet { 46 | let mut stdout = std::io::stdout().lock(); 47 | for s in samples.iter() { 48 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 49 | } 50 | } 51 | Ok(()) 52 | } 53 | 54 | fn memchr_oneshot_count(b: &Benchmark) -> anyhow::Result> { 55 | let haystack = &b.haystack; 56 | let needle = b.one_needle_byte()?; 57 | shared::run(b, || { 58 | Ok(shared::count_memchr(haystack, needle, |h, n1| { 59 | memchr::memchr(n1, h) 60 | })) 61 | }) 62 | } 63 | 64 | fn memchr_prebuilt_count(b: &Benchmark) -> anyhow::Result> { 65 | let haystack = &b.haystack; 66 | let needle = b.one_needle_byte()?; 67 | shared::run(b, || Ok(memchr::memchr_iter(needle, haystack).count())) 68 | } 69 | 70 | fn memchr_naive_count(b: &Benchmark) -> anyhow::Result> { 71 | let haystack = &b.haystack; 72 | let needle = b.one_needle_byte()?; 73 | shared::run(b, || { 74 | Ok(shared::count_memchr(haystack, needle, |h, n1| { 75 | h.iter().position(|&b| b == n1) 76 | })) 77 | }) 78 | } 79 | 80 | fn memchr2_count(b: &Benchmark) -> anyhow::Result> { 81 | let haystack = &b.haystack; 82 | let (n1, n2) = b.two_needle_bytes()?; 83 | shared::run(b, || Ok(memchr::memchr2_iter(n1, n2, haystack).count())) 84 | } 85 | 86 | fn memchr3_count(b: &Benchmark) -> anyhow::Result> { 87 | let haystack = &b.haystack; 88 | let (n1, n2, n3) = b.three_needle_bytes()?; 89 | shared::run(b, || Ok(memchr::memchr3_iter(n1, n2, n3, haystack).count())) 90 | } 91 | 92 | fn memrchr_count(b: &Benchmark) -> anyhow::Result> { 93 | let haystack = &b.haystack; 94 | let needle = b.one_needle_byte()?; 95 | shared::run(b, || Ok(memchr::memchr_iter(needle, haystack).rev().count())) 96 | } 97 | 98 | fn memrchr2_count(b: &Benchmark) -> anyhow::Result> { 99 | let haystack = &b.haystack; 100 | let (n1, n2) = b.two_needle_bytes()?; 101 | shared::run(b, || Ok(memchr::memchr2_iter(n1, n2, haystack).rev().count())) 102 | } 103 | 104 | fn memrchr3_count(b: &Benchmark) -> anyhow::Result> { 105 | let haystack = &b.haystack; 106 | let (n1, n2, n3) = b.three_needle_bytes()?; 107 | shared::run(b, || { 108 | Ok(memchr::memchr3_iter(n1, n2, n3, haystack).rev().count()) 109 | }) 110 | } 111 | 112 | fn memmem_prebuilt_count(b: &Benchmark) -> anyhow::Result> { 113 | let haystack = &b.haystack; 114 | let needle = b.one_needle()?; 115 | let finder = memchr::memmem::Finder::new(needle); 116 | shared::run(b, || Ok(finder.find_iter(haystack).count())) 117 | } 118 | 119 | fn memmem_prebuilt_needles(b: &Benchmark) -> anyhow::Result> { 120 | let finders = b 121 | .needles 122 | .iter() 123 | .map(|n| memchr::memmem::Finder::new(n)) 124 | .collect::>(); 125 | shared::run(b, || { 126 | let mut count = 0; 127 | for (i, finder) in finders.iter().enumerate() { 128 | for haystack in b.needles[i..].iter() { 129 | if finder.find(haystack).is_some() { 130 | count += 1; 131 | } 132 | } 133 | } 134 | Ok(count) 135 | }) 136 | } 137 | 138 | fn memmem_prebuilt_haystack(b: &Benchmark) -> anyhow::Result> { 139 | let haystack = &b.haystack; 140 | let finders = b 141 | .needles 142 | .iter() 143 | .map(|n| memchr::memmem::Finder::new(n)) 144 | .collect::>(); 145 | shared::run(b, || { 146 | let mut count = 0; 147 | for finder in finders.iter() { 148 | if finder.find(haystack).is_some() { 149 | count += 1; 150 | } 151 | } 152 | Ok(count) 153 | }) 154 | } 155 | 156 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 157 | let haystack = &b.haystack; 158 | let needle = b.one_needle()?; 159 | let memmem = memchr::memmem::find; 160 | shared::run(b, || Ok(shared::count_memmem(haystack, needle, memmem))) 161 | } 162 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-sliceslice/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.9.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "main" 23 | version = "0.4.2" 24 | dependencies = [ 25 | "anyhow", 26 | "shared", 27 | "sliceslice", 28 | ] 29 | 30 | [[package]] 31 | name = "memchr" 32 | version = "2.7.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 35 | 36 | [[package]] 37 | name = "multiversion" 38 | version = "0.6.1" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "025c962a3dd3cc5e0e520aa9c612201d127dcdf28616974961a649dca64f5373" 41 | dependencies = [ 42 | "multiversion-macros", 43 | ] 44 | 45 | [[package]] 46 | name = "multiversion-macros" 47 | version = "0.6.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "a8a3e2bde382ebf960c1f3e79689fa5941625fe9bf694a1cb64af3e85faff3af" 50 | dependencies = [ 51 | "proc-macro2", 52 | "quote", 53 | "syn 1.0.109", 54 | ] 55 | 56 | [[package]] 57 | name = "proc-macro2" 58 | version = "1.0.85" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 61 | dependencies = [ 62 | "unicode-ident", 63 | ] 64 | 65 | [[package]] 66 | name = "quote" 67 | version = "1.0.36" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 70 | dependencies = [ 71 | "proc-macro2", 72 | ] 73 | 74 | [[package]] 75 | name = "seq-macro" 76 | version = "0.3.5" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" 79 | 80 | [[package]] 81 | name = "serde" 82 | version = "1.0.203" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 85 | dependencies = [ 86 | "serde_derive", 87 | ] 88 | 89 | [[package]] 90 | name = "serde_derive" 91 | version = "1.0.203" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 94 | dependencies = [ 95 | "proc-macro2", 96 | "quote", 97 | "syn 2.0.66", 98 | ] 99 | 100 | [[package]] 101 | name = "shared" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "anyhow", 105 | "bstr", 106 | ] 107 | 108 | [[package]] 109 | name = "sliceslice" 110 | version = "0.4.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "04b2417cc92a8539903b09296ec1652d6a670de285243cdb1a7b4f19e23587c6" 113 | dependencies = [ 114 | "memchr", 115 | "multiversion", 116 | "seq-macro", 117 | ] 118 | 119 | [[package]] 120 | name = "syn" 121 | version = "1.0.109" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 124 | dependencies = [ 125 | "proc-macro2", 126 | "quote", 127 | "unicode-ident", 128 | ] 129 | 130 | [[package]] 131 | name = "syn" 132 | version = "2.0.66" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 135 | dependencies = [ 136 | "proc-macro2", 137 | "quote", 138 | "unicode-ident", 139 | ] 140 | 141 | [[package]] 142 | name = "unicode-ident" 143 | version = "1.0.12" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 146 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-sliceslice/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "0.4.2" # should match 'sliceslice' version below 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | sliceslice = "0.4.2" 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-sliceslice/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let Some(arg) = std::env::args_os().nth(1) else { 7 | anyhow::bail!("Usage: runner ( | --version)") 8 | }; 9 | let Ok(arg) = arg.into_string() else { 10 | anyhow::bail!("argument given is not valid UTF-8") 11 | }; 12 | if arg == "--version" { 13 | writeln!(std::io::stdout(), env!("CARGO_PKG_VERSION"))?; 14 | return Ok(()); 15 | } 16 | let engine = arg; 17 | let b = Benchmark::from_stdin()?; 18 | let samples = match (&*engine, &*b.model) { 19 | ("memmem-prebuilt", "count") => memmem_prebuilt_count(&b)?, 20 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 21 | ("memmem-prebuilt", "needles-in-needles") => { 22 | memmem_prebuilt_needles(&b)? 23 | } 24 | ("memmem-prebuilt", "needles-in-haystack") => { 25 | memmem_prebuilt_haystack(&b)? 26 | } 27 | (engine, model) => { 28 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 29 | } 30 | }; 31 | let mut stdout = std::io::stdout().lock(); 32 | for s in samples.iter() { 33 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 34 | } 35 | Ok(()) 36 | } 37 | 38 | // The sliceslice crate perplexingly only offers "does the needle occur at 39 | // least once in the haystack" APIs, and does not actually report the offset 40 | // at which the needle matches. It's therefore questionable to even bother 41 | // benchmarking it, but alas, it is quite fast and has been heavily optimized. 42 | // But this does mean we can only use sliceslice in benchmarks with a count 43 | // equal to 0 or 1. (We could define a distinct "does any match occur" model, 44 | // but it does not seem worth it.) 45 | 46 | fn memmem_prebuilt_count(b: &Benchmark) -> anyhow::Result> { 47 | #[cfg(target_arch = "x86_64")] 48 | { 49 | use sliceslice::x86::DynamicAvx2Searcher; 50 | anyhow::ensure!( 51 | is_x86_feature_detected!("avx2"), 52 | "AVX2 not available" 53 | ); 54 | let haystack = &b.haystack; 55 | let needle = b.one_needle()?; 56 | // SAFETY: We just checked that avx2 is available. 57 | unsafe { 58 | let finder = DynamicAvx2Searcher::new(needle); 59 | shared::run(b, || { 60 | Ok(if finder.search_in(haystack) { 1 } else { 0 }) 61 | }) 62 | } 63 | } 64 | #[cfg(target_arch = "aarch64")] 65 | { 66 | use sliceslice::aarch64::NeonSearcher; 67 | anyhow::ensure!( 68 | std::arch::is_aarch64_feature_detected!("neon"), 69 | "NEON not available" 70 | ); 71 | let haystack = &b.haystack; 72 | let needle = b.one_needle()?; 73 | // SAFETY: We just checked that avx2 is available. 74 | unsafe { 75 | let finder = NeonSearcher::new(needle); 76 | shared::run(b, || { 77 | Ok(if finder.search_in(haystack) { 1 } else { 0 }) 78 | }) 79 | } 80 | } 81 | } 82 | 83 | fn memmem_prebuilt_needles(b: &Benchmark) -> anyhow::Result> { 84 | #[cfg(target_arch = "x86_64")] 85 | { 86 | use sliceslice::x86::DynamicAvx2Searcher; 87 | 88 | anyhow::ensure!( 89 | is_x86_feature_detected!("avx2"), 90 | "AVX2 not available" 91 | ); 92 | let finders = b 93 | .needles 94 | .iter() 95 | // SAFETY: We just checked that avx2 is available. 96 | .map(|n| unsafe { DynamicAvx2Searcher::new(n) }) 97 | .collect::>(); 98 | shared::run(b, || { 99 | let mut count = 0; 100 | for (i, finder) in finders.iter().enumerate() { 101 | for haystack in b.needles[i..].iter() { 102 | // SAFETY: We just checked that avx2 is available. 103 | unsafe { 104 | if finder.search_in(haystack) { 105 | count += 1; 106 | } 107 | } 108 | } 109 | } 110 | Ok(count) 111 | }) 112 | } 113 | #[cfg(target_arch = "aarch64")] 114 | { 115 | use sliceslice::aarch64::NeonSearcher; 116 | 117 | anyhow::ensure!( 118 | std::arch::is_aarch64_feature_detected!("neon"), 119 | "NEON not available" 120 | ); 121 | let finders = b 122 | .needles 123 | .iter() 124 | // SAFETY: We just checked that avx2 is available. 125 | .map(|n| unsafe { NeonSearcher::new(n) }) 126 | .collect::>(); 127 | shared::run(b, || { 128 | let mut count = 0; 129 | for (i, finder) in finders.iter().enumerate() { 130 | for haystack in b.needles[i..].iter() { 131 | // SAFETY: We just checked that avx2 is available. 132 | unsafe { 133 | if finder.search_in(haystack) { 134 | count += 1; 135 | } 136 | } 137 | } 138 | } 139 | Ok(count) 140 | }) 141 | } 142 | } 143 | 144 | fn memmem_prebuilt_haystack(b: &Benchmark) -> anyhow::Result> { 145 | #[cfg(target_arch = "x86_64")] 146 | { 147 | use sliceslice::x86::DynamicAvx2Searcher; 148 | 149 | anyhow::ensure!( 150 | is_x86_feature_detected!("avx2"), 151 | "AVX2 not available" 152 | ); 153 | let haystack = &b.haystack; 154 | let finders = b 155 | .needles 156 | .iter() 157 | // SAFETY: We just checked that avx2 is available. 158 | .map(|n| unsafe { DynamicAvx2Searcher::new(n) }) 159 | .collect::>(); 160 | shared::run(b, || { 161 | let mut count = 0; 162 | for finder in finders.iter() { 163 | // SAFETY: We just checked that avx2 is available. 164 | unsafe { 165 | if finder.search_in(haystack) { 166 | count += 1; 167 | } 168 | } 169 | } 170 | Ok(count) 171 | }) 172 | } 173 | #[cfg(target_arch = "aarch64")] 174 | { 175 | use sliceslice::aarch64::NeonSearcher; 176 | 177 | anyhow::ensure!( 178 | std::arch::is_aarch64_feature_detected!("neon"), 179 | "NEON not available" 180 | ); 181 | let haystack = &b.haystack; 182 | let finders = b 183 | .needles 184 | .iter() 185 | // SAFETY: We just checked that avx2 is available. 186 | .map(|n| unsafe { NeonSearcher::new(n) }) 187 | .collect::>(); 188 | shared::run(b, || { 189 | let mut count = 0; 190 | for finder in finders.iter() { 191 | // SAFETY: We just checked that avx2 is available. 192 | unsafe { 193 | if finder.search_in(haystack) { 194 | count += 1; 195 | } 196 | } 197 | } 198 | Ok(count) 199 | }) 200 | } 201 | } 202 | 203 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 204 | #[cfg(target_arch = "x86_64")] 205 | { 206 | use sliceslice::x86::DynamicAvx2Searcher; 207 | 208 | anyhow::ensure!( 209 | is_x86_feature_detected!("avx2"), 210 | "AVX2 not available" 211 | ); 212 | let haystack = &b.haystack; 213 | let needle = b.one_needle()?; 214 | shared::run(b, || { 215 | // SAFETY: We just checked that avx2 is available. 216 | Ok(unsafe { 217 | if DynamicAvx2Searcher::new(needle).search_in(haystack) { 218 | 1 219 | } else { 220 | 0 221 | } 222 | }) 223 | }) 224 | } 225 | #[cfg(target_arch = "aarch64")] 226 | { 227 | use sliceslice::aarch64::NeonSearcher; 228 | 229 | anyhow::ensure!( 230 | std::arch::is_aarch64_feature_detected!("neon"), 231 | "NEON not available" 232 | ); 233 | let haystack = &b.haystack; 234 | let needle = b.one_needle()?; 235 | shared::run(b, || { 236 | // SAFETY: We just checked that avx2 is available. 237 | Ok(unsafe { 238 | if NeonSearcher::new(needle).search_in(haystack) { 239 | 1 240 | } else { 241 | 0 242 | } 243 | }) 244 | }) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-std/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.9.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "main" 23 | version = "0.0.0" 24 | dependencies = [ 25 | "anyhow", 26 | "regex-lite", 27 | "shared", 28 | ] 29 | 30 | [[package]] 31 | name = "memchr" 32 | version = "2.7.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 35 | 36 | [[package]] 37 | name = "proc-macro2" 38 | version = "1.0.85" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 41 | dependencies = [ 42 | "unicode-ident", 43 | ] 44 | 45 | [[package]] 46 | name = "quote" 47 | version = "1.0.36" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 50 | dependencies = [ 51 | "proc-macro2", 52 | ] 53 | 54 | [[package]] 55 | name = "regex-lite" 56 | version = "0.1.6" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 59 | 60 | [[package]] 61 | name = "serde" 62 | version = "1.0.203" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 65 | dependencies = [ 66 | "serde_derive", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_derive" 71 | version = "1.0.203" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "shared" 82 | version = "0.1.0" 83 | dependencies = [ 84 | "anyhow", 85 | "bstr", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "2.0.66" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-ident" 101 | version = "1.0.12" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 104 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "0.0.0" # we get version from build script 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | 12 | [build-dependencies] 13 | regex-lite = "0.1" 14 | 15 | [dependencies.shared] 16 | path = "../../shared" 17 | 18 | [[bin]] 19 | name = "main" 20 | path = "main.rs" 21 | 22 | [profile.release] 23 | debug = true 24 | codegen-units = 1 25 | lto = "fat" 26 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-std/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use regex_lite::Regex; 4 | 5 | fn main() { 6 | let version = rustc_version().unwrap_or("unknown".to_string()); 7 | println!("cargo:rustc-env=RUSTC_VERSION={}", version); 8 | } 9 | 10 | fn rustc_version() -> Option { 11 | let re = Regex::new( 12 | r"^rustc (?\d+\.\d+\.\d+(?:-\S+)) \((?[0-9a-f]+)", 13 | ) 14 | .unwrap(); 15 | 16 | let output = Command::new("rustc").arg("--version").output().ok()?; 17 | let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); 18 | let first_line = stdout.lines().next()?; 19 | let caps = re.captures(first_line)?; 20 | Some(format!("{} {}", &caps["version"], &caps["rev"])) 21 | } 22 | -------------------------------------------------------------------------------- /benchmarks/engines/rust-std/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | fn main() -> anyhow::Result<()> { 6 | let Some(arg) = std::env::args_os().nth(1) else { 7 | anyhow::bail!("Usage: runner ( | --version)") 8 | }; 9 | let Ok(arg) = arg.into_string() else { 10 | anyhow::bail!("argument given is not valid UTF-8") 11 | }; 12 | if arg == "--version" { 13 | writeln!(std::io::stdout(), env!("RUSTC_VERSION"))?; 14 | return Ok(()); 15 | } 16 | let engine = arg; 17 | let b = Benchmark::from_stdin()?; 18 | let samples = match (&*engine, &*b.model) { 19 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 20 | ("memmem-prebuilt", "count") => memmem_prebuilt_count(&b)?, 21 | (engine, model) => { 22 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 23 | } 24 | }; 25 | let mut stdout = std::io::stdout().lock(); 26 | for s in samples.iter() { 27 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 28 | } 29 | Ok(()) 30 | } 31 | 32 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 33 | let haystack = std::str::from_utf8(&b.haystack)?; 34 | let needle = std::str::from_utf8(b.one_needle()?)?; 35 | shared::run(b, || { 36 | Ok(shared::count_memmem_str(haystack, needle, |h, n| { 37 | h.match_indices(n).map(|(i, _)| i).next() 38 | })) 39 | }) 40 | } 41 | 42 | /// A prebuilt version of the std searcher. This isn't quite as "prebuilt" as 43 | /// is possible, since each measurement doesn't just include the creation of 44 | /// the iterator but also the searcher. Where as the memchr crate can do this 45 | /// where the searcher is created once and the measurement only includes the 46 | /// creation of the iterator. 47 | /// 48 | /// So probably we "should" have created some other kind of engine called 49 | /// "partial prebuilt" but it makes things even more confusing. At some point, 50 | /// this is just the way the cookie crumbles. 51 | /// 52 | /// Remember, it's not that oneshot and prebuilt aren't comparable. 53 | /// It's absolutely fair to say, for example, "Rust's standard library 54 | /// substring search is slow because it doesn't let one amortize the cost of 55 | /// construction." But it's *also* interesting to make these assumptions a 56 | /// little more explicit in our measurement model. But we do this within 57 | /// reason. 58 | fn memmem_prebuilt_count(b: &Benchmark) -> anyhow::Result> { 59 | let haystack = std::str::from_utf8(&b.haystack)?; 60 | let needle = std::str::from_utf8(b.one_needle()?)?; 61 | shared::run(b, || Ok(haystack.matches(needle).count())) 62 | } 63 | -------------------------------------------------------------------------------- /benchmarks/engines/stringzilla/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.97" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.11.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "cc" 23 | version = "1.2.17" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" 26 | dependencies = [ 27 | "shlex", 28 | ] 29 | 30 | [[package]] 31 | name = "main" 32 | version = "3.12.3" 33 | dependencies = [ 34 | "anyhow", 35 | "shared", 36 | "stringzilla", 37 | ] 38 | 39 | [[package]] 40 | name = "memchr" 41 | version = "2.7.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 44 | 45 | [[package]] 46 | name = "proc-macro2" 47 | version = "1.0.94" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 50 | dependencies = [ 51 | "unicode-ident", 52 | ] 53 | 54 | [[package]] 55 | name = "quote" 56 | version = "1.0.40" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 59 | dependencies = [ 60 | "proc-macro2", 61 | ] 62 | 63 | [[package]] 64 | name = "serde" 65 | version = "1.0.219" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 68 | dependencies = [ 69 | "serde_derive", 70 | ] 71 | 72 | [[package]] 73 | name = "serde_derive" 74 | version = "1.0.219" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 77 | dependencies = [ 78 | "proc-macro2", 79 | "quote", 80 | "syn", 81 | ] 82 | 83 | [[package]] 84 | name = "shared" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "anyhow", 88 | "bstr", 89 | ] 90 | 91 | [[package]] 92 | name = "shlex" 93 | version = "1.3.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 96 | 97 | [[package]] 98 | name = "stringzilla" 99 | version = "3.12.3" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "f063296520979db7590db738d7fc067ae94b4b881c2260d7acd36d8439173c6b" 102 | dependencies = [ 103 | "cc", 104 | ] 105 | 106 | [[package]] 107 | name = "syn" 108 | version = "2.0.100" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 111 | dependencies = [ 112 | "proc-macro2", 113 | "quote", 114 | "unicode-ident", 115 | ] 116 | 117 | [[package]] 118 | name = "unicode-ident" 119 | version = "1.0.18" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 122 | -------------------------------------------------------------------------------- /benchmarks/engines/stringzilla/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "main" 4 | version = "3.12.3" # should match 'stringzilla' version below 5 | edition = "2021" 6 | 7 | [workspace] 8 | 9 | [dependencies] 10 | anyhow = "1.0.72" 11 | stringzilla = "=3.12.3" 12 | 13 | [dependencies.shared] 14 | path = "../../shared" 15 | 16 | [[bin]] 17 | name = "main" 18 | path = "main.rs" 19 | 20 | [profile.release] 21 | debug = true 22 | codegen-units = 1 23 | lto = "fat" 24 | -------------------------------------------------------------------------------- /benchmarks/engines/stringzilla/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use shared::{Benchmark, Sample}; 4 | 5 | use stringzilla::StringZilla; 6 | 7 | fn main() -> anyhow::Result<()> { 8 | let Some(arg) = std::env::args_os().nth(1) else { 9 | anyhow::bail!("Usage: runner ( | --version)") 10 | }; 11 | let Ok(arg) = arg.into_string() else { 12 | anyhow::bail!("argument given is not valid UTF-8") 13 | }; 14 | if arg == "--version" { 15 | writeln!(std::io::stdout(), env!("CARGO_PKG_VERSION"))?; 16 | return Ok(()); 17 | } 18 | let engine = arg; 19 | let b = Benchmark::from_stdin()?; 20 | let samples = match (&*engine, &*b.model) { 21 | ("memmem-oneshot", "count") => memmem_oneshot_count(&b)?, 22 | (engine, model) => { 23 | anyhow::bail!("unrecognized engine '{engine}' and model '{model}'") 24 | } 25 | }; 26 | let mut stdout = std::io::stdout().lock(); 27 | for s in samples.iter() { 28 | writeln!(stdout, "{},{}", s.duration.as_nanos(), s.count)?; 29 | } 30 | Ok(()) 31 | } 32 | 33 | fn memmem_oneshot_count(b: &Benchmark) -> anyhow::Result> { 34 | let haystack = &b.haystack; 35 | let needle = b.one_needle()?; 36 | shared::run(b, || { 37 | Ok(shared::count_memmem(haystack, needle, |h, n| h.sz_find(n))) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /benchmarks/haystacks/README.md: -------------------------------------------------------------------------------- 1 | This directory contains benchmark corpora. Each sub-directory contains a README 2 | documenting the corpus a bit more. 3 | -------------------------------------------------------------------------------- /benchmarks/haystacks/code/README.md: -------------------------------------------------------------------------------- 1 | This data contains corpora generated from source code. These sorts of corpora 2 | are important because code is something that is frequently searched. 3 | 4 | This corpus was generated by running 5 | 6 | ``` 7 | $ find ./library/alloc -name '*.rs' -print0 \ 8 | | xargs -0 cat > .../memchr/bench/data/code/rust-library.rs 9 | ``` 10 | 11 | in a checkout of the https://github.com/rust-lang/rust repository at commit 12 | 78c963945aa35a76703bf62e024af2d85b2796e2. 13 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/README.md: -------------------------------------------------------------------------------- 1 | These were downloaded and derived from the Open Subtitles data set: 2 | https://opus.nlpl.eu/OpenSubtitles-v2018.php 3 | 4 | The specific way in which they were modified has been lost to time, but it's 5 | likely they were just a simple truncation based on target file sizes for 6 | various benchmarks. 7 | 8 | The main reason why we have them is that it gives us a way to test similar 9 | inputs on non-ASCII text. Normally this wouldn't matter for a substring search 10 | implementation, but because of the heuristics used to pick a priori determined 11 | "rare bytes" to base a prefilter on, it's possible for this heuristic to do 12 | more poorly on non-ASCII text than one might expect. 13 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/en-small.txt: -------------------------------------------------------------------------------- 1 | Now you can tell 'em. 2 | What for are you mixing in? 3 | Maybe I don't like to see kids get hurt. 4 | Break any bones, son? 5 | He's got a knife behind his collar! 6 | - There's a stirrup. 7 | You want a lift? 8 | - No. 9 | - Why not? 10 | - I'm beholden to you, mister. 11 | Couldn't we just leave it that way? 12 | - Morning. 13 | - Morning. 14 | - Put him up? 15 | - For how long? 16 | - I wouldn't know. 17 | - It'll be two bits for oats. 18 | - Ain't I seen you before? 19 | - Depends on where you've been. 20 | - I follow the railroad, mostly. 21 | - Could be you've seen me. 22 | - It'll be four bits if he stays the night. 23 | - Fair enough. 24 | Morning. 25 | Did a man ride in today - tall, sort of heavyset? 26 | - You mean him, Mr Renner? 27 | - Not him. 28 | This one had a scar. 29 | Along his cheek? 30 | No, sir. 31 | I don't see no man with a scar. 32 | I guess maybe I can have some apple pie and coffee. 33 | I guess you could have eggs with bacon if you wanted eggs with bacon. 34 | - Hello, Charlie. 35 | - Hello, Grant. 36 | It's good to see you, Charlie. 37 | It's awful good to see you. 38 | It's good to see you too. 39 | Doc you're beginning to sound like Sherlock Holmes. 40 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/en-teeny.txt: -------------------------------------------------------------------------------- 1 | Sound like Sherlock Holmes. 2 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/en-tiny.txt: -------------------------------------------------------------------------------- 1 | I saw you before but I didn't think you were this young 2 | Doc you're beginning to sound like Sherlock Holmes. 3 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/ru-small.txt: -------------------------------------------------------------------------------- 1 | -Две недели не даешь мне прохода. 2 | Вот и действуй, чем ты рискуешь? 3 | Я думал, что сделаю тебя счастливой. 4 | Тоже мне счастье. 5 | Муж не дает ни гроша, и у любовника ума не хватает подумать о деньгах. 6 | - Хорошенькое счастье. 7 | - Извини, я думал, ты любишь меня. 8 | Ну люблю, люблю тебя, но и не хочу, чтобы все началось как в прошлый раз. 9 | Ты не права. 10 | У меня для тебя сюрприз. 11 | Шлихтовальная машина, ты о ней давно мечтала. 12 | -Для костей? 13 | - Нет, настоящая. 14 | Хочешь, приходи за ней вечером. 15 | Я тебе не девочка. 16 | Была бы ты девочкой, я бы тебе ее не купил. 17 | Я люблю тебя 18 | Митч МакКафи, летающий Шерлок Холмс. 19 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/ru-teeny.txt: -------------------------------------------------------------------------------- 1 | летающий Шерлок Холмс. 2 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/ru-tiny.txt: -------------------------------------------------------------------------------- 1 | Это - одно из самых поразительных недавних открытий науки. 2 | Митч МакКафи, летающий Шерлок Холмс. 3 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/zh-small.txt: -------------------------------------------------------------------------------- 1 | 魯哇克香貓咖啡 世界上最稀有的飲品 Kopi luwak. 2 | the rarest beverage in the world. 3 | 嘗一小口 Take a whiff. 4 | 來 Go ahead. 5 | 寇爾先生 董事會已準備好聽你的提案 Uh, mr. 6 | cole, the board is ready to hear your proposal. 7 | 等一下下 Hold on just a second. 8 | 來 繼續 Go ahead. 9 | go on. 10 | 怎樣 Well? 11 | 真不錯 Really good. 12 | 真不錯 Really good. 13 | 寇爾先生? 14 | Mr. 15 | cole. 16 | sir? 17 | 吉姆 你知道庸俗是什麼嗎 Do you know what a philistine is, jim? 18 | 先生 我叫理查德 Sir, it's richard. 19 | 沒錯 費爾 出動你的如簧巧舌吧 That's right, phil. 20 | give them the spiel. 21 | 謝謝 主席先生 主管們 Thank you, mr. 22 | chairman, fellow supervisors. 23 | 我們寇爾集團財務的管理不善 We at the cole group feel the decline of the winwood hospital... 24 | 直接造成了溫伍德醫院的衰敗 ...is a direct result of significant fiscal mismanagement. 25 | 請原諒 我們醫院... 26 | I beg your pardon, this hospital... 27 | 日常開支近2倍 overhead costs are nearly double. 28 | 帽子不错 汤姆 夏洛克·福尔摩斯 29 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/zh-teeny.txt: -------------------------------------------------------------------------------- 1 | 汤姆 夏洛克·福尔摩斯 2 | -------------------------------------------------------------------------------- /benchmarks/haystacks/opensubtitles/zh-tiny.txt: -------------------------------------------------------------------------------- 1 | 谁是早餐界的冠军? 2 | 你突然来信说最近要搬到这里 3 | 帽子不错 汤姆 夏洛克·福尔摩斯 4 | -------------------------------------------------------------------------------- /benchmarks/haystacks/pathological/README.md: -------------------------------------------------------------------------------- 1 | These data sets are specifically crafted to try and defeat heuristic 2 | optimizations in various substring search implementations. The point of these 3 | is to make the costs of those heuristics clearer. In particular, the main idea 4 | behind heuristics is to sell out some rare or edge cases in favor of making 5 | some common cases *a lot* faster (potentially by orders of magnitude). The key 6 | to this is to make sure that those edge cases are impacted at tolerable levels. 7 | 8 | Below is a description of each. 9 | 10 | * `repeated-rare-*`: This is meant to be used with the needle `abczdef`. This 11 | input defeats a heuristic in the old bstr and regex substring implementations 12 | that looked for a rare byte (in this case, `z`) to run memchr on before 13 | looking for an actual match. This particular input causes that heuristic to 14 | stop on every byte in the input. In regex's case in particular, this causes 15 | `O(mn)` time complexity. (In the case of `bstr`, it does a little better by 16 | stopping this heuristic after a number of tries once it becomes clear that it 17 | is ineffective.) 18 | * `defeat-simple-vector`: The corpus consists of `qaz` repeated over and over 19 | again. The intended needle is `qbz`. This is meant to be difficult for the 20 | "generic SIMD" algorithm[1] to handle. Namely, it will repeatedly find a 21 | candidate match via the `q` and `z` bytes in the needle, but the overall 22 | match will fail at the `memcmp` phase. Nevertheless, optimized versions of 23 | [1] still do reasonably well on this benchmark because the `memcmp` can be 24 | specialized to a single `u32` unaligned load and compare. 25 | * `defeat-simple-vector-freq`: This is similarish to `defeat-simple-vector`, 26 | except it also attempts to defeat heuristic frequency analysis. The corpus 27 | consists of `qjaz` repeated over and over again, with the intended needle 28 | being `qja{49}z`. Heuristic frequency analysis might try either the `q` or 29 | the `j`, in addition to `z`. Given the nature of the corpus, this will result 30 | in a lot of false positive candidates, thus leading to an ineffective 31 | prefilter. 32 | * `defeat-simple-vector-repeated`: This combines the "repeated-rare" and 33 | "defeat-simple-vector" inputs. The corpus consists of `z` entirely, with only 34 | the second to last byte being changed to `a`. The intended needle is 35 | `z{135}az`. The key here is that in [1], a candidate match will be found at 36 | every position in the haystack. And since the needle is very large, this will 37 | result in a full `memcmp` call out. [1] effectively drowns in `memcmp` being 38 | called at every position in the haystack. The algorithm in this crate does 39 | a bit better by noticing that the prefilter is ineffective and falling back 40 | to standard Two-Way. 41 | * `md5-huge`: This file contains one md5 hash per line for each word in the 42 | `../sliceslice/words.txt` corpus. The intent of this benchmark is to defeat 43 | frequency heuristics by using a corpus comprised of random data. That is, 44 | no one bytes should be significantly more frequent than any other. 45 | * `random-huge`: Similar to `md5-huge`, but with longer lines and more 46 | princpally random data. Generated via 47 | `dd if=/dev/urandom bs=32 count=10000 | xxd -ps -c32`. 48 | This was derived from a real world benchmark reported to ripgrep[2]. 49 | In particular, it originally motivated the addition of Boyer-Moore to 50 | the regex crate, but now this case is handled just fine by the memmem 51 | implementation in this crate. 52 | 53 | [1]: http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd 54 | [2]: https://github.com/BurntSushi/ripgrep/issues/617 55 | -------------------------------------------------------------------------------- /benchmarks/haystacks/pathological/repeated-rare-small.txt: -------------------------------------------------------------------------------- 1 | zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 2 | -------------------------------------------------------------------------------- /benchmarks/haystacks/rg-13.0.0.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurntSushi/memchr/ceef3c921b5685847ea39647b6361033dfe1aa36/benchmarks/haystacks/rg-13.0.0.txt -------------------------------------------------------------------------------- /benchmarks/haystacks/sherlock/README.md: -------------------------------------------------------------------------------- 1 | These were the original inputs used for the memchr benchmarks. In theory, they 2 | could be replaced with the subtitle text in order to trim down the number of 3 | inputs we use in the benchmark suite. 4 | 5 | The nice thing about the subtitle corpus is that it gives us a translation into 6 | Russian and Chinese, which lets us measure our code on non-ASCII text. 7 | -------------------------------------------------------------------------------- /benchmarks/haystacks/sherlock/small.txt: -------------------------------------------------------------------------------- 1 | Mr. Sherlock Holmes, who was usually very late in the mornings, save 2 | upon those not infrequent occasions when he was up all night, was seated 3 | at the breakfast table. I stood upon the hearth-rug and picked up the 4 | stick which our visitor had left behind him the night before. It was a 5 | fine, thick piece of wood, bulbous-headed, of the sort which is known as 6 | a "Penang lawyer." Just under the head was a broad silver band nearly 7 | an inch across. "To James Mortimer, M.R.C.S., from his friends of the 8 | C.C.H.," was engraved upon it, with the date "1884." It was just such a 9 | stick as the old-fashioned family practitioner used to carry--dignified, 10 | solid, and reassuring. 11 | -------------------------------------------------------------------------------- /benchmarks/haystacks/sherlock/tiny.txt: -------------------------------------------------------------------------------- 1 | Mr. Sherlock Holmes, who was usually very late in the mornings, save 2 | -------------------------------------------------------------------------------- /benchmarks/haystacks/sliceslice/README.md: -------------------------------------------------------------------------------- 1 | These benchmark inputs were taken from the sliceslice [project benchmarks][1]. 2 | 3 | These inputs drive two benchmarks, one on short haystacks and the other on long 4 | haystacks, with a slightly unusual but interesting configuration. Neither of 5 | these benchmarks include the time it takes to build a searcher. They only 6 | measure actual search time. 7 | 8 | The short haystack benchmark starts by loading all of the words in `words.txt` 9 | into memory and sorting them in ascending order by their length. Then, a 10 | substring searcher is created for each of these words in the same order. The 11 | actual benchmark consists of executing each searcher once on every needle that 12 | appears after it in the list. In essence, this benchmark tests how quickly the 13 | implementation can deal with tiny haystacks. The results of this benchmark tend 14 | to come down to how much overhead the implementation has. In other words, this 15 | benchmark tests latency. 16 | 17 | The long haystack benchmark has a setup similar to the short haystack 18 | benchmark, except it also loads the contents of `i386.txt` into memory. The 19 | actual benchmark itself executes each of the searchers built (from `words.txt`) 20 | on the `i386.txt` haystack. This benchmark, executing on a much longer 21 | haystack, tests throughput as opposed to latency across a wide variety of 22 | needles. 23 | 24 | [1]: https://github.com/cloudflare/sliceslice-rs 25 | -------------------------------------------------------------------------------- /benchmarks/haystacks/sliceslice/haystack.txt: -------------------------------------------------------------------------------- 1 | I4BCeTWkpm1G1StV1WP8HKMX1IaRTslq88MOEg4nx7kWLaWYDRDYZrdTtsg26mz9S8DhYcd1ty35aPLyRZSeimOmPAroaqi861z44o35qP9GdrofuuHwdhSEeOg7E0L1KrTYgWZZrzyk7o0DVxw7vvmo6rVJ5T4lLMkesL1U6ObckS9oorPI5ZDm4Nk1ryh3AC8juZwJLcyWKveenfaVGaB42iYrOvrZGIMQG0OmA0k73ydw2mJawUzWE8O61MQpDm19JFaTgSJMkPs3yjY149knHg03uOBvb6d0hCmUbiHkRgFwH17cL7UJUlkggx55Gk1n07v7fGdkjvxmOmCHCvLrY9pCvewCAy1Lg1NTdE4N7AJfjPj1hiw9DqiDaJwTVc4JA229EubFHnMBlHkrSSnR1wh7RYOngmQUxjGOdctDkjVard24dO2mGkGCXAHrOjRGq9J54ml3HtCx3Ui3YN68Q8eywJnkS7CdiA2d2SmRako5VlGfRCDdoiNpZwq9FodDRcw4XsWLuH9XEAfXSGzx5X8PVUwnmTImOcc1az1x8dUWVdK5St7M5dVPsWZ5hrll7F0MjKdf2wBFYEp0gqZTswXUn2RwkDLCj4oikpBCE1fLSaiKL89rbo1RZ2phIVrXb7eQ8xJTBU1iSwWF97OJlIBcNjmE4TzrqgIl2m8ybVAURk3iC51cwO8wD5V1dcVzIuqAWJUwKh4PPyHhGdv4T439nRh3ukwAlHBtxj56izkIKWNm01Mfrc1boFApld1E597BDSrWEcXwn44KogPpJ058LGNB8IaAufcWEXfqbPwgNQUSqqarItOAFir3tr8nBGQar22nZifJzUiU35TzXUc46LYAxSEeDmbqMh3Zcmuw5YrFF5Ma0oaAuiiJb2IdpkPr5E6HuN5vDvvnjxDw0GbOX72xuSRFDxbOmt40hDLQLy3UrlaP92jS7DfBMUGefSo7mTfCpz5hvllPhbjYqJ31P9d8zJYPpQMW -------------------------------------------------------------------------------- /benchmarks/haystacks/sliceslice/i386-notutf8.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurntSushi/memchr/ceef3c921b5685847ea39647b6361033dfe1aa36/benchmarks/haystacks/sliceslice/i386-notutf8.txt -------------------------------------------------------------------------------- /benchmarks/regexes/sliceslice/README.md: -------------------------------------------------------------------------------- 1 | These needles were taken from the `sliceslice` [project benchmarks][1]. 2 | 3 | These inputs drive two benchmarks, one on short haystacks and the other on long 4 | haystacks. For the short haystack case, we actually search the needles themselves. 5 | That is, for each needle we count the number of needles in which that needle is 6 | contained. 7 | 8 | For the long haystack case, we just take the haystacks used in the `sliceslice` 9 | benchmarks. 10 | 11 | [1]: https://github.com/cloudflare/sliceslice-rs 12 | -------------------------------------------------------------------------------- /benchmarks/regexes/zero-zero-dd-dd.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BurntSushi/memchr/ceef3c921b5685847ea39647b6361033dfe1aa36/benchmarks/regexes/zero-zero-dd-dd.txt -------------------------------------------------------------------------------- /benchmarks/shared/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.72" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" 10 | 11 | [[package]] 12 | name = "bstr" 13 | version = "1.6.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" 16 | dependencies = [ 17 | "memchr", 18 | "serde", 19 | ] 20 | 21 | [[package]] 22 | name = "memchr" 23 | version = "2.5.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 26 | 27 | [[package]] 28 | name = "serde" 29 | version = "1.0.177" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a" 32 | 33 | [[package]] 34 | name = "shared" 35 | version = "0.1.0" 36 | dependencies = [ 37 | "anyhow", 38 | "bstr", 39 | ] 40 | -------------------------------------------------------------------------------- /benchmarks/shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [dependencies] 9 | anyhow = "1.0.69" 10 | bstr = { version = "1.6.0", default-features = false, features = ["std"] } 11 | 12 | [lib] 13 | name = "shared" 14 | path = "lib.rs" 15 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /corpus 3 | /artifacts 4 | /coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ['named-profiles'] 2 | 3 | [package] 4 | publish = false 5 | name = "memchr-fuzz" 6 | version = "0.0.0" 7 | authors = ["Andrew Gallant "] 8 | edition = "2018" 9 | 10 | [package.metadata] 11 | cargo-fuzz = true 12 | 13 | [dependencies] 14 | libfuzzer-sys = "0.4" 15 | 16 | [dependencies.memchr] 17 | path = ".." 18 | 19 | # Prevent this from interfering with workspaces 20 | [workspace] 21 | 22 | [[bin]] 23 | name = "memchr" 24 | path = "fuzz_targets/memchr.rs" 25 | test = false 26 | doc = false 27 | 28 | [[bin]] 29 | name = "memchr2" 30 | path = "fuzz_targets/memchr2.rs" 31 | test = false 32 | doc = false 33 | 34 | [[bin]] 35 | name = "memchr3" 36 | path = "fuzz_targets/memchr3.rs" 37 | test = false 38 | doc = false 39 | 40 | [[bin]] 41 | name = "memrchr" 42 | path = "fuzz_targets/memrchr.rs" 43 | test = false 44 | doc = false 45 | 46 | [[bin]] 47 | name = "memrchr2" 48 | path = "fuzz_targets/memrchr2.rs" 49 | test = false 50 | doc = false 51 | 52 | [[bin]] 53 | name = "memrchr3" 54 | path = "fuzz_targets/memrchr3.rs" 55 | test = false 56 | doc = false 57 | 58 | [[bin]] 59 | name = "memmem" 60 | path = "fuzz_targets/memmem.rs" 61 | test = false 62 | doc = false 63 | 64 | [[bin]] 65 | name = "memrmem" 66 | path = "fuzz_targets/memrmem.rs" 67 | test = false 68 | doc = false 69 | 70 | [profile.release] 71 | opt-level = 3 72 | debug = true 73 | 74 | [profile.dev] 75 | opt-level = 3 76 | debug = true 77 | 78 | [profile.test] 79 | opt-level = 3 80 | debug = true 81 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memchr.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.is_empty() { 8 | return; 9 | } 10 | memchr_iter(data[0], &data[1..]).count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memchr2.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr2_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 2 { 8 | return; 9 | } 10 | memchr2_iter(data[0], data[1], &data[2..]).count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memchr3.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr3_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 3 { 8 | return; 9 | } 10 | memchr3_iter(data[0], data[1], data[2], &data[3..]).count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memmem.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memmem; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 2 { 8 | return; 9 | } 10 | let split = std::cmp::max(data[0] as usize, 1) % data.len() as usize; 11 | let (needle, haystack) = (&data[..split], &data[split..]); 12 | memmem::find_iter(haystack, needle).count(); 13 | }); 14 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memrchr.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.is_empty() { 8 | return; 9 | } 10 | memchr_iter(data[0], &data[1..]).rev().count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memrchr2.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr2_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 2 { 8 | return; 9 | } 10 | memchr2_iter(data[0], data[1], &data[2..]).rev().count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memrchr3.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memchr3_iter; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 3 { 8 | return; 9 | } 10 | memchr3_iter(data[0], data[1], data[2], &data[3..]).rev().count(); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/memrmem.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use memchr::memmem; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | if data.len() < 2 { 8 | return; 9 | } 10 | let split = std::cmp::max(data[0] as usize, 1) % data.len() as usize; 11 | let (needle, haystack) = (&data[..split], &data[split..]); 12 | memmem::rfind_iter(haystack, needle).count(); 13 | }); 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 79 2 | use_small_heuristics = "max" 3 | -------------------------------------------------------------------------------- /scripts/make-byte-frequency-table: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This does simple normalized frequency analysis on UTF-8 encoded text. The 4 | # result of the analysis is translated to a ranked list, where every byte is 5 | # assigned a rank. This list is written to src/freqs.rs. 6 | # 7 | # Currently, the frequencies are generated from the following corpuses: 8 | # 9 | # * The CIA world fact book 10 | # * The source code of rustc 11 | # * Septuaginta 12 | 13 | from __future__ import absolute_import, division, print_function 14 | 15 | import argparse 16 | from collections import Counter 17 | import sys 18 | 19 | preamble = ''' 20 | // NOTE: The following code was generated by "scripts/frequencies.py", do not 21 | // edit directly 22 | '''.lstrip() 23 | 24 | 25 | def eprint(*args, **kwargs): 26 | kwargs['file'] = sys.stderr 27 | print(*args, **kwargs) 28 | 29 | 30 | def main(): 31 | p = argparse.ArgumentParser() 32 | p.add_argument('corpus', metavar='FILE', nargs='+') 33 | args = p.parse_args() 34 | 35 | # Get frequency counts of each byte. 36 | freqs = Counter() 37 | for i in range(0, 256): 38 | freqs[i] = 0 39 | 40 | eprint('reading entire corpus into memory') 41 | corpus = [] 42 | for fpath in args.corpus: 43 | corpus.append(open(fpath, 'rb').read()) 44 | 45 | eprint('computing byte frequencies') 46 | for c in corpus: 47 | for byte in c: 48 | freqs[byte] += 1.0 / float(len(c)) 49 | 50 | eprint('writing Rust code') 51 | # Get the rank of each byte. A lower rank => lower relative frequency. 52 | rank = [0] * 256 53 | for i, (byte, _) in enumerate(freqs.most_common()): 54 | # print(byte) 55 | rank[byte] = 255 - i 56 | 57 | # Forcefully set the highest rank possible for bytes that start multi-byte 58 | # UTF-8 sequences. The idea here is that a continuation byte will be more 59 | # discerning in a homogenous haystack. 60 | for byte in range(0xC0, 0xFF + 1): 61 | rank[byte] = 255 62 | 63 | # Now write Rust. 64 | olines = ['pub const BYTE_FREQUENCIES: [u8; 256] = ['] 65 | for byte in range(256): 66 | olines.append(' %3d, // %r' % (rank[byte], chr(byte))) 67 | olines.append('];') 68 | 69 | print(preamble) 70 | print('\n'.join(olines)) 71 | 72 | 73 | if __name__ == '__main__': 74 | main() 75 | -------------------------------------------------------------------------------- /src/arch/aarch64/memchr.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Wrapper routines for `memchr` and friends. 3 | 4 | These routines choose the best implementation at compile time. (This is 5 | different from `x86_64` because it is expected that `neon` is almost always 6 | available for `aarch64` targets.) 7 | */ 8 | 9 | macro_rules! defraw { 10 | ($ty:ident, $find:ident, $start:ident, $end:ident, $($needles:ident),+) => {{ 11 | #[cfg(target_feature = "neon")] 12 | { 13 | use crate::arch::aarch64::neon::memchr::$ty; 14 | 15 | debug!("chose neon for {}", stringify!($ty)); 16 | debug_assert!($ty::is_available()); 17 | // SAFETY: We know that wasm memchr is always available whenever 18 | // code is compiled for `aarch64` with the `neon` target feature 19 | // enabled. 20 | $ty::new_unchecked($($needles),+).$find($start, $end) 21 | } 22 | #[cfg(not(target_feature = "neon"))] 23 | { 24 | use crate::arch::all::memchr::$ty; 25 | 26 | debug!( 27 | "no neon feature available, using fallback for {}", 28 | stringify!($ty), 29 | ); 30 | $ty::new($($needles),+).$find($start, $end) 31 | } 32 | }} 33 | } 34 | 35 | /// memchr, but using raw pointers to represent the haystack. 36 | /// 37 | /// # Safety 38 | /// 39 | /// Pointers must be valid. See `One::find_raw`. 40 | #[inline(always)] 41 | pub(crate) unsafe fn memchr_raw( 42 | n1: u8, 43 | start: *const u8, 44 | end: *const u8, 45 | ) -> Option<*const u8> { 46 | defraw!(One, find_raw, start, end, n1) 47 | } 48 | 49 | /// memrchr, but using raw pointers to represent the haystack. 50 | /// 51 | /// # Safety 52 | /// 53 | /// Pointers must be valid. See `One::rfind_raw`. 54 | #[inline(always)] 55 | pub(crate) unsafe fn memrchr_raw( 56 | n1: u8, 57 | start: *const u8, 58 | end: *const u8, 59 | ) -> Option<*const u8> { 60 | defraw!(One, rfind_raw, start, end, n1) 61 | } 62 | 63 | /// memchr2, but using raw pointers to represent the haystack. 64 | /// 65 | /// # Safety 66 | /// 67 | /// Pointers must be valid. See `Two::find_raw`. 68 | #[inline(always)] 69 | pub(crate) unsafe fn memchr2_raw( 70 | n1: u8, 71 | n2: u8, 72 | start: *const u8, 73 | end: *const u8, 74 | ) -> Option<*const u8> { 75 | defraw!(Two, find_raw, start, end, n1, n2) 76 | } 77 | 78 | /// memrchr2, but using raw pointers to represent the haystack. 79 | /// 80 | /// # Safety 81 | /// 82 | /// Pointers must be valid. See `Two::rfind_raw`. 83 | #[inline(always)] 84 | pub(crate) unsafe fn memrchr2_raw( 85 | n1: u8, 86 | n2: u8, 87 | start: *const u8, 88 | end: *const u8, 89 | ) -> Option<*const u8> { 90 | defraw!(Two, rfind_raw, start, end, n1, n2) 91 | } 92 | 93 | /// memchr3, but using raw pointers to represent the haystack. 94 | /// 95 | /// # Safety 96 | /// 97 | /// Pointers must be valid. See `Three::find_raw`. 98 | #[inline(always)] 99 | pub(crate) unsafe fn memchr3_raw( 100 | n1: u8, 101 | n2: u8, 102 | n3: u8, 103 | start: *const u8, 104 | end: *const u8, 105 | ) -> Option<*const u8> { 106 | defraw!(Three, find_raw, start, end, n1, n2, n3) 107 | } 108 | 109 | /// memrchr3, but using raw pointers to represent the haystack. 110 | /// 111 | /// # Safety 112 | /// 113 | /// Pointers must be valid. See `Three::rfind_raw`. 114 | #[inline(always)] 115 | pub(crate) unsafe fn memrchr3_raw( 116 | n1: u8, 117 | n2: u8, 118 | n3: u8, 119 | start: *const u8, 120 | end: *const u8, 121 | ) -> Option<*const u8> { 122 | defraw!(Three, rfind_raw, start, end, n1, n2, n3) 123 | } 124 | 125 | /// Count all matching bytes, but using raw pointers to represent the haystack. 126 | /// 127 | /// # Safety 128 | /// 129 | /// Pointers must be valid. See `One::count_raw`. 130 | #[inline(always)] 131 | pub(crate) unsafe fn count_raw( 132 | n1: u8, 133 | start: *const u8, 134 | end: *const u8, 135 | ) -> usize { 136 | defraw!(One, count_raw, start, end, n1) 137 | } 138 | -------------------------------------------------------------------------------- /src/arch/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Vector algorithms for the `aarch64` target. 3 | */ 4 | 5 | pub mod neon; 6 | 7 | pub(crate) mod memchr; 8 | -------------------------------------------------------------------------------- /src/arch/aarch64/neon/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Algorithms for the `aarch64` target using 128-bit vectors via NEON. 3 | */ 4 | 5 | pub mod memchr; 6 | pub mod packedpair; 7 | -------------------------------------------------------------------------------- /src/arch/aarch64/neon/packedpair.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | A 128-bit vector implementation of the "packed pair" SIMD algorithm. 3 | 4 | The "packed pair" algorithm is based on the [generic SIMD] algorithm. The main 5 | difference is that it (by default) uses a background distribution of byte 6 | frequencies to heuristically select the pair of bytes to search for. 7 | 8 | [generic SIMD]: http://0x80.pl/articles/simd-strfind.html#first-and-last 9 | */ 10 | 11 | use core::arch::aarch64::uint8x16_t; 12 | 13 | use crate::arch::{all::packedpair::Pair, generic::packedpair}; 14 | 15 | /// A "packed pair" finder that uses 128-bit vector operations. 16 | /// 17 | /// This finder picks two bytes that it believes have high predictive power 18 | /// for indicating an overall match of a needle. Depending on whether 19 | /// `Finder::find` or `Finder::find_prefilter` is used, it reports offsets 20 | /// where the needle matches or could match. In the prefilter case, candidates 21 | /// are reported whenever the [`Pair`] of bytes given matches. 22 | #[derive(Clone, Copy, Debug)] 23 | pub struct Finder(packedpair::Finder); 24 | 25 | /// A "packed pair" finder that uses 128-bit vector operations. 26 | /// 27 | /// This finder picks two bytes that it believes have high predictive power 28 | /// for indicating an overall match of a needle. Depending on whether 29 | /// `Finder::find` or `Finder::find_prefilter` is used, it reports offsets 30 | /// where the needle matches or could match. In the prefilter case, candidates 31 | /// are reported whenever the [`Pair`] of bytes given matches. 32 | impl Finder { 33 | /// Create a new pair searcher. The searcher returned can either report 34 | /// exact matches of `needle` or act as a prefilter and report candidate 35 | /// positions of `needle`. 36 | /// 37 | /// If neon is unavailable in the current environment or if a [`Pair`] 38 | /// could not be constructed from the needle given, then `None` is 39 | /// returned. 40 | #[inline] 41 | pub fn new(needle: &[u8]) -> Option { 42 | Finder::with_pair(needle, Pair::new(needle)?) 43 | } 44 | 45 | /// Create a new "packed pair" finder using the pair of bytes given. 46 | /// 47 | /// This constructor permits callers to control precisely which pair of 48 | /// bytes is used as a predicate. 49 | /// 50 | /// If neon is unavailable in the current environment, then `None` is 51 | /// returned. 52 | #[inline] 53 | pub fn with_pair(needle: &[u8], pair: Pair) -> Option { 54 | if Finder::is_available() { 55 | // SAFETY: we check that sse2 is available above. We are also 56 | // guaranteed to have needle.len() > 1 because we have a valid 57 | // Pair. 58 | unsafe { Some(Finder::with_pair_impl(needle, pair)) } 59 | } else { 60 | None 61 | } 62 | } 63 | 64 | /// Create a new `Finder` specific to neon vectors and routines. 65 | /// 66 | /// # Safety 67 | /// 68 | /// Same as the safety for `packedpair::Finder::new`, and callers must also 69 | /// ensure that neon is available. 70 | #[target_feature(enable = "neon")] 71 | #[inline] 72 | unsafe fn with_pair_impl(needle: &[u8], pair: Pair) -> Finder { 73 | let finder = packedpair::Finder::::new(needle, pair); 74 | Finder(finder) 75 | } 76 | 77 | /// Returns true when this implementation is available in the current 78 | /// environment. 79 | /// 80 | /// When this is true, it is guaranteed that [`Finder::with_pair`] will 81 | /// return a `Some` value. Similarly, when it is false, it is guaranteed 82 | /// that `Finder::with_pair` will return a `None` value. Notice that this 83 | /// does not guarantee that [`Finder::new`] will return a `Finder`. Namely, 84 | /// even when `Finder::is_available` is true, it is not guaranteed that a 85 | /// valid [`Pair`] can be found from the needle given. 86 | /// 87 | /// Note also that for the lifetime of a single program, if this returns 88 | /// true then it will always return true. 89 | #[inline] 90 | pub fn is_available() -> bool { 91 | #[cfg(target_feature = "neon")] 92 | { 93 | true 94 | } 95 | #[cfg(not(target_feature = "neon"))] 96 | { 97 | false 98 | } 99 | } 100 | 101 | /// Execute a search using neon vectors and routines. 102 | /// 103 | /// # Panics 104 | /// 105 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 106 | #[inline] 107 | pub fn find(&self, haystack: &[u8], needle: &[u8]) -> Option { 108 | // SAFETY: Building a `Finder` means it's safe to call 'neon' routines. 109 | unsafe { self.find_impl(haystack, needle) } 110 | } 111 | 112 | /// Execute a search using neon vectors and routines. 113 | /// 114 | /// # Panics 115 | /// 116 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 117 | #[inline] 118 | pub fn find_prefilter(&self, haystack: &[u8]) -> Option { 119 | // SAFETY: Building a `Finder` means it's safe to call 'neon' routines. 120 | unsafe { self.find_prefilter_impl(haystack) } 121 | } 122 | 123 | /// Execute a search using neon vectors and routines. 124 | /// 125 | /// # Panics 126 | /// 127 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 128 | /// 129 | /// # Safety 130 | /// 131 | /// (The target feature safety obligation is automatically fulfilled by 132 | /// virtue of being a method on `Finder`, which can only be constructed 133 | /// when it is safe to call `neon` routines.) 134 | #[target_feature(enable = "neon")] 135 | #[inline] 136 | unsafe fn find_impl( 137 | &self, 138 | haystack: &[u8], 139 | needle: &[u8], 140 | ) -> Option { 141 | self.0.find(haystack, needle) 142 | } 143 | 144 | /// Execute a prefilter search using neon vectors and routines. 145 | /// 146 | /// # Panics 147 | /// 148 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 149 | /// 150 | /// # Safety 151 | /// 152 | /// (The target feature safety obligation is automatically fulfilled by 153 | /// virtue of being a method on `Finder`, which can only be constructed 154 | /// when it is safe to call `neon` routines.) 155 | #[target_feature(enable = "neon")] 156 | #[inline] 157 | unsafe fn find_prefilter_impl(&self, haystack: &[u8]) -> Option { 158 | self.0.find_prefilter(haystack) 159 | } 160 | 161 | /// Returns the pair of offsets (into the needle) used to check as a 162 | /// predicate before confirming whether a needle exists at a particular 163 | /// position. 164 | #[inline] 165 | pub fn pair(&self) -> &Pair { 166 | self.0.pair() 167 | } 168 | 169 | /// Returns the minimum haystack length that this `Finder` can search. 170 | /// 171 | /// Using a haystack with length smaller than this in a search will result 172 | /// in a panic. The reason for this restriction is that this finder is 173 | /// meant to be a low-level component that is part of a larger substring 174 | /// strategy. In that sense, it avoids trying to handle all cases and 175 | /// instead only handles the cases that it can handle very well. 176 | #[inline] 177 | pub fn min_haystack_len(&self) -> usize { 178 | self.0.min_haystack_len() 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | use super::*; 185 | 186 | fn find(haystack: &[u8], needle: &[u8]) -> Option> { 187 | let f = Finder::new(needle)?; 188 | if haystack.len() < f.min_haystack_len() { 189 | return None; 190 | } 191 | Some(f.find(haystack, needle)) 192 | } 193 | 194 | define_substring_forward_quickcheck!(find); 195 | 196 | #[test] 197 | fn forward_substring() { 198 | crate::tests::substring::Runner::new().fwd(find).run() 199 | } 200 | 201 | #[test] 202 | fn forward_packedpair() { 203 | fn find( 204 | haystack: &[u8], 205 | needle: &[u8], 206 | index1: u8, 207 | index2: u8, 208 | ) -> Option> { 209 | let pair = Pair::with_indices(needle, index1, index2)?; 210 | let f = Finder::with_pair(needle, pair)?; 211 | if haystack.len() < f.min_haystack_len() { 212 | return None; 213 | } 214 | Some(f.find(haystack, needle)) 215 | } 216 | crate::tests::packedpair::Runner::new().fwd(find).run() 217 | } 218 | 219 | #[test] 220 | fn forward_packedpair_prefilter() { 221 | fn find( 222 | haystack: &[u8], 223 | needle: &[u8], 224 | index1: u8, 225 | index2: u8, 226 | ) -> Option> { 227 | let pair = Pair::with_indices(needle, index1, index2)?; 228 | let f = Finder::with_pair(needle, pair)?; 229 | if haystack.len() < f.min_haystack_len() { 230 | return None; 231 | } 232 | Some(f.find_prefilter(haystack)) 233 | } 234 | crate::tests::packedpair::Runner::new().fwd(find).run() 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/arch/all/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Contains architecture independent routines. 3 | 4 | These routines are often used as a "fallback" implementation when the more 5 | specialized architecture dependent routines are unavailable. 6 | */ 7 | 8 | pub mod memchr; 9 | pub mod packedpair; 10 | pub mod rabinkarp; 11 | #[cfg(feature = "alloc")] 12 | pub mod shiftor; 13 | pub mod twoway; 14 | 15 | /// Returns true if and only if `needle` is a prefix of `haystack`. 16 | /// 17 | /// This uses a latency optimized variant of `memcmp` internally which *might* 18 | /// make this faster for very short strings. 19 | /// 20 | /// # Inlining 21 | /// 22 | /// This routine is marked `inline(always)`. If you want to call this function 23 | /// in a way that is not always inlined, you'll need to wrap a call to it in 24 | /// another function that is marked as `inline(never)` or just `inline`. 25 | #[inline(always)] 26 | pub fn is_prefix(haystack: &[u8], needle: &[u8]) -> bool { 27 | needle.len() <= haystack.len() 28 | && is_equal(&haystack[..needle.len()], needle) 29 | } 30 | 31 | /// Returns true if and only if `needle` is a suffix of `haystack`. 32 | /// 33 | /// This uses a latency optimized variant of `memcmp` internally which *might* 34 | /// make this faster for very short strings. 35 | /// 36 | /// # Inlining 37 | /// 38 | /// This routine is marked `inline(always)`. If you want to call this function 39 | /// in a way that is not always inlined, you'll need to wrap a call to it in 40 | /// another function that is marked as `inline(never)` or just `inline`. 41 | #[inline(always)] 42 | pub fn is_suffix(haystack: &[u8], needle: &[u8]) -> bool { 43 | needle.len() <= haystack.len() 44 | && is_equal(&haystack[haystack.len() - needle.len()..], needle) 45 | } 46 | 47 | /// Compare corresponding bytes in `x` and `y` for equality. 48 | /// 49 | /// That is, this returns true if and only if `x.len() == y.len()` and 50 | /// `x[i] == y[i]` for all `0 <= i < x.len()`. 51 | /// 52 | /// # Inlining 53 | /// 54 | /// This routine is marked `inline(always)`. If you want to call this function 55 | /// in a way that is not always inlined, you'll need to wrap a call to it in 56 | /// another function that is marked as `inline(never)` or just `inline`. 57 | /// 58 | /// # Motivation 59 | /// 60 | /// Why not use slice equality instead? Well, slice equality usually results in 61 | /// a call out to the current platform's `libc` which might not be inlineable 62 | /// or have other overhead. This routine isn't guaranteed to be a win, but it 63 | /// might be in some cases. 64 | #[inline(always)] 65 | pub fn is_equal(x: &[u8], y: &[u8]) -> bool { 66 | if x.len() != y.len() { 67 | return false; 68 | } 69 | // SAFETY: Our pointers are derived directly from borrowed slices which 70 | // uphold all of our safety guarantees except for length. We account for 71 | // length with the check above. 72 | unsafe { is_equal_raw(x.as_ptr(), y.as_ptr(), x.len()) } 73 | } 74 | 75 | /// Compare `n` bytes at the given pointers for equality. 76 | /// 77 | /// This returns true if and only if `*x.add(i) == *y.add(i)` for all 78 | /// `0 <= i < n`. 79 | /// 80 | /// # Inlining 81 | /// 82 | /// This routine is marked `inline(always)`. If you want to call this function 83 | /// in a way that is not always inlined, you'll need to wrap a call to it in 84 | /// another function that is marked as `inline(never)` or just `inline`. 85 | /// 86 | /// # Motivation 87 | /// 88 | /// Why not use slice equality instead? Well, slice equality usually results in 89 | /// a call out to the current platform's `libc` which might not be inlineable 90 | /// or have other overhead. This routine isn't guaranteed to be a win, but it 91 | /// might be in some cases. 92 | /// 93 | /// # Safety 94 | /// 95 | /// * Both `x` and `y` must be valid for reads of up to `n` bytes. 96 | /// * Both `x` and `y` must point to an initialized value. 97 | /// * Both `x` and `y` must each point to an allocated object and 98 | /// must either be in bounds or at most one byte past the end of the 99 | /// allocated object. `x` and `y` do not need to point to the same allocated 100 | /// object, but they may. 101 | /// * Both `x` and `y` must be _derived from_ a pointer to their respective 102 | /// allocated objects. 103 | /// * The distance between `x` and `x+n` must not overflow `isize`. Similarly 104 | /// for `y` and `y+n`. 105 | /// * The distance being in bounds must not rely on "wrapping around" the 106 | /// address space. 107 | #[inline(always)] 108 | pub unsafe fn is_equal_raw( 109 | mut x: *const u8, 110 | mut y: *const u8, 111 | mut n: usize, 112 | ) -> bool { 113 | // When we have 4 or more bytes to compare, then proceed in chunks of 4 at 114 | // a time using unaligned loads. 115 | // 116 | // Also, why do 4 byte loads instead of, say, 8 byte loads? The reason is 117 | // that this particular version of memcmp is likely to be called with tiny 118 | // needles. That means that if we do 8 byte loads, then a higher proportion 119 | // of memcmp calls will use the slower variant above. With that said, this 120 | // is a hypothesis and is only loosely supported by benchmarks. There's 121 | // likely some improvement that could be made here. The main thing here 122 | // though is to optimize for latency, not throughput. 123 | 124 | // SAFETY: The caller is responsible for ensuring the pointers we get are 125 | // valid and readable for at least `n` bytes. We also do unaligned loads, 126 | // so there's no need to ensure we're aligned. (This is justified by this 127 | // routine being specifically for short strings.) 128 | while n >= 4 { 129 | let vx = x.cast::().read_unaligned(); 130 | let vy = y.cast::().read_unaligned(); 131 | if vx != vy { 132 | return false; 133 | } 134 | x = x.add(4); 135 | y = y.add(4); 136 | n -= 4; 137 | } 138 | // If we don't have enough bytes to do 4-byte at a time loads, then 139 | // do partial loads. Note that I used to have a byte-at-a-time 140 | // loop here and that turned out to be quite a bit slower for the 141 | // memmem/pathological/defeat-simple-vector-alphabet benchmark. 142 | if n >= 2 { 143 | let vx = x.cast::().read_unaligned(); 144 | let vy = y.cast::().read_unaligned(); 145 | if vx != vy { 146 | return false; 147 | } 148 | x = x.add(2); 149 | y = y.add(2); 150 | n -= 2; 151 | } 152 | if n > 0 { 153 | if x.read() != y.read() { 154 | return false; 155 | } 156 | } 157 | true 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | 164 | #[test] 165 | fn equals_different_lengths() { 166 | assert!(!is_equal(b"", b"a")); 167 | assert!(!is_equal(b"a", b"")); 168 | assert!(!is_equal(b"ab", b"a")); 169 | assert!(!is_equal(b"a", b"ab")); 170 | } 171 | 172 | #[test] 173 | fn equals_mismatch() { 174 | let one_mismatch = [ 175 | (&b"a"[..], &b"x"[..]), 176 | (&b"ab"[..], &b"ax"[..]), 177 | (&b"abc"[..], &b"abx"[..]), 178 | (&b"abcd"[..], &b"abcx"[..]), 179 | (&b"abcde"[..], &b"abcdx"[..]), 180 | (&b"abcdef"[..], &b"abcdex"[..]), 181 | (&b"abcdefg"[..], &b"abcdefx"[..]), 182 | (&b"abcdefgh"[..], &b"abcdefgx"[..]), 183 | (&b"abcdefghi"[..], &b"abcdefghx"[..]), 184 | (&b"abcdefghij"[..], &b"abcdefghix"[..]), 185 | (&b"abcdefghijk"[..], &b"abcdefghijx"[..]), 186 | (&b"abcdefghijkl"[..], &b"abcdefghijkx"[..]), 187 | (&b"abcdefghijklm"[..], &b"abcdefghijklx"[..]), 188 | (&b"abcdefghijklmn"[..], &b"abcdefghijklmx"[..]), 189 | ]; 190 | for (x, y) in one_mismatch { 191 | assert_eq!(x.len(), y.len(), "lengths should match"); 192 | assert!(!is_equal(x, y)); 193 | assert!(!is_equal(y, x)); 194 | } 195 | } 196 | 197 | #[test] 198 | fn equals_yes() { 199 | assert!(is_equal(b"", b"")); 200 | assert!(is_equal(b"a", b"a")); 201 | assert!(is_equal(b"ab", b"ab")); 202 | assert!(is_equal(b"abc", b"abc")); 203 | assert!(is_equal(b"abcd", b"abcd")); 204 | assert!(is_equal(b"abcde", b"abcde")); 205 | assert!(is_equal(b"abcdef", b"abcdef")); 206 | assert!(is_equal(b"abcdefg", b"abcdefg")); 207 | assert!(is_equal(b"abcdefgh", b"abcdefgh")); 208 | assert!(is_equal(b"abcdefghi", b"abcdefghi")); 209 | } 210 | 211 | #[test] 212 | fn prefix() { 213 | assert!(is_prefix(b"", b"")); 214 | assert!(is_prefix(b"a", b"")); 215 | assert!(is_prefix(b"ab", b"")); 216 | assert!(is_prefix(b"foo", b"foo")); 217 | assert!(is_prefix(b"foobar", b"foo")); 218 | 219 | assert!(!is_prefix(b"foo", b"fob")); 220 | assert!(!is_prefix(b"foobar", b"fob")); 221 | } 222 | 223 | #[test] 224 | fn suffix() { 225 | assert!(is_suffix(b"", b"")); 226 | assert!(is_suffix(b"a", b"")); 227 | assert!(is_suffix(b"ab", b"")); 228 | assert!(is_suffix(b"foo", b"foo")); 229 | assert!(is_suffix(b"foobar", b"bar")); 230 | 231 | assert!(!is_suffix(b"foo", b"goo")); 232 | assert!(!is_suffix(b"foobar", b"gar")); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/arch/all/packedpair/default_rank.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const RANK: [u8; 256] = [ 2 | 55, // '\x00' 3 | 52, // '\x01' 4 | 51, // '\x02' 5 | 50, // '\x03' 6 | 49, // '\x04' 7 | 48, // '\x05' 8 | 47, // '\x06' 9 | 46, // '\x07' 10 | 45, // '\x08' 11 | 103, // '\t' 12 | 242, // '\n' 13 | 66, // '\x0b' 14 | 67, // '\x0c' 15 | 229, // '\r' 16 | 44, // '\x0e' 17 | 43, // '\x0f' 18 | 42, // '\x10' 19 | 41, // '\x11' 20 | 40, // '\x12' 21 | 39, // '\x13' 22 | 38, // '\x14' 23 | 37, // '\x15' 24 | 36, // '\x16' 25 | 35, // '\x17' 26 | 34, // '\x18' 27 | 33, // '\x19' 28 | 56, // '\x1a' 29 | 32, // '\x1b' 30 | 31, // '\x1c' 31 | 30, // '\x1d' 32 | 29, // '\x1e' 33 | 28, // '\x1f' 34 | 255, // ' ' 35 | 148, // '!' 36 | 164, // '"' 37 | 149, // '#' 38 | 136, // '$' 39 | 160, // '%' 40 | 155, // '&' 41 | 173, // "'" 42 | 221, // '(' 43 | 222, // ')' 44 | 134, // '*' 45 | 122, // '+' 46 | 232, // ',' 47 | 202, // '-' 48 | 215, // '.' 49 | 224, // '/' 50 | 208, // '0' 51 | 220, // '1' 52 | 204, // '2' 53 | 187, // '3' 54 | 183, // '4' 55 | 179, // '5' 56 | 177, // '6' 57 | 168, // '7' 58 | 178, // '8' 59 | 200, // '9' 60 | 226, // ':' 61 | 195, // ';' 62 | 154, // '<' 63 | 184, // '=' 64 | 174, // '>' 65 | 126, // '?' 66 | 120, // '@' 67 | 191, // 'A' 68 | 157, // 'B' 69 | 194, // 'C' 70 | 170, // 'D' 71 | 189, // 'E' 72 | 162, // 'F' 73 | 161, // 'G' 74 | 150, // 'H' 75 | 193, // 'I' 76 | 142, // 'J' 77 | 137, // 'K' 78 | 171, // 'L' 79 | 176, // 'M' 80 | 185, // 'N' 81 | 167, // 'O' 82 | 186, // 'P' 83 | 112, // 'Q' 84 | 175, // 'R' 85 | 192, // 'S' 86 | 188, // 'T' 87 | 156, // 'U' 88 | 140, // 'V' 89 | 143, // 'W' 90 | 123, // 'X' 91 | 133, // 'Y' 92 | 128, // 'Z' 93 | 147, // '[' 94 | 138, // '\\' 95 | 146, // ']' 96 | 114, // '^' 97 | 223, // '_' 98 | 151, // '`' 99 | 249, // 'a' 100 | 216, // 'b' 101 | 238, // 'c' 102 | 236, // 'd' 103 | 253, // 'e' 104 | 227, // 'f' 105 | 218, // 'g' 106 | 230, // 'h' 107 | 247, // 'i' 108 | 135, // 'j' 109 | 180, // 'k' 110 | 241, // 'l' 111 | 233, // 'm' 112 | 246, // 'n' 113 | 244, // 'o' 114 | 231, // 'p' 115 | 139, // 'q' 116 | 245, // 'r' 117 | 243, // 's' 118 | 251, // 't' 119 | 235, // 'u' 120 | 201, // 'v' 121 | 196, // 'w' 122 | 240, // 'x' 123 | 214, // 'y' 124 | 152, // 'z' 125 | 182, // '{' 126 | 205, // '|' 127 | 181, // '}' 128 | 127, // '~' 129 | 27, // '\x7f' 130 | 212, // '\x80' 131 | 211, // '\x81' 132 | 210, // '\x82' 133 | 213, // '\x83' 134 | 228, // '\x84' 135 | 197, // '\x85' 136 | 169, // '\x86' 137 | 159, // '\x87' 138 | 131, // '\x88' 139 | 172, // '\x89' 140 | 105, // '\x8a' 141 | 80, // '\x8b' 142 | 98, // '\x8c' 143 | 96, // '\x8d' 144 | 97, // '\x8e' 145 | 81, // '\x8f' 146 | 207, // '\x90' 147 | 145, // '\x91' 148 | 116, // '\x92' 149 | 115, // '\x93' 150 | 144, // '\x94' 151 | 130, // '\x95' 152 | 153, // '\x96' 153 | 121, // '\x97' 154 | 107, // '\x98' 155 | 132, // '\x99' 156 | 109, // '\x9a' 157 | 110, // '\x9b' 158 | 124, // '\x9c' 159 | 111, // '\x9d' 160 | 82, // '\x9e' 161 | 108, // '\x9f' 162 | 118, // '\xa0' 163 | 141, // '¡' 164 | 113, // '¢' 165 | 129, // '£' 166 | 119, // '¤' 167 | 125, // '¥' 168 | 165, // '¦' 169 | 117, // '§' 170 | 92, // '¨' 171 | 106, // '©' 172 | 83, // 'ª' 173 | 72, // '«' 174 | 99, // '¬' 175 | 93, // '\xad' 176 | 65, // '®' 177 | 79, // '¯' 178 | 166, // '°' 179 | 237, // '±' 180 | 163, // '²' 181 | 199, // '³' 182 | 190, // '´' 183 | 225, // 'µ' 184 | 209, // '¶' 185 | 203, // '·' 186 | 198, // '¸' 187 | 217, // '¹' 188 | 219, // 'º' 189 | 206, // '»' 190 | 234, // '¼' 191 | 248, // '½' 192 | 158, // '¾' 193 | 239, // '¿' 194 | 255, // 'À' 195 | 255, // 'Á' 196 | 255, // 'Â' 197 | 255, // 'Ã' 198 | 255, // 'Ä' 199 | 255, // 'Å' 200 | 255, // 'Æ' 201 | 255, // 'Ç' 202 | 255, // 'È' 203 | 255, // 'É' 204 | 255, // 'Ê' 205 | 255, // 'Ë' 206 | 255, // 'Ì' 207 | 255, // 'Í' 208 | 255, // 'Î' 209 | 255, // 'Ï' 210 | 255, // 'Ð' 211 | 255, // 'Ñ' 212 | 255, // 'Ò' 213 | 255, // 'Ó' 214 | 255, // 'Ô' 215 | 255, // 'Õ' 216 | 255, // 'Ö' 217 | 255, // '×' 218 | 255, // 'Ø' 219 | 255, // 'Ù' 220 | 255, // 'Ú' 221 | 255, // 'Û' 222 | 255, // 'Ü' 223 | 255, // 'Ý' 224 | 255, // 'Þ' 225 | 255, // 'ß' 226 | 255, // 'à' 227 | 255, // 'á' 228 | 255, // 'â' 229 | 255, // 'ã' 230 | 255, // 'ä' 231 | 255, // 'å' 232 | 255, // 'æ' 233 | 255, // 'ç' 234 | 255, // 'è' 235 | 255, // 'é' 236 | 255, // 'ê' 237 | 255, // 'ë' 238 | 255, // 'ì' 239 | 255, // 'í' 240 | 255, // 'î' 241 | 255, // 'ï' 242 | 255, // 'ð' 243 | 255, // 'ñ' 244 | 255, // 'ò' 245 | 255, // 'ó' 246 | 255, // 'ô' 247 | 255, // 'õ' 248 | 255, // 'ö' 249 | 255, // '÷' 250 | 255, // 'ø' 251 | 255, // 'ù' 252 | 255, // 'ú' 253 | 255, // 'û' 254 | 255, // 'ü' 255 | 255, // 'ý' 256 | 255, // 'þ' 257 | 255, // 'ÿ' 258 | ]; 259 | -------------------------------------------------------------------------------- /src/arch/all/shiftor.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | An implementation of the [Shift-Or substring search algorithm][shiftor]. 3 | 4 | [shiftor]: https://en.wikipedia.org/wiki/Bitap_algorithm 5 | */ 6 | 7 | use alloc::boxed::Box; 8 | 9 | /// The type of our mask. 10 | /// 11 | /// While we don't expose anyway to configure this in the public API, if one 12 | /// really needs less memory usage or support for longer needles, then it is 13 | /// suggested to copy the code from this module and modify it to fit your 14 | /// needs. The code below is written to be correct regardless of whether Mask 15 | /// is a u8, u16, u32, u64 or u128. 16 | type Mask = u16; 17 | 18 | /// A forward substring searcher using the Shift-Or algorithm. 19 | #[derive(Debug)] 20 | pub struct Finder { 21 | masks: Box<[Mask; 256]>, 22 | needle_len: usize, 23 | } 24 | 25 | impl Finder { 26 | const MAX_NEEDLE_LEN: usize = (Mask::BITS - 1) as usize; 27 | 28 | /// Create a new Shift-Or forward searcher for the given `needle`. 29 | /// 30 | /// The needle may be empty. The empty needle matches at every byte offset. 31 | #[inline] 32 | pub fn new(needle: &[u8]) -> Option { 33 | let needle_len = needle.len(); 34 | if needle_len > Finder::MAX_NEEDLE_LEN { 35 | // A match is found when bit 7 is set in 'result' in the search 36 | // routine below. So our needle can't be bigger than 7. We could 37 | // permit bigger needles by using u16, u32 or u64 for our mask 38 | // entries. But this is all we need for this example. 39 | return None; 40 | } 41 | let mut searcher = Finder { masks: Box::from([!0; 256]), needle_len }; 42 | for (i, &byte) in needle.iter().enumerate() { 43 | searcher.masks[usize::from(byte)] &= !(1 << i); 44 | } 45 | Some(searcher) 46 | } 47 | 48 | /// Return the first occurrence of the needle given to `Finder::new` in 49 | /// the `haystack` given. If no such occurrence exists, then `None` is 50 | /// returned. 51 | /// 52 | /// Unlike most other substring search implementations in this crate, this 53 | /// finder does not require passing the needle at search time. A match can 54 | /// be determined without the needle at all since the required information 55 | /// is already encoded into this finder at construction time. 56 | /// 57 | /// The maximum value this can return is `haystack.len()`, which can only 58 | /// occur when the needle and haystack both have length zero. Otherwise, 59 | /// for non-empty haystacks, the maximum value is `haystack.len() - 1`. 60 | #[inline] 61 | pub fn find(&self, haystack: &[u8]) -> Option { 62 | if self.needle_len == 0 { 63 | return Some(0); 64 | } 65 | let mut result = !1; 66 | for (i, &byte) in haystack.iter().enumerate() { 67 | result |= self.masks[usize::from(byte)]; 68 | result <<= 1; 69 | if result & (1 << self.needle_len) == 0 { 70 | return Some(i + 1 - self.needle_len); 71 | } 72 | } 73 | None 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | 81 | define_substring_forward_quickcheck!(|h, n| Some(Finder::new(n)?.find(h))); 82 | 83 | #[test] 84 | fn forward() { 85 | crate::tests::substring::Runner::new() 86 | .fwd(|h, n| Some(Finder::new(n)?.find(h))) 87 | .run(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/arch/generic/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module defines "generic" routines that can be specialized to specific 3 | architectures. 4 | 5 | We don't expose this module primarily because it would require exposing all 6 | of the internal infrastructure required to write these generic routines. 7 | That infrastructure should be treated as an implementation detail so that 8 | it is allowed to evolve. Instead, what we expose are architecture specific 9 | instantiations of these generic implementations. The generic code just lets us 10 | write the code once (usually). 11 | */ 12 | 13 | pub(crate) mod memchr; 14 | pub(crate) mod packedpair; 15 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | A module with low-level architecture dependent routines. 3 | 4 | These routines are useful as primitives for tasks not covered by the higher 5 | level crate API. 6 | */ 7 | 8 | pub mod all; 9 | pub(crate) mod generic; 10 | 11 | #[cfg(target_arch = "aarch64")] 12 | pub mod aarch64; 13 | #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))] 14 | pub mod wasm32; 15 | #[cfg(target_arch = "x86_64")] 16 | pub mod x86_64; 17 | -------------------------------------------------------------------------------- /src/arch/wasm32/memchr.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Wrapper routines for `memchr` and friends. 3 | 4 | These routines choose the best implementation at compile time. (This is 5 | different from `x86_64` because it is expected that `simd128` is almost always 6 | available for `wasm32` targets.) 7 | */ 8 | 9 | macro_rules! defraw { 10 | ($ty:ident, $find:ident, $start:ident, $end:ident, $($needles:ident),+) => {{ 11 | use crate::arch::wasm32::simd128::memchr::$ty; 12 | 13 | debug!("chose simd128 for {}", stringify!($ty)); 14 | debug_assert!($ty::is_available()); 15 | // SAFETY: We know that wasm memchr is always available whenever 16 | // code is compiled for `wasm32` with the `simd128` target feature 17 | // enabled. 18 | $ty::new_unchecked($($needles),+).$find($start, $end) 19 | }} 20 | } 21 | 22 | /// memchr, but using raw pointers to represent the haystack. 23 | /// 24 | /// # Safety 25 | /// 26 | /// Pointers must be valid. See `One::find_raw`. 27 | #[inline(always)] 28 | pub(crate) unsafe fn memchr_raw( 29 | n1: u8, 30 | start: *const u8, 31 | end: *const u8, 32 | ) -> Option<*const u8> { 33 | defraw!(One, find_raw, start, end, n1) 34 | } 35 | 36 | /// memrchr, but using raw pointers to represent the haystack. 37 | /// 38 | /// # Safety 39 | /// 40 | /// Pointers must be valid. See `One::rfind_raw`. 41 | #[inline(always)] 42 | pub(crate) unsafe fn memrchr_raw( 43 | n1: u8, 44 | start: *const u8, 45 | end: *const u8, 46 | ) -> Option<*const u8> { 47 | defraw!(One, rfind_raw, start, end, n1) 48 | } 49 | 50 | /// memchr2, but using raw pointers to represent the haystack. 51 | /// 52 | /// # Safety 53 | /// 54 | /// Pointers must be valid. See `Two::find_raw`. 55 | #[inline(always)] 56 | pub(crate) unsafe fn memchr2_raw( 57 | n1: u8, 58 | n2: u8, 59 | start: *const u8, 60 | end: *const u8, 61 | ) -> Option<*const u8> { 62 | defraw!(Two, find_raw, start, end, n1, n2) 63 | } 64 | 65 | /// memrchr2, but using raw pointers to represent the haystack. 66 | /// 67 | /// # Safety 68 | /// 69 | /// Pointers must be valid. See `Two::rfind_raw`. 70 | #[inline(always)] 71 | pub(crate) unsafe fn memrchr2_raw( 72 | n1: u8, 73 | n2: u8, 74 | start: *const u8, 75 | end: *const u8, 76 | ) -> Option<*const u8> { 77 | defraw!(Two, rfind_raw, start, end, n1, n2) 78 | } 79 | 80 | /// memchr3, but using raw pointers to represent the haystack. 81 | /// 82 | /// # Safety 83 | /// 84 | /// Pointers must be valid. See `Three::find_raw`. 85 | #[inline(always)] 86 | pub(crate) unsafe fn memchr3_raw( 87 | n1: u8, 88 | n2: u8, 89 | n3: u8, 90 | start: *const u8, 91 | end: *const u8, 92 | ) -> Option<*const u8> { 93 | defraw!(Three, find_raw, start, end, n1, n2, n3) 94 | } 95 | 96 | /// memrchr3, but using raw pointers to represent the haystack. 97 | /// 98 | /// # Safety 99 | /// 100 | /// Pointers must be valid. See `Three::rfind_raw`. 101 | #[inline(always)] 102 | pub(crate) unsafe fn memrchr3_raw( 103 | n1: u8, 104 | n2: u8, 105 | n3: u8, 106 | start: *const u8, 107 | end: *const u8, 108 | ) -> Option<*const u8> { 109 | defraw!(Three, rfind_raw, start, end, n1, n2, n3) 110 | } 111 | 112 | /// Count all matching bytes, but using raw pointers to represent the haystack. 113 | /// 114 | /// # Safety 115 | /// 116 | /// Pointers must be valid. See `One::count_raw`. 117 | #[inline(always)] 118 | pub(crate) unsafe fn count_raw( 119 | n1: u8, 120 | start: *const u8, 121 | end: *const u8, 122 | ) -> usize { 123 | defraw!(One, count_raw, start, end, n1) 124 | } 125 | -------------------------------------------------------------------------------- /src/arch/wasm32/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Vector algorithms for the `wasm32` target. 3 | */ 4 | 5 | pub mod simd128; 6 | 7 | pub(crate) mod memchr; 8 | -------------------------------------------------------------------------------- /src/arch/wasm32/simd128/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Algorithms for the `wasm32` target using 128-bit vectors via simd128. 3 | */ 4 | 5 | pub mod memchr; 6 | pub mod packedpair; 7 | -------------------------------------------------------------------------------- /src/arch/wasm32/simd128/packedpair.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | A 128-bit vector implementation of the "packed pair" SIMD algorithm. 3 | 4 | The "packed pair" algorithm is based on the [generic SIMD] algorithm. The main 5 | difference is that it (by default) uses a background distribution of byte 6 | frequencies to heuristically select the pair of bytes to search for. 7 | 8 | [generic SIMD]: http://0x80.pl/articles/simd-strfind.html#first-and-last 9 | */ 10 | 11 | use core::arch::wasm32::v128; 12 | 13 | use crate::arch::{all::packedpair::Pair, generic::packedpair}; 14 | 15 | /// A "packed pair" finder that uses 128-bit vector operations. 16 | /// 17 | /// This finder picks two bytes that it believes have high predictive power 18 | /// for indicating an overall match of a needle. Depending on whether 19 | /// `Finder::find` or `Finder::find_prefilter` is used, it reports offsets 20 | /// where the needle matches or could match. In the prefilter case, candidates 21 | /// are reported whenever the [`Pair`] of bytes given matches. 22 | #[derive(Clone, Copy, Debug)] 23 | pub struct Finder(packedpair::Finder); 24 | 25 | impl Finder { 26 | /// Create a new pair searcher. The searcher returned can either report 27 | /// exact matches of `needle` or act as a prefilter and report candidate 28 | /// positions of `needle`. 29 | /// 30 | /// If simd128 is unavailable in the current environment or if a [`Pair`] 31 | /// could not be constructed from the needle given, then `None` is 32 | /// returned. 33 | #[inline] 34 | pub fn new(needle: &[u8]) -> Option { 35 | Finder::with_pair(needle, Pair::new(needle)?) 36 | } 37 | 38 | /// Create a new "packed pair" finder using the pair of bytes given. 39 | /// 40 | /// This constructor permits callers to control precisely which pair of 41 | /// bytes is used as a predicate. 42 | /// 43 | /// If simd128 is unavailable in the current environment, then `None` is 44 | /// returned. 45 | #[inline] 46 | pub fn with_pair(needle: &[u8], pair: Pair) -> Option { 47 | if Finder::is_available() { 48 | // SAFETY: we check that simd128 is available above. We are also 49 | // guaranteed to have needle.len() > 1 because we have a valid 50 | // Pair. 51 | unsafe { Some(Finder::with_pair_impl(needle, pair)) } 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | /// Create a new `Finder` specific to simd128 vectors and routines. 58 | /// 59 | /// # Safety 60 | /// 61 | /// Same as the safety for `packedpair::Finder::new`, and callers must also 62 | /// ensure that simd128 is available. 63 | #[target_feature(enable = "simd128")] 64 | #[inline] 65 | unsafe fn with_pair_impl(needle: &[u8], pair: Pair) -> Finder { 66 | let finder = packedpair::Finder::::new(needle, pair); 67 | Finder(finder) 68 | } 69 | 70 | /// Returns true when this implementation is available in the current 71 | /// environment. 72 | /// 73 | /// When this is true, it is guaranteed that [`Finder::with_pair`] will 74 | /// return a `Some` value. Similarly, when it is false, it is guaranteed 75 | /// that `Finder::with_pair` will return a `None` value. Notice that this 76 | /// does not guarantee that [`Finder::new`] will return a `Finder`. Namely, 77 | /// even when `Finder::is_available` is true, it is not guaranteed that a 78 | /// valid [`Pair`] can be found from the needle given. 79 | /// 80 | /// Note also that for the lifetime of a single program, if this returns 81 | /// true then it will always return true. 82 | #[inline] 83 | pub fn is_available() -> bool { 84 | // We used to gate on `cfg(target_feature = "simd128")` here, but 85 | // we've since required the feature to be enabled at compile time to 86 | // even include this module at all. Therefore, it is always enabled 87 | // in this context. See the linked issue for why this was changed. 88 | // 89 | // Ref: https://github.com/BurntSushi/memchr/issues/144 90 | true 91 | } 92 | 93 | /// Execute a search using wasm32 v128 vectors and routines. 94 | /// 95 | /// # Panics 96 | /// 97 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 98 | #[inline] 99 | pub fn find(&self, haystack: &[u8], needle: &[u8]) -> Option { 100 | self.find_impl(haystack, needle) 101 | } 102 | 103 | /// Execute a search using wasm32 v128 vectors and routines. 104 | /// 105 | /// # Panics 106 | /// 107 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 108 | #[inline] 109 | pub fn find_prefilter(&self, haystack: &[u8]) -> Option { 110 | self.find_prefilter_impl(haystack) 111 | } 112 | 113 | /// Execute a search using wasm32 v128 vectors and routines. 114 | /// 115 | /// # Panics 116 | /// 117 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 118 | /// 119 | /// # Safety 120 | /// 121 | /// (The target feature safety obligation is automatically fulfilled by 122 | /// virtue of being a method on `Finder`, which can only be constructed 123 | /// when it is safe to call `simd128` routines.) 124 | #[target_feature(enable = "simd128")] 125 | #[inline] 126 | fn find_impl(&self, haystack: &[u8], needle: &[u8]) -> Option { 127 | // SAFETY: The target feature safety obligation is automatically 128 | // fulfilled by virtue of being a method on `Finder`, which can only be 129 | // constructed when it is safe to call `simd128` routines. 130 | unsafe { self.0.find(haystack, needle) } 131 | } 132 | 133 | /// Execute a prefilter search using wasm32 v128 vectors and routines. 134 | /// 135 | /// # Panics 136 | /// 137 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 138 | /// 139 | /// # Safety 140 | /// 141 | /// (The target feature safety obligation is automatically fulfilled by 142 | /// virtue of being a method on `Finder`, which can only be constructed 143 | /// when it is safe to call `simd128` routines.) 144 | #[target_feature(enable = "simd128")] 145 | #[inline] 146 | fn find_prefilter_impl(&self, haystack: &[u8]) -> Option { 147 | // SAFETY: The target feature safety obligation is automatically 148 | // fulfilled by virtue of being a method on `Finder`, which can only be 149 | // constructed when it is safe to call `simd128` routines. 150 | unsafe { self.0.find_prefilter(haystack) } 151 | } 152 | 153 | /// Returns the pair of offsets (into the needle) used to check as a 154 | /// predicate before confirming whether a needle exists at a particular 155 | /// position. 156 | #[inline] 157 | pub fn pair(&self) -> &Pair { 158 | self.0.pair() 159 | } 160 | 161 | /// Returns the minimum haystack length that this `Finder` can search. 162 | /// 163 | /// Using a haystack with length smaller than this in a search will result 164 | /// in a panic. The reason for this restriction is that this finder is 165 | /// meant to be a low-level component that is part of a larger substring 166 | /// strategy. In that sense, it avoids trying to handle all cases and 167 | /// instead only handles the cases that it can handle very well. 168 | #[inline] 169 | pub fn min_haystack_len(&self) -> usize { 170 | self.0.min_haystack_len() 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | use super::*; 177 | 178 | fn find(haystack: &[u8], needle: &[u8]) -> Option> { 179 | let f = Finder::new(needle)?; 180 | if haystack.len() < f.min_haystack_len() { 181 | return None; 182 | } 183 | Some(f.find(haystack, needle)) 184 | } 185 | 186 | define_substring_forward_quickcheck!(find); 187 | 188 | #[test] 189 | fn forward_substring() { 190 | crate::tests::substring::Runner::new().fwd(find).run() 191 | } 192 | 193 | #[test] 194 | fn forward_packedpair() { 195 | fn find( 196 | haystack: &[u8], 197 | needle: &[u8], 198 | index1: u8, 199 | index2: u8, 200 | ) -> Option> { 201 | let pair = Pair::with_indices(needle, index1, index2)?; 202 | let f = Finder::with_pair(needle, pair)?; 203 | if haystack.len() < f.min_haystack_len() { 204 | return None; 205 | } 206 | Some(f.find(haystack, needle)) 207 | } 208 | crate::tests::packedpair::Runner::new().fwd(find).run() 209 | } 210 | 211 | #[test] 212 | fn forward_packedpair_prefilter() { 213 | fn find( 214 | haystack: &[u8], 215 | needle: &[u8], 216 | index1: u8, 217 | index2: u8, 218 | ) -> Option> { 219 | let pair = Pair::with_indices(needle, index1, index2)?; 220 | let f = Finder::with_pair(needle, pair)?; 221 | if haystack.len() < f.min_haystack_len() { 222 | return None; 223 | } 224 | Some(f.find_prefilter(haystack)) 225 | } 226 | crate::tests::packedpair::Runner::new().fwd(find).run() 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/arch/x86_64/avx2/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Algorithms for the `x86_64` target using 256-bit vectors via AVX2. 3 | */ 4 | 5 | pub mod memchr; 6 | pub mod packedpair; 7 | -------------------------------------------------------------------------------- /src/arch/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Vector algorithms for the `x86_64` target. 3 | */ 4 | 5 | pub mod avx2; 6 | pub mod sse2; 7 | 8 | pub(crate) mod memchr; 9 | -------------------------------------------------------------------------------- /src/arch/x86_64/sse2/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Algorithms for the `x86_64` target using 128-bit vectors via SSE2. 3 | */ 4 | 5 | pub mod memchr; 6 | pub mod packedpair; 7 | -------------------------------------------------------------------------------- /src/arch/x86_64/sse2/packedpair.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | A 128-bit vector implementation of the "packed pair" SIMD algorithm. 3 | 4 | The "packed pair" algorithm is based on the [generic SIMD] algorithm. The main 5 | difference is that it (by default) uses a background distribution of byte 6 | frequencies to heuristically select the pair of bytes to search for. 7 | 8 | [generic SIMD]: http://0x80.pl/articles/simd-strfind.html#first-and-last 9 | */ 10 | 11 | use core::arch::x86_64::__m128i; 12 | 13 | use crate::arch::{all::packedpair::Pair, generic::packedpair}; 14 | 15 | /// A "packed pair" finder that uses 128-bit vector operations. 16 | /// 17 | /// This finder picks two bytes that it believes have high predictive power 18 | /// for indicating an overall match of a needle. Depending on whether 19 | /// `Finder::find` or `Finder::find_prefilter` is used, it reports offsets 20 | /// where the needle matches or could match. In the prefilter case, candidates 21 | /// are reported whenever the [`Pair`] of bytes given matches. 22 | #[derive(Clone, Copy, Debug)] 23 | pub struct Finder(packedpair::Finder<__m128i>); 24 | 25 | impl Finder { 26 | /// Create a new pair searcher. The searcher returned can either report 27 | /// exact matches of `needle` or act as a prefilter and report candidate 28 | /// positions of `needle`. 29 | /// 30 | /// If SSE2 is unavailable in the current environment or if a [`Pair`] 31 | /// could not be constructed from the needle given, then `None` is 32 | /// returned. 33 | #[inline] 34 | pub fn new(needle: &[u8]) -> Option { 35 | Finder::with_pair(needle, Pair::new(needle)?) 36 | } 37 | 38 | /// Create a new "packed pair" finder using the pair of bytes given. 39 | /// 40 | /// This constructor permits callers to control precisely which pair of 41 | /// bytes is used as a predicate. 42 | /// 43 | /// If SSE2 is unavailable in the current environment, then `None` is 44 | /// returned. 45 | #[inline] 46 | pub fn with_pair(needle: &[u8], pair: Pair) -> Option { 47 | if Finder::is_available() { 48 | // SAFETY: we check that sse2 is available above. We are also 49 | // guaranteed to have needle.len() > 1 because we have a valid 50 | // Pair. 51 | unsafe { Some(Finder::with_pair_impl(needle, pair)) } 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | /// Create a new `Finder` specific to SSE2 vectors and routines. 58 | /// 59 | /// # Safety 60 | /// 61 | /// Same as the safety for `packedpair::Finder::new`, and callers must also 62 | /// ensure that SSE2 is available. 63 | #[target_feature(enable = "sse2")] 64 | #[inline] 65 | unsafe fn with_pair_impl(needle: &[u8], pair: Pair) -> Finder { 66 | let finder = packedpair::Finder::<__m128i>::new(needle, pair); 67 | Finder(finder) 68 | } 69 | 70 | /// Returns true when this implementation is available in the current 71 | /// environment. 72 | /// 73 | /// When this is true, it is guaranteed that [`Finder::with_pair`] will 74 | /// return a `Some` value. Similarly, when it is false, it is guaranteed 75 | /// that `Finder::with_pair` will return a `None` value. Notice that this 76 | /// does not guarantee that [`Finder::new`] will return a `Finder`. Namely, 77 | /// even when `Finder::is_available` is true, it is not guaranteed that a 78 | /// valid [`Pair`] can be found from the needle given. 79 | /// 80 | /// Note also that for the lifetime of a single program, if this returns 81 | /// true then it will always return true. 82 | #[inline] 83 | pub fn is_available() -> bool { 84 | #[cfg(not(target_feature = "sse2"))] 85 | { 86 | false 87 | } 88 | #[cfg(target_feature = "sse2")] 89 | { 90 | true 91 | } 92 | } 93 | 94 | /// Execute a search using SSE2 vectors and routines. 95 | /// 96 | /// # Panics 97 | /// 98 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 99 | #[inline] 100 | pub fn find(&self, haystack: &[u8], needle: &[u8]) -> Option { 101 | // SAFETY: Building a `Finder` means it's safe to call 'sse2' routines. 102 | unsafe { self.find_impl(haystack, needle) } 103 | } 104 | 105 | /// Run this finder on the given haystack as a prefilter. 106 | /// 107 | /// If a candidate match is found, then an offset where the needle *could* 108 | /// begin in the haystack is returned. 109 | /// 110 | /// # Panics 111 | /// 112 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 113 | #[inline] 114 | pub fn find_prefilter(&self, haystack: &[u8]) -> Option { 115 | // SAFETY: Building a `Finder` means it's safe to call 'sse2' routines. 116 | unsafe { self.find_prefilter_impl(haystack) } 117 | } 118 | 119 | /// Execute a search using SSE2 vectors and routines. 120 | /// 121 | /// # Panics 122 | /// 123 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 124 | /// 125 | /// # Safety 126 | /// 127 | /// (The target feature safety obligation is automatically fulfilled by 128 | /// virtue of being a method on `Finder`, which can only be constructed 129 | /// when it is safe to call `sse2` routines.) 130 | #[target_feature(enable = "sse2")] 131 | #[inline] 132 | unsafe fn find_impl( 133 | &self, 134 | haystack: &[u8], 135 | needle: &[u8], 136 | ) -> Option { 137 | self.0.find(haystack, needle) 138 | } 139 | 140 | /// Execute a prefilter search using SSE2 vectors and routines. 141 | /// 142 | /// # Panics 143 | /// 144 | /// When `haystack.len()` is less than [`Finder::min_haystack_len`]. 145 | /// 146 | /// # Safety 147 | /// 148 | /// (The target feature safety obligation is automatically fulfilled by 149 | /// virtue of being a method on `Finder`, which can only be constructed 150 | /// when it is safe to call `sse2` routines.) 151 | #[target_feature(enable = "sse2")] 152 | #[inline] 153 | unsafe fn find_prefilter_impl(&self, haystack: &[u8]) -> Option { 154 | self.0.find_prefilter(haystack) 155 | } 156 | 157 | /// Returns the pair of offsets (into the needle) used to check as a 158 | /// predicate before confirming whether a needle exists at a particular 159 | /// position. 160 | #[inline] 161 | pub fn pair(&self) -> &Pair { 162 | self.0.pair() 163 | } 164 | 165 | /// Returns the minimum haystack length that this `Finder` can search. 166 | /// 167 | /// Using a haystack with length smaller than this in a search will result 168 | /// in a panic. The reason for this restriction is that this finder is 169 | /// meant to be a low-level component that is part of a larger substring 170 | /// strategy. In that sense, it avoids trying to handle all cases and 171 | /// instead only handles the cases that it can handle very well. 172 | #[inline] 173 | pub fn min_haystack_len(&self) -> usize { 174 | self.0.min_haystack_len() 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod tests { 180 | use super::*; 181 | 182 | fn find(haystack: &[u8], needle: &[u8]) -> Option> { 183 | let f = Finder::new(needle)?; 184 | if haystack.len() < f.min_haystack_len() { 185 | return None; 186 | } 187 | Some(f.find(haystack, needle)) 188 | } 189 | 190 | define_substring_forward_quickcheck!(find); 191 | 192 | #[test] 193 | fn forward_substring() { 194 | crate::tests::substring::Runner::new().fwd(find).run() 195 | } 196 | 197 | #[test] 198 | fn forward_packedpair() { 199 | fn find( 200 | haystack: &[u8], 201 | needle: &[u8], 202 | index1: u8, 203 | index2: u8, 204 | ) -> Option> { 205 | let pair = Pair::with_indices(needle, index1, index2)?; 206 | let f = Finder::with_pair(needle, pair)?; 207 | if haystack.len() < f.min_haystack_len() { 208 | return None; 209 | } 210 | Some(f.find(haystack, needle)) 211 | } 212 | crate::tests::packedpair::Runner::new().fwd(find).run() 213 | } 214 | 215 | #[test] 216 | fn forward_packedpair_prefilter() { 217 | fn find( 218 | haystack: &[u8], 219 | needle: &[u8], 220 | index1: u8, 221 | index2: u8, 222 | ) -> Option> { 223 | let pair = Pair::with_indices(needle, index1, index2)?; 224 | let f = Finder::with_pair(needle, pair)?; 225 | if haystack.len() < f.min_haystack_len() { 226 | return None; 227 | } 228 | Some(f.find_prefilter(haystack)) 229 | } 230 | crate::tests::packedpair::Runner::new().fwd(find).run() 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/cow.rs: -------------------------------------------------------------------------------- 1 | use core::ops; 2 | 3 | /// A specialized copy-on-write byte string. 4 | /// 5 | /// The purpose of this type is to permit usage of a "borrowed or owned 6 | /// byte string" in a way that keeps std/no-std compatibility. That is, in 7 | /// no-std/alloc mode, this type devolves into a simple &[u8] with no owned 8 | /// variant available. We can't just use a plain Cow because Cow is not in 9 | /// core. 10 | #[derive(Clone, Debug)] 11 | pub struct CowBytes<'a>(Imp<'a>); 12 | 13 | // N.B. We don't use alloc::borrow::Cow here since we can get away with a 14 | // Box<[u8]> for our use case, which is 1/3 smaller than the Vec that 15 | // a Cow<[u8]> would use. 16 | #[cfg(feature = "alloc")] 17 | #[derive(Clone, Debug)] 18 | enum Imp<'a> { 19 | Borrowed(&'a [u8]), 20 | Owned(alloc::boxed::Box<[u8]>), 21 | } 22 | 23 | #[cfg(not(feature = "alloc"))] 24 | #[derive(Clone, Debug)] 25 | struct Imp<'a>(&'a [u8]); 26 | 27 | impl<'a> ops::Deref for CowBytes<'a> { 28 | type Target = [u8]; 29 | 30 | #[inline(always)] 31 | fn deref(&self) -> &[u8] { 32 | self.as_slice() 33 | } 34 | } 35 | 36 | impl<'a> CowBytes<'a> { 37 | /// Create a new borrowed CowBytes. 38 | #[inline(always)] 39 | pub(crate) fn new>(bytes: &'a B) -> CowBytes<'a> { 40 | CowBytes(Imp::new(bytes.as_ref())) 41 | } 42 | 43 | /// Create a new owned CowBytes. 44 | #[cfg(feature = "alloc")] 45 | #[inline(always)] 46 | fn new_owned(bytes: alloc::boxed::Box<[u8]>) -> CowBytes<'static> { 47 | CowBytes(Imp::Owned(bytes)) 48 | } 49 | 50 | /// Return a borrowed byte string, regardless of whether this is an owned 51 | /// or borrowed byte string internally. 52 | #[inline(always)] 53 | pub(crate) fn as_slice(&self) -> &[u8] { 54 | self.0.as_slice() 55 | } 56 | 57 | /// Return an owned version of this copy-on-write byte string. 58 | /// 59 | /// If this is already an owned byte string internally, then this is a 60 | /// no-op. Otherwise, the internal byte string is copied. 61 | #[cfg(feature = "alloc")] 62 | #[inline(always)] 63 | pub(crate) fn into_owned(self) -> CowBytes<'static> { 64 | match self.0 { 65 | Imp::Borrowed(b) => { 66 | CowBytes::new_owned(alloc::boxed::Box::from(b)) 67 | } 68 | Imp::Owned(b) => CowBytes::new_owned(b), 69 | } 70 | } 71 | } 72 | 73 | impl<'a> Imp<'a> { 74 | #[inline(always)] 75 | pub fn new(bytes: &'a [u8]) -> Imp<'a> { 76 | #[cfg(feature = "alloc")] 77 | { 78 | Imp::Borrowed(bytes) 79 | } 80 | #[cfg(not(feature = "alloc"))] 81 | { 82 | Imp(bytes) 83 | } 84 | } 85 | 86 | #[cfg(feature = "alloc")] 87 | #[inline(always)] 88 | pub fn as_slice(&self) -> &[u8] { 89 | #[cfg(feature = "alloc")] 90 | { 91 | match self { 92 | Imp::Owned(ref x) => x, 93 | Imp::Borrowed(x) => x, 94 | } 95 | } 96 | #[cfg(not(feature = "alloc"))] 97 | { 98 | self.0 99 | } 100 | } 101 | 102 | #[cfg(not(feature = "alloc"))] 103 | #[inline(always)] 104 | pub fn as_slice(&self) -> &[u8] { 105 | self.0 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ext.rs: -------------------------------------------------------------------------------- 1 | /// A trait for adding some helper routines to pointers. 2 | pub(crate) trait Pointer { 3 | /// Returns the distance, in units of `T`, between `self` and `origin`. 4 | /// 5 | /// # Safety 6 | /// 7 | /// Same as `ptr::offset_from` in addition to `self >= origin`. 8 | unsafe fn distance(self, origin: Self) -> usize; 9 | 10 | /// Casts this pointer to `usize`. 11 | /// 12 | /// Callers should not convert the `usize` back to a pointer if at all 13 | /// possible. (And if you believe it's necessary, open an issue to discuss 14 | /// why. Otherwise, it has the potential to violate pointer provenance.) 15 | /// The purpose of this function is just to be able to do arithmetic, i.e., 16 | /// computing offsets or alignments. 17 | fn as_usize(self) -> usize; 18 | } 19 | 20 | impl Pointer for *const T { 21 | unsafe fn distance(self, origin: *const T) -> usize { 22 | // TODO: Replace with `ptr::sub_ptr` once stabilized. 23 | usize::try_from(self.offset_from(origin)).unwrap_unchecked() 24 | } 25 | 26 | fn as_usize(self) -> usize { 27 | self as usize 28 | } 29 | } 30 | 31 | impl Pointer for *mut T { 32 | unsafe fn distance(self, origin: *mut T) -> usize { 33 | (self as *const T).distance(origin as *const T) 34 | } 35 | 36 | fn as_usize(self) -> usize { 37 | (self as *const T).as_usize() 38 | } 39 | } 40 | 41 | /// A trait for adding some helper routines to raw bytes. 42 | #[cfg(test)] 43 | pub(crate) trait Byte { 44 | /// Converts this byte to a `char` if it's ASCII. Otherwise panics. 45 | fn to_char(self) -> char; 46 | } 47 | 48 | #[cfg(test)] 49 | impl Byte for u8 { 50 | fn to_char(self) -> char { 51 | assert!(self.is_ascii()); 52 | char::from(self) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This library provides heavily optimized routines for string search primitives. 3 | 4 | # Overview 5 | 6 | This section gives a brief high level overview of what this crate offers. 7 | 8 | * The top-level module provides routines for searching for 1, 2 or 3 bytes 9 | in the forward or reverse direction. When searching for more than one byte, 10 | positions are considered a match if the byte at that position matches any 11 | of the bytes. 12 | * The [`memmem`] sub-module provides forward and reverse substring search 13 | routines. 14 | 15 | In all such cases, routines operate on `&[u8]` without regard to encoding. This 16 | is exactly what you want when searching either UTF-8 or arbitrary bytes. 17 | 18 | # Example: using `memchr` 19 | 20 | This example shows how to use `memchr` to find the first occurrence of `z` in 21 | a haystack: 22 | 23 | ``` 24 | use memchr::memchr; 25 | 26 | let haystack = b"foo bar baz quuz"; 27 | assert_eq!(Some(10), memchr(b'z', haystack)); 28 | ``` 29 | 30 | # Example: matching one of three possible bytes 31 | 32 | This examples shows how to use `memrchr3` to find occurrences of `a`, `b` or 33 | `c`, starting at the end of the haystack. 34 | 35 | ``` 36 | use memchr::memchr3_iter; 37 | 38 | let haystack = b"xyzaxyzbxyzc"; 39 | 40 | let mut it = memchr3_iter(b'a', b'b', b'c', haystack).rev(); 41 | assert_eq!(Some(11), it.next()); 42 | assert_eq!(Some(7), it.next()); 43 | assert_eq!(Some(3), it.next()); 44 | assert_eq!(None, it.next()); 45 | ``` 46 | 47 | # Example: iterating over substring matches 48 | 49 | This example shows how to use the [`memmem`] sub-module to find occurrences of 50 | a substring in a haystack. 51 | 52 | ``` 53 | use memchr::memmem; 54 | 55 | let haystack = b"foo bar foo baz foo"; 56 | 57 | let mut it = memmem::find_iter(haystack, "foo"); 58 | assert_eq!(Some(0), it.next()); 59 | assert_eq!(Some(8), it.next()); 60 | assert_eq!(Some(16), it.next()); 61 | assert_eq!(None, it.next()); 62 | ``` 63 | 64 | # Example: repeating a search for the same needle 65 | 66 | It may be possible for the overhead of constructing a substring searcher to be 67 | measurable in some workloads. In cases where the same needle is used to search 68 | many haystacks, it is possible to do construction once and thus to avoid it for 69 | subsequent searches. This can be done with a [`memmem::Finder`]: 70 | 71 | ``` 72 | use memchr::memmem; 73 | 74 | let finder = memmem::Finder::new("foo"); 75 | 76 | assert_eq!(Some(4), finder.find(b"baz foo quux")); 77 | assert_eq!(None, finder.find(b"quux baz bar")); 78 | ``` 79 | 80 | # Why use this crate? 81 | 82 | At first glance, the APIs provided by this crate might seem weird. Why provide 83 | a dedicated routine like `memchr` for something that could be implemented 84 | clearly and trivially in one line: 85 | 86 | ``` 87 | fn memchr(needle: u8, haystack: &[u8]) -> Option { 88 | haystack.iter().position(|&b| b == needle) 89 | } 90 | ``` 91 | 92 | Or similarly, why does this crate provide substring search routines when Rust's 93 | core library already provides them? 94 | 95 | ``` 96 | fn search(haystack: &str, needle: &str) -> Option { 97 | haystack.find(needle) 98 | } 99 | ``` 100 | 101 | The primary reason for both of them to exist is performance. When it comes to 102 | performance, at a high level at least, there are two primary ways to look at 103 | it: 104 | 105 | * **Throughput**: For this, think about it as, "given some very large haystack 106 | and a byte that never occurs in that haystack, how long does it take to 107 | search through it and determine that it, in fact, does not occur?" 108 | * **Latency**: For this, think about it as, "given a tiny haystack---just a 109 | few bytes---how long does it take to determine if a byte is in it?" 110 | 111 | The `memchr` routine in this crate has _slightly_ worse latency than the 112 | solution presented above, however, its throughput can easily be over an 113 | order of magnitude faster. This is a good general purpose trade off to make. 114 | You rarely lose, but often gain big. 115 | 116 | **NOTE:** The name `memchr` comes from the corresponding routine in `libc`. A 117 | key advantage of using this library is that its performance is not tied to its 118 | quality of implementation in the `libc` you happen to be using, which can vary 119 | greatly from platform to platform. 120 | 121 | But what about substring search? This one is a bit more complicated. The 122 | primary reason for its existence is still indeed performance, but it's also 123 | useful because Rust's core library doesn't actually expose any substring 124 | search routine on arbitrary bytes. The only substring search routine that 125 | exists works exclusively on valid UTF-8. 126 | 127 | So if you have valid UTF-8, is there a reason to use this over the standard 128 | library substring search routine? Yes. This routine is faster on almost every 129 | metric, including latency. The natural question then, is why isn't this 130 | implementation in the standard library, even if only for searching on UTF-8? 131 | The reason is that the implementation details for using SIMD in the standard 132 | library haven't quite been worked out yet. 133 | 134 | **NOTE:** Currently, only `x86_64`, `wasm32` and `aarch64` targets have vector 135 | accelerated implementations of `memchr` (and friends) and `memmem`. 136 | 137 | # Crate features 138 | 139 | * **std** - When enabled (the default), this will permit features specific to 140 | the standard library. Currently, the only thing used from the standard library 141 | is runtime SIMD CPU feature detection. This means that this feature must be 142 | enabled to get AVX2 accelerated routines on `x86_64` targets without enabling 143 | the `avx2` feature at compile time, for example. When `std` is not enabled, 144 | this crate will still attempt to use SSE2 accelerated routines on `x86_64`. It 145 | will also use AVX2 accelerated routines when the `avx2` feature is enabled at 146 | compile time. In general, enable this feature if you can. 147 | * **alloc** - When enabled (the default), APIs in this crate requiring some 148 | kind of allocation will become available. For example, the 149 | [`memmem::Finder::into_owned`](crate::memmem::Finder::into_owned) API and the 150 | [`arch::all::shiftor`](crate::arch::all::shiftor) substring search 151 | implementation. Otherwise, this crate is designed from the ground up to be 152 | usable in core-only contexts, so the `alloc` feature doesn't add much 153 | currently. Notably, disabling `std` but enabling `alloc` will **not** result 154 | in the use of AVX2 on `x86_64` targets unless the `avx2` feature is enabled 155 | at compile time. (With `std` enabled, AVX2 can be used even without the `avx2` 156 | feature enabled at compile time by way of runtime CPU feature detection.) 157 | * **logging** - When enabled (disabled by default), the `log` crate is used 158 | to emit log messages about what kinds of `memchr` and `memmem` algorithms 159 | are used. Namely, both `memchr` and `memmem` have a number of different 160 | implementation choices depending on the target and CPU, and the log messages 161 | can help show what specific implementations are being used. Generally, this is 162 | useful for debugging performance issues. 163 | * **libc** - **DEPRECATED**. Previously, this enabled the use of the target's 164 | `memchr` function from whatever `libc` was linked into the program. This 165 | feature is now a no-op because this crate's implementation of `memchr` should 166 | now be sufficiently fast on a number of platforms that `libc` should no longer 167 | be needed. (This feature is somewhat of a holdover from this crate's origins. 168 | Originally, this crate was literally just a safe wrapper function around the 169 | `memchr` function from `libc`.) 170 | */ 171 | 172 | #![deny(missing_docs)] 173 | #![no_std] 174 | // It's just not worth trying to squash all dead code warnings. Pretty 175 | // unfortunate IMO. Not really sure how to fix this other than to either 176 | // live with it or sprinkle a whole mess of `cfg` annotations everywhere. 177 | #![cfg_attr( 178 | not(any( 179 | all(target_arch = "x86_64", target_feature = "sse2"), 180 | all(target_arch = "wasm32", target_feature = "simd128"), 181 | target_arch = "aarch64", 182 | )), 183 | allow(dead_code) 184 | )] 185 | // Same deal for miri. 186 | #![cfg_attr(miri, allow(dead_code, unused_macros))] 187 | 188 | // Supporting 8-bit (or others) would be fine. If you need it, please submit a 189 | // bug report at https://github.com/BurntSushi/memchr 190 | #[cfg(not(any( 191 | target_pointer_width = "16", 192 | target_pointer_width = "32", 193 | target_pointer_width = "64" 194 | )))] 195 | compile_error!("memchr currently not supported on non-{16,32,64}"); 196 | 197 | #[cfg(any(test, feature = "std"))] 198 | extern crate std; 199 | 200 | #[cfg(any(test, feature = "alloc"))] 201 | extern crate alloc; 202 | 203 | pub use crate::memchr::{ 204 | memchr, memchr2, memchr2_iter, memchr3, memchr3_iter, memchr_iter, 205 | memrchr, memrchr2, memrchr2_iter, memrchr3, memrchr3_iter, memrchr_iter, 206 | Memchr, Memchr2, Memchr3, 207 | }; 208 | 209 | #[macro_use] 210 | mod macros; 211 | 212 | #[cfg(test)] 213 | #[macro_use] 214 | mod tests; 215 | 216 | pub mod arch; 217 | mod cow; 218 | mod ext; 219 | mod memchr; 220 | pub mod memmem; 221 | mod vector; 222 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // Some feature combinations result in some of these macros never being used. 2 | // Which is fine. Just squash the warnings. 3 | #![allow(unused_macros)] 4 | 5 | macro_rules! log { 6 | ($($tt:tt)*) => { 7 | #[cfg(feature = "logging")] 8 | { 9 | $($tt)* 10 | } 11 | } 12 | } 13 | 14 | macro_rules! debug { 15 | ($($tt:tt)*) => { log!(log::debug!($($tt)*)) } 16 | } 17 | 18 | macro_rules! trace { 19 | ($($tt:tt)*) => { log!(log::trace!($($tt)*)) } 20 | } 21 | -------------------------------------------------------------------------------- /src/tests/memchr/naive.rs: -------------------------------------------------------------------------------- 1 | pub(crate) fn memchr(n1: u8, haystack: &[u8]) -> Option { 2 | haystack.iter().position(|&b| b == n1) 3 | } 4 | 5 | pub(crate) fn memchr2(n1: u8, n2: u8, haystack: &[u8]) -> Option { 6 | haystack.iter().position(|&b| b == n1 || b == n2) 7 | } 8 | 9 | pub(crate) fn memchr3( 10 | n1: u8, 11 | n2: u8, 12 | n3: u8, 13 | haystack: &[u8], 14 | ) -> Option { 15 | haystack.iter().position(|&b| b == n1 || b == n2 || b == n3) 16 | } 17 | 18 | pub(crate) fn memrchr(n1: u8, haystack: &[u8]) -> Option { 19 | haystack.iter().rposition(|&b| b == n1) 20 | } 21 | 22 | pub(crate) fn memrchr2(n1: u8, n2: u8, haystack: &[u8]) -> Option { 23 | haystack.iter().rposition(|&b| b == n1 || b == n2) 24 | } 25 | 26 | pub(crate) fn memrchr3( 27 | n1: u8, 28 | n2: u8, 29 | n3: u8, 30 | haystack: &[u8], 31 | ) -> Option { 32 | haystack.iter().rposition(|&b| b == n1 || b == n2 || b == n3) 33 | } 34 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub(crate) mod memchr; 3 | pub(crate) mod packedpair; 4 | #[macro_use] 5 | pub(crate) mod substring; 6 | 7 | // For debugging, particularly in CI, print out the byte order of the current 8 | // target. 9 | #[test] 10 | fn byte_order() { 11 | #[cfg(target_endian = "little")] 12 | std::eprintln!("LITTLE ENDIAN"); 13 | #[cfg(target_endian = "big")] 14 | std::eprintln!("BIG ENDIAN"); 15 | } 16 | -------------------------------------------------------------------------------- /src/tests/packedpair.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, vec, vec::Vec}; 2 | 3 | /// A set of "packed pair" test seeds. Each seed serves as the base for the 4 | /// generation of many other tests. In essence, the seed captures the pair of 5 | /// bytes we used for a predicate and first byte among our needle. The tests 6 | /// generated from each seed essentially vary the length of the needle and 7 | /// haystack, while using the rare/first byte configuration from the seed. 8 | /// 9 | /// The purpose of this is to test many different needle/haystack lengths. 10 | /// In particular, some of the vector optimizations might only have bugs 11 | /// in haystacks of a certain size. 12 | const SEEDS: &[Seed] = &[ 13 | // Why not use different 'first' bytes? It seemed like a good idea to be 14 | // able to configure it, but when I wrote the test generator below, it 15 | // didn't seem necessary to use for reasons that I forget. 16 | Seed { first: b'x', index1: b'y', index2: b'z' }, 17 | Seed { first: b'x', index1: b'x', index2: b'z' }, 18 | Seed { first: b'x', index1: b'y', index2: b'x' }, 19 | Seed { first: b'x', index1: b'x', index2: b'x' }, 20 | Seed { first: b'x', index1: b'y', index2: b'y' }, 21 | ]; 22 | 23 | /// Runs a host of "packed pair" search tests. 24 | /// 25 | /// These tests specifically look for the occurrence of a possible substring 26 | /// match based on a pair of bytes matching at the right offsets. 27 | pub(crate) struct Runner { 28 | fwd: Option< 29 | Box< 30 | dyn FnMut(&[u8], &[u8], u8, u8) -> Option> + 'static, 31 | >, 32 | >, 33 | } 34 | 35 | impl Runner { 36 | /// Create a new test runner for "packed pair" substring search. 37 | pub(crate) fn new() -> Runner { 38 | Runner { fwd: None } 39 | } 40 | 41 | /// Run all tests. This panics on the first failure. 42 | /// 43 | /// If the implementation being tested returns `None` for a particular 44 | /// haystack/needle combination, then that test is skipped. 45 | /// 46 | /// This runs tests on both the forward and reverse implementations given. 47 | /// If either (or both) are missing, then tests for that implementation are 48 | /// skipped. 49 | pub(crate) fn run(self) { 50 | if let Some(mut fwd) = self.fwd { 51 | for seed in SEEDS.iter() { 52 | for t in seed.generate() { 53 | match fwd(&t.haystack, &t.needle, t.index1, t.index2) { 54 | None => continue, 55 | Some(result) => { 56 | assert_eq!( 57 | t.fwd, result, 58 | "FORWARD, needle: {:?}, haystack: {:?}, \ 59 | index1: {:?}, index2: {:?}", 60 | t.needle, t.haystack, t.index1, t.index2, 61 | ) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | /// Set the implementation for forward "packed pair" substring search. 70 | /// 71 | /// If the closure returns `None`, then it is assumed that the given 72 | /// test cannot be applied to the particular implementation and it is 73 | /// skipped. For example, if a particular implementation only supports 74 | /// needles or haystacks for some minimum length. 75 | /// 76 | /// If this is not set, then forward "packed pair" search is not tested. 77 | pub(crate) fn fwd( 78 | mut self, 79 | search: impl FnMut(&[u8], &[u8], u8, u8) -> Option> + 'static, 80 | ) -> Runner { 81 | self.fwd = Some(Box::new(search)); 82 | self 83 | } 84 | } 85 | 86 | /// A test that represents the input and expected output to a "packed pair" 87 | /// search function. The test should be able to run with any "packed pair" 88 | /// implementation and get the expected output. 89 | struct Test { 90 | haystack: Vec, 91 | needle: Vec, 92 | index1: u8, 93 | index2: u8, 94 | fwd: Option, 95 | } 96 | 97 | impl Test { 98 | /// Create a new "packed pair" test from a seed and some given offsets to 99 | /// the pair of bytes to use as a predicate in the seed's needle. 100 | /// 101 | /// If a valid test could not be constructed, then None is returned. 102 | /// (Currently, we take the approach of massaging tests to be valid 103 | /// instead of rejecting them outright.) 104 | fn new( 105 | seed: Seed, 106 | index1: usize, 107 | index2: usize, 108 | haystack_len: usize, 109 | needle_len: usize, 110 | fwd: Option, 111 | ) -> Option { 112 | let mut index1: u8 = index1.try_into().unwrap(); 113 | let mut index2: u8 = index2.try_into().unwrap(); 114 | // The '#' byte is never used in a haystack (unless we're expecting 115 | // a match), while the '@' byte is never used in a needle. 116 | let mut haystack = vec![b'@'; haystack_len]; 117 | let mut needle = vec![b'#'; needle_len]; 118 | needle[0] = seed.first; 119 | needle[index1 as usize] = seed.index1; 120 | needle[index2 as usize] = seed.index2; 121 | // If we're expecting a match, then make sure the needle occurs 122 | // in the haystack at the expected position. 123 | if let Some(i) = fwd { 124 | haystack[i..i + needle.len()].copy_from_slice(&needle); 125 | } 126 | // If the operations above lead to rare offsets pointing to the 127 | // non-first occurrence of a byte, then adjust it. This might lead 128 | // to redundant tests, but it's simpler than trying to change the 129 | // generation process I think. 130 | if let Some(i) = crate::memchr(seed.index1, &needle) { 131 | index1 = u8::try_from(i).unwrap(); 132 | } 133 | if let Some(i) = crate::memchr(seed.index2, &needle) { 134 | index2 = u8::try_from(i).unwrap(); 135 | } 136 | Some(Test { haystack, needle, index1, index2, fwd }) 137 | } 138 | } 139 | 140 | /// Data that describes a single prefilter test seed. 141 | #[derive(Clone, Copy)] 142 | struct Seed { 143 | first: u8, 144 | index1: u8, 145 | index2: u8, 146 | } 147 | 148 | impl Seed { 149 | const NEEDLE_LENGTH_LIMIT: usize = { 150 | #[cfg(not(miri))] 151 | { 152 | 33 153 | } 154 | #[cfg(miri)] 155 | { 156 | 5 157 | } 158 | }; 159 | 160 | const HAYSTACK_LENGTH_LIMIT: usize = { 161 | #[cfg(not(miri))] 162 | { 163 | 65 164 | } 165 | #[cfg(miri)] 166 | { 167 | 8 168 | } 169 | }; 170 | 171 | /// Generate a series of prefilter tests from this seed. 172 | fn generate(self) -> impl Iterator { 173 | let len_start = 2; 174 | // The iterator below generates *a lot* of tests. The number of 175 | // tests was chosen somewhat empirically to be "bearable" when 176 | // running the test suite. 177 | // 178 | // We use an iterator here because the collective haystacks of all 179 | // these test cases add up to enough memory to OOM a conservative 180 | // sandbox or a small laptop. 181 | (len_start..=Seed::NEEDLE_LENGTH_LIMIT).flat_map(move |needle_len| { 182 | let index_start = len_start - 1; 183 | (index_start..needle_len).flat_map(move |index1| { 184 | (index1..needle_len).flat_map(move |index2| { 185 | (needle_len..=Seed::HAYSTACK_LENGTH_LIMIT).flat_map( 186 | move |haystack_len| { 187 | Test::new( 188 | self, 189 | index1, 190 | index2, 191 | haystack_len, 192 | needle_len, 193 | None, 194 | ) 195 | .into_iter() 196 | .chain( 197 | (0..=(haystack_len - needle_len)).flat_map( 198 | move |output| { 199 | Test::new( 200 | self, 201 | index1, 202 | index2, 203 | haystack_len, 204 | needle_len, 205 | Some(output), 206 | ) 207 | }, 208 | ), 209 | ) 210 | }, 211 | ) 212 | }) 213 | }) 214 | }) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/tests/substring/naive.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module defines "naive" implementations of substring search. 3 | 4 | These are sometimes useful to compare with "real" substring implementations. 5 | The idea is that they are so simple that they are unlikely to be incorrect. 6 | */ 7 | 8 | /// Naively search forwards for the given needle in the given haystack. 9 | pub(crate) fn find(haystack: &[u8], needle: &[u8]) -> Option { 10 | let end = haystack.len().checked_sub(needle.len()).map_or(0, |i| i + 1); 11 | for i in 0..end { 12 | if needle == &haystack[i..i + needle.len()] { 13 | return Some(i); 14 | } 15 | } 16 | None 17 | } 18 | 19 | /// Naively search in reverse for the given needle in the given haystack. 20 | pub(crate) fn rfind(haystack: &[u8], needle: &[u8]) -> Option { 21 | let end = haystack.len().checked_sub(needle.len()).map_or(0, |i| i + 1); 22 | for i in (0..end).rev() { 23 | if needle == &haystack[i..i + needle.len()] { 24 | return Some(i); 25 | } 26 | } 27 | None 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use crate::tests::substring; 33 | 34 | use super::*; 35 | 36 | #[test] 37 | fn forward() { 38 | substring::Runner::new().fwd(|h, n| Some(find(h, n))).run() 39 | } 40 | 41 | #[test] 42 | fn reverse() { 43 | substring::Runner::new().rev(|h, n| Some(rfind(h, n))).run() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/tests/substring/prop.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This module defines a few quickcheck properties for substring search. 3 | 4 | It also provides a forward and reverse macro for conveniently defining 5 | quickcheck tests that run these properties over any substring search 6 | implementation. 7 | */ 8 | 9 | use crate::tests::substring::naive; 10 | 11 | /// $fwd is a `impl FnMut(haystack, needle) -> Option>`. When the 12 | /// routine returns `None`, then it's skipped, which is useful for substring 13 | /// implementations that don't work for all inputs. 14 | #[macro_export] 15 | macro_rules! define_substring_forward_quickcheck { 16 | ($fwd:expr) => { 17 | #[cfg(not(miri))] 18 | quickcheck::quickcheck! { 19 | fn qc_fwd_prefix_is_substring(bs: alloc::vec::Vec) -> bool { 20 | crate::tests::substring::prop::prefix_is_substring(&bs, $fwd) 21 | } 22 | 23 | fn qc_fwd_suffix_is_substring(bs: alloc::vec::Vec) -> bool { 24 | crate::tests::substring::prop::suffix_is_substring(&bs, $fwd) 25 | } 26 | 27 | fn qc_fwd_matches_naive( 28 | haystack: alloc::vec::Vec, 29 | needle: alloc::vec::Vec 30 | ) -> bool { 31 | crate::tests::substring::prop::same_as_naive( 32 | false, 33 | &haystack, 34 | &needle, 35 | $fwd, 36 | ) 37 | } 38 | } 39 | }; 40 | } 41 | 42 | /// $rev is a `impl FnMut(haystack, needle) -> Option>`. When the 43 | /// routine returns `None`, then it's skipped, which is useful for substring 44 | /// implementations that don't work for all inputs. 45 | #[macro_export] 46 | macro_rules! define_substring_reverse_quickcheck { 47 | ($rev:expr) => { 48 | #[cfg(not(miri))] 49 | quickcheck::quickcheck! { 50 | fn qc_rev_prefix_is_substring(bs: alloc::vec::Vec) -> bool { 51 | crate::tests::substring::prop::prefix_is_substring(&bs, $rev) 52 | } 53 | 54 | fn qc_rev_suffix_is_substring(bs: alloc::vec::Vec) -> bool { 55 | crate::tests::substring::prop::suffix_is_substring(&bs, $rev) 56 | } 57 | 58 | fn qc_rev_matches_naive( 59 | haystack: alloc::vec::Vec, 60 | needle: alloc::vec::Vec 61 | ) -> bool { 62 | crate::tests::substring::prop::same_as_naive( 63 | true, 64 | &haystack, 65 | &needle, 66 | $rev, 67 | ) 68 | } 69 | } 70 | }; 71 | } 72 | 73 | /// Check that every prefix of the given byte string is a substring. 74 | pub(crate) fn prefix_is_substring( 75 | bs: &[u8], 76 | mut search: impl FnMut(&[u8], &[u8]) -> Option>, 77 | ) -> bool { 78 | for i in 0..bs.len().saturating_sub(1) { 79 | let prefix = &bs[..i]; 80 | let result = match search(bs, prefix) { 81 | None => continue, 82 | Some(result) => result, 83 | }; 84 | if !result.is_some() { 85 | return false; 86 | } 87 | } 88 | true 89 | } 90 | 91 | /// Check that every suffix of the given byte string is a substring. 92 | pub(crate) fn suffix_is_substring( 93 | bs: &[u8], 94 | mut search: impl FnMut(&[u8], &[u8]) -> Option>, 95 | ) -> bool { 96 | for i in 0..bs.len().saturating_sub(1) { 97 | let suffix = &bs[i..]; 98 | let result = match search(bs, suffix) { 99 | None => continue, 100 | Some(result) => result, 101 | }; 102 | if !result.is_some() { 103 | return false; 104 | } 105 | } 106 | true 107 | } 108 | 109 | /// Check that naive substring search matches the result of the given search 110 | /// algorithm. 111 | pub(crate) fn same_as_naive( 112 | reverse: bool, 113 | haystack: &[u8], 114 | needle: &[u8], 115 | mut search: impl FnMut(&[u8], &[u8]) -> Option>, 116 | ) -> bool { 117 | let result = match search(haystack, needle) { 118 | None => return true, 119 | Some(result) => result, 120 | }; 121 | if reverse { 122 | result == naive::rfind(haystack, needle) 123 | } else { 124 | result == naive::find(haystack, needle) 125 | } 126 | } 127 | --------------------------------------------------------------------------------