Crates.io | abin |
lib.rs | abin |
version | 0.1.6 |
source | src |
created_at | 2020-10-06 09:12:11.221223 |
updated_at | 2020-10-08 08:34:16.984211 |
description | A library for working with binaries and strings. The library tries to avoid heap-allocations / memory-copy whenever possible by automatically choosing a reasonable strategy: stack for small binaries; static-lifetime-binary or reference-counting. |
homepage | https://github.com/cronosun/abin/ |
repository | https://github.com/cronosun/abin/ |
max_upload_size | |
id | 296569 |
size | 249,700 |
A library for working with binaries and strings. The library tries to avoid heap-allocations / memory-copy whenever possible by automatically choosing a reasonable strategy: stack for small binaries; static-lifetime-binary or reference-counting. It's easy to use (no lifetimes; the binary type is sized), Send + Sync
is optional (thus no synchronization overhead), provides optional serde support and has a similar API for strings and binaries. Custom binary/string types can be implemented for fine-tuning.
Libraries that provide similar functionality:
Licensed under either of
at your option.
[dependencies]
abin = "*"
use std::iter::FromIterator;
use std::ops::Deref;
use abin::{AnyBin, AnyStr, Bin, BinFactory, NewBin, NewStr, Str, StrFactory};
#[test]
fn usage_basics() {
// static binary / static string
let static_bin: Bin = NewBin::from_static("I'm a static binary, hello!".as_bytes());
let static_str: Str = NewStr::from_static("I'm a static binary, hello!");
assert_eq!(&static_bin, static_str.as_bin());
assert_eq!(static_str.as_str(), "I'm a static binary, hello!");
// non-static (but small enough to be stored on the stack)
let hello_bin: Bin = NewBin::from_iter([72u8, 101u8, 108u8, 108u8, 111u8].iter().copied());
let hello_str: Str = NewStr::copy_from_str("Hello");
assert_eq!(&hello_bin, hello_str.as_bin());
assert_eq!(hello_str.as_ref() as &str, "Hello");
// operations for binaries / strings
// length (number of bytes / number of utf-8 bytes)
assert_eq!(5, hello_bin.len());
assert_eq!(5, hello_str.len());
// is_empty
assert_eq!(false, hello_bin.is_empty());
assert_eq!(false, hello_str.is_empty());
// as_slice / as_str / deref / as_bin
assert_eq!(&[72u8, 101u8, 108u8, 108u8, 111u8], hello_bin.as_slice());
assert_eq!("Hello", hello_str.as_str());
assert_eq!("Hello", hello_str.deref());
assert_eq!(&hello_bin, hello_str.as_bin());
// slice
assert_eq!(
NewBin::from_static(&[72u8, 101u8]),
hello_bin.slice(0..2).unwrap()
);
assert_eq!(NewStr::from_static("He"), hello_str.slice(0..2).unwrap());
// clone
assert_eq!(hello_bin.clone(), hello_bin);
assert_eq!(hello_str.clone(), hello_str);
// compare
assert!(NewBin::from_static(&[255u8]) > hello_bin);
assert!(NewStr::from_static("Z") > hello_str);
// convert string into binary and binary into string
let hello_bin_from_str: Bin = hello_str.clone().into_bin();
assert_eq!(hello_bin_from_str, hello_bin);
let hello_str_from_bin: Str = AnyStr::from_utf8(hello_bin.clone()).expect("invalid utf8!");
assert_eq!(hello_str_from_bin, hello_str);
// convert into Vec<u8> / String
assert_eq!(
Vec::from_iter([72u8, 101u8, 108u8, 108u8, 111u8].iter().copied()),
hello_bin.into_vec()
);
assert_eq!("Hello".to_owned(), hello_str.into_string());
}
Interfaces:
Bin
: Binary (it's a struct).SBin
: Synchronized binary (it's a struct).Str
: String (type Str = AnyStr<Bin>
)SStr
: Synchronized string (type SStr = AnyStr<SBin>
).Factories provided by the default implementation:
NewBin
: Creates Bin
.NewSBin
: Creates SBin
.NewStr
: Creates Str
.NewSStr
: Creates SStr
.See also:
AnyBin
: Trait implemented by Bin
and SBin
.AnyStr
: See Str
and SStr
; string backed by either Bin
or SBin
.BinFactory
: Factory trait implemented by NewBin
and NewSBin
.StrFactory
: Factory trait implemented by NewStr
and NewSStr
.See the example tests:
Vec<u8>
and String
.Cow
that works with types that don't implement ToOwned
.Boo
with serde.Send + Sync
) and non-synchronized binaries / strings.It's quite young (development started in October 2020). The main functionality has been implemented. Things I might do:
loom
/ more tests.There's already other crates with similar functionality, why another one? / Features
This crate provides some features that cannot be found in other crates (or not all of them):
Bin
/Str
to Bin
/Str
) (usually zero-allocation / zero-copy).Bin
/Str
to &[u8]
/&str
).Why NewBin
, NewStr
? what's this?
Why let string = NewStr::from_static("Hello")
instead of just let string = Str::from_static("Hello")
(or implement From<&str> for Str
)? This is due to the decision to decouple the interface from the implementation. The Str
is the interface, whereas NewStr
is the factory of the built-in implementation. This library is designed to be extensible; you can provide your own implementation, tweaked for your use case.
How does the default-implementation NewBin
/ NewStr
work?
3 * sizeof(word) - 1
bytes; that's 23 bytes on a 64-bit platform. For reference, the string Hello, world!
only takes 13 bytes and could easily be stored on the stack.GivenVecConfig
). The reference-counter is stored inside the vector-data. This has those advantages:
Bin
from Vec<u8>
without allocation (if Vec<u8>
has some capacity left for the reference-counter) - something which is not possible by using Rc<[u8]>
.Rc<Vec<u8>>
) no second indirection is introduced.The only difference between NewBin
and NewSBin
is the reference-counted binaries: SBin
created by NewSBin
have a synchronized reference counter (AtomicUsize
).
Note: The same statements also apply to strings (since strings are backed by the binary implementation).
What operations are allocation-free / zero-copy?
It's not documented (in text) - and of course depends on the implementation ... but for the default-implementation (NewBin
/NewSBin
/NewStr
/NewSStr
) there's a test, see no_alloc_guarantees.rs.
Also, see these two tests for single-allocation guarantee:
I want to write my own implementation, how to?
There's currently no documentation - but you can use the default implementation for reference. It's found in the module implementation
.
Why Boo
and not Cow
?
Cow
requires where B: 'a + ToOwned
. This does not work with this crate, since the implementation is separated from the interface. Say we have &[u8]
(borrowed), to convert that to owned (Bin
or SBin
), the implementation has to be known. I don't want Cow
to contain information about the implementation.
Aren't Bin
and Str
huge (stack-size)?
Bin
and Str
have a size of 4 words and are word-aligned. Yes, it's not small - but for reference, a Vec<u8>
also takes 3 words (pointer, length and capacity).
What is re-integration?
Say we have this code (pseudocode):
let large_binary_from_network : Vec<u8> = <...>;
let bin = NewBin::from_given_vec(large_binary_from_network);
let slice_of_that_bin : &[u8] = &bin.as_slice()[45..458];
// it's now possible to re-integrate that `slice_of_that_bin` into the `bin` it was sliced from.
// re-integration converts the borrowed type `&[u8]` (`slice_of_that_bin`) into an owned
// type (`Bin`) without memory-allocation or memory-copy.
let bin_re_integrated : Bin = bin.try_re_integrate(slice_of_that_bin).unwrap();
This is useful if you want to de-serialize to owned (without using Boo
) using serde. When deserializing a type, we get slice_of_that_bin
from serde; using re-integration it's possible to get an owned binary (Bin
) without allocation.
Technical detail: It checks whether slice_of_that_bin
lies within the memory range of bin
; if so, it increments the reference-count of bin
by one, and the returned binary (bin_re_integrated
) is then just a sliced reference to bin
.
Name abin
?
It's named after the trait AnyBin
.
See abin-benchmark
crate for details.
cd benchmark
cargo bench
cargo test
The benchmarks are performed against those implementations:
BytesBenchStr
: Uses the bytes
crate. Overall, this implementation performs similar to abin
(memory and performance; abin
allocates a bit less).StdLibOptimized
: Uses Arc<str>
/ Arc<String>
/ &'static str
, ()
(empty) with slicing-support (hand-optimized). It's very similar to what abin
internally does (except for storing small binaries on the stack). Overall, this implementation performs similar to abin
(abin
allocates a bit less).StdLibStringOnly
: Uses always String
(from Rust std-lib); no optimization. Much worse than abin
(slower and allocates way more).StdLibArcStrOnly
: Always uses Arc<str>
(from Rust std-lib); no optimization. Much worse than abin
(slower and allocates way more).abin
is slightly better than StdLibOptimized
& BytesBenchStr
(especially in number of allocations) - and outperforms StdLibStringOnly
and StdLibArcStrOnly
by margin (see number of bytes allocated, it's 380 MB vs 840 MB / 1.2 GB; and the number of allocations is almost 10x).
Results for abin
(using SStr
)
{ allocations: 3154, deallocations: 3154, reallocations: 12, bytes_allocated: 388755346,
bytes_deallocated: 388755346, bytes_reallocated: 11520 }
Results for BytesBenchStr
{ allocations: 15454, deallocations: 15454, reallocations: 2212, bytes_allocated: 494895196,
bytes_deallocated: 494895196, bytes_reallocated: 520 }
Results for StdLibOptimized
{ allocations: 18154, deallocations: 18154, reallocations: 12, bytes_allocated: 495272868,
bytes_deallocated: 495272868, bytes_reallocated: 14400 }
Results for StdLibStringOnly
{ allocations: 21754, deallocations: 21754, reallocations: 1212, bytes_allocated: 848171274,
bytes_deallocated: 848171274, bytes_reallocated: 105981240 }
Results for StdLibArcStrOnly
{ allocations: 34354, deallocations: 34354, reallocations: 1212, bytes_allocated: 1201859852,
bytes_deallocated: 1201859852, bytes_reallocated: 105978360 }
As you can see, abin
, StdLibOptimized
and BytesBenchStr
perform about the same (abin
is slightly better and has fewer outliers); but are almost twice as fast as StdLibStringOnly
and StdLibArcStrOnly
.
Results for abin
(using SStr
)
time: [65.503 ms 67.157 ms 68.869 ms]
Found 1 outliers among 100 measurements (1.00%)
Results for abin
(using Str
)
time: [71.207 ms 72.825 ms 74.546 ms]
Found 3 outliers among 100 measurements (3.00%)
Results for BytesBenchStr
time: [89.518 ms 91.279 ms 93.124 ms]
Found 13 outliers among 100 measurements (13.00%)
Results for StdLibOptimized
time: [78.972 ms 79.765 ms 80.556 ms]
Found 4 outliers among 100 measurements (4.00%)
Results for StdLibStringOnly
time: [118.53 ms 121.24 ms 124.15 ms]
Found 21 outliers among 100 measurements (21.00%)
Results for StdLibArcStrOnly
time: [118.36 ms 118.90 ms 119.56 ms]
Found 10 outliers among 100 measurements (10.00%)
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
See CONTRIBUTING.md.