Skip to content

Update benchmarks and descriptions. #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ac54ea9
Migrate to a v2 fork.
Alexhuszagh Oct 30, 2024
dd9ebc2
Update CI for older versions.
Alexhuszagh Oct 30, 2024
36b5ab8
Merge pull request #1 from Alexhuszagh/fork
Alexhuszagh Oct 30, 2024
cdcde1e
Document maintenance mode.
Alexhuszagh Oct 30, 2024
a097265
Create SECURITY.md
Alexhuszagh Oct 30, 2024
f24b4c8
Merge pull request #3 from Alexhuszagh/Alexhuszagh-patch-1
Alexhuszagh Oct 30, 2024
b5904d4
Add fast-float to the bench comparisons.
Alexhuszagh Oct 31, 2024
04a9d4d
Merge pull request #5 from Alexhuszagh/benches
Alexhuszagh Oct 31, 2024
d36f96b
Increment dependencies for tests and benches.
Alexhuszagh Oct 31, 2024
52cc113
Merge pull request #6 from Alexhuszagh/deps
Alexhuszagh Oct 31, 2024
9d91b3a
Remove all non-local unsafety.
Alexhuszagh Oct 31, 2024
57bf987
Merge pull request #7 from Alexhuszagh/issue_2
Alexhuszagh Oct 31, 2024
6f430e6
Ensure checked indexing is used for the power-of-5 table lookup.
Alexhuszagh Oct 31, 2024
c8daa4d
Merge pull request #8 from Alexhuszagh/issue_02
Alexhuszagh Oct 31, 2024
4b079a5
Update changelog.
Alexhuszagh Oct 31, 2024
ea01d16
Add in our rust formatters.
Alexhuszagh Oct 31, 2024
90b7523
Add our clippy lints.
Alexhuszagh Oct 31, 2024
af2ce36
Add linters to our CI.
Alexhuszagh Oct 31, 2024
b3588da
Patch clippy lints.
Alexhuszagh Oct 31, 2024
9fdcdca
Merge pull request #9 from Alexhuszagh/fmtclippy
Alexhuszagh Oct 31, 2024
86c93dd
Add no_std support.
Alexhuszagh Oct 31, 2024
b1f7003
Merge pull request #10 from Alexhuszagh/nostd
Alexhuszagh Oct 31, 2024
a164083
Update our changelog for no_std support.
Alexhuszagh Oct 31, 2024
31d1abf
Remove unsafety for the v0.2.2 release.
Alexhuszagh Oct 31, 2024
04cd614
Add miri and fuzz targets to our CI.
Alexhuszagh Oct 31, 2024
9013c83
Merge pull request #13 from Alexhuszagh/fuzz
Alexhuszagh Oct 31, 2024
2cc6889
Ensure our lengths use `isize` and not `usize` for checks.
Alexhuszagh Oct 31, 2024
53bce57
Fix the `MAX` to `max_value()` for ancient rustc support.
Alexhuszagh Oct 31, 2024
d217cf5
Merge pull request #14 from Alexhuszagh/max
Alexhuszagh Oct 31, 2024
a6ef805
Update benchmarks and descriptions.
Alexhuszagh Dec 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: CI

on:
push:
pull_request:
[push, pull_request, workflow_dispatch]

jobs:
test:
Expand All @@ -11,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [1.37.0, stable, nightly]
rust: [1.63.0, stable, nightly]
steps:
- uses: actions/checkout@v2
with:
Expand All @@ -23,6 +22,23 @@ jobs:
- run: cargo test
- run: cd extras/data-tests && cargo run --release

msrv:
name: Rust ${{matrix.rust}}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust: [1.37.0]
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
- run: cargo check
- run: cargo build

cross:
name: Rust ${{matrix.target}}
runs-on: ubuntu-latest
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Fuzz

on:
[pull_request, workflow_dispatch]

jobs:
fuzz:
name: Fuzz ${{matrix.target}}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: [fast_float, roundtrip_f64]
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
- run: cargo install cargo-fuzz
- run: cargo check
- run: |
cd fuzz
cargo +nightly fuzz run --release ${{matrix.target}} -- -max_total_time=300
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Lint

on:
[push, pull_request, workflow_dispatch]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
components: rustfmt, clippy
- run: cargo check
- run: cargo fmt -- --check
- run: RUSTFLAGS="--deny warnings" cargo build
- run: cargo clippy --all-features -- --deny warnings
21 changes: 21 additions & 0 deletions .github/workflows/miri.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Miri

on:
[pull_request, workflow_dispatch]

jobs:
miri:
name: Miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
- run: cargo check
- run: cargo build
- run: |
rustup component add --toolchain nightly miri
cargo miri test
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 0.2.2

- Fix `no_std` support.
- Remove most uses of unsafe.
- Remove non-local safety invariants to prevent unsoundness.

## 0.2.1

- Fix undefined behavior in checking the buffer length.

## 0.2.0

- Fixed an edge case where long decimals with trailing zeros were truncated.
Expand Down
20 changes: 11 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "fast-float"
version = "0.2.0"
authors = ["Ivan Smirnov <[email protected]>"]
repository = "https://github.com/aldanor/fast-float-rust"
documentation = "https://docs.rs/fast-float"
name = "fast-float2"
version = "0.2.3"
authors = ["Ivan Smirnov <[email protected]>", "Alex Huszagh <[email protected]>"]
repository = "https://github.com/Alexhuszagh/fast-float-rust"
documentation = "https://docs.rs/fast-float2"
description = "Fast floating-point number parser."
keywords = ["parser", "parsing", "parse", "float", "no-std"]
categories = ["parser-implementations", "parsing", "text-processing", "algorithms", "no-std"]
Expand All @@ -12,17 +12,19 @@ license = "MIT OR Apache-2.0"
autobenches = false
edition = "2018"
exclude = ["benches/*", "extras/*"]
# FIXME: rust-version is not supported until 1.56.0.
rust-version = "1.37"

[features]
default = ["std"]
std = []

[dev-dependencies]
lexical-core = "0.7"
hexf-parse = "0.1"
lexical-core = "1.0.2"
hexf-parse = "0.2.1"
ryu = "1.0"
fastrand = "1.4"
num-bigint = "0.3"
fastrand = "2.1.1"
num-bigint = "0.4.6"

[workspace]
members = [".", "extras/data-tests", "extras/simple-bench"]
Expand Down
110 changes: 44 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
fast-float
==========
# fast-float2

[![Build](https://github.com/aldanor/fast-float-rust/workflows/CI/badge.svg)](https://github.com/aldanor/fast-float-rust/actions?query=branch%3Amaster)
[![Latest Version](https://img.shields.io/crates/v/fast-float.svg)](https://crates.io/crates/fast-float)
[![Documentation](https://docs.rs/fast-float/badge.svg)](https://docs.rs/fast-float)
[![Build](https://github.com/Alexhuszagh/fast-float-rust/workflows/CI/badge.svg)](https://github.com/Alexhuszagh/fast-float-rust/actions?query=branch%3Amaster)
[![Latest Version](https://img.shields.io/crates/v/fast-float2.svg)](https://crates.io/crates/fast-float2)
[![Documentation](https://docs.rs/fast-float2/badge.svg)](https://docs.rs/fast-float2)
[![Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Rustc 1.37+](https://img.shields.io/badge/rustc-1.37+-lightgray.svg)](https://blog.rust-lang.org/2019/08/15/Rust-1.37.0.html)
Expand All @@ -12,19 +11,21 @@ This crate provides a super-fast decimal number parser from strings into floats.

```toml
[dependencies]
fast-float = "0.2"
fast-float2 = "0.2.3"
```

There are no dependencies and the crate can be used in a no_std context by disabling the "std" feature.

*Compiler support: rustc 1.37+.*

This crate is in maintenance mode for bug fixes (especially security patches): minimal feature enhancements will be accepted. This implementation has been adopted by the Rust standard library: if you do not need parsing directly from bytes and/or partial parsers, you should use [FromStr](https://doc.rust-lang.org/std/str/trait.FromStr.html) for [f32](https://doc.rust-lang.org/std/primitive.f32.html) or [f64](https://doc.rust-lang.org/std/primitive.f64.html) instead.

## Usage

There's two top-level functions provided:
[`parse()`](https://docs.rs/fast-float/latest/fast_float/fn.parse.html) and
There's two top-level functions provided:
[`parse()`](https://docs.rs/fast-float/latest/fast_float/fn.parse.html) and
[`parse_partial()`](https://docs.rs/fast-float/latest/fast_float/fn.parse_partial.html), both taking
either a string or a bytes slice and parsing the input into either `f32` or `f64`:
either a string or a bytes slice and parsing the input into either `f32` or `f64`:

- `parse()` treats the whole string as a decimal number and returns an error if there are
invalid characters or if the string is empty.
Expand All @@ -39,12 +40,12 @@ Example:
```rust
// Parse the entire string as a decimal number.
let s = "1.23e-02";
let x: f32 = fast_float::parse(s).unwrap();
let x: f32 = fast_float2::parse(s).unwrap();
assert_eq!(x, 0.0123);

// Parse as many characters as possible as a decimal number.
let s = "1.23e-02foo";
let (x, n) = fast_float::parse_partial::<f32, _>(s).unwrap();
let (x, n) = fast_float2::parse_partial::<f32, _>(s).unwrap();
assert_eq!(x, 0.0123);
assert_eq!(n, 8);
assert_eq!(&s[n..], "foo");
Expand All @@ -53,19 +54,22 @@ assert_eq!(&s[n..], "foo");
## Details

This crate is a direct port of Daniel Lemire's [`fast_float`](https://github.com/fastfloat/fast_float)
C++ library (valuable discussions with Daniel while porting it helped shape the crate and get it to
C++ library (valuable discussions with Daniel while porting it helped shape the crate and get it to
the performance level it's at now), with some Rust-specific tweaks. Please see the original
repository for many useful details regarding the algorithm and the implementation.

The parser is locale-independent. The resulting value is the closest floating-point values (using either
`f32` or `f64`), using the "round to even" convention for values that would otherwise fall right in-between
two values. That is, we provide exact parsing according to the IEEE standard.
The parser is locale-independent. The resulting value is the closest floating-point values (using either
`f32` or `f64`), using the "round to even" convention for values that would otherwise fall right in-between
two values. That is, we provide exact parsing according to the IEEE standard.

Infinity and NaN values can be parsed, along with scientific notation.

Both little-endian and big-endian platforms are equally supported, with extra optimizations enabled
on little-endian architectures.

Since [fast-float-rust](https://github.com/aldanor/fast-float-rust) is unmaintained, this is a fork
containing the patches and security updates.

## Testing

There are a few ways this crate is tested:
Expand All @@ -80,14 +84,11 @@ There are a few ways this crate is tested:
## Performance

The presented parser seems to beat all of the existing C/C++/Rust float parsers known to us at the
moment by a large margin, in all of the datasets we tested it on so far – see detailed benchmarks
moment by a large margin, in all of the datasets we tested it on so far – see detailed benchmarks
below (the only exception being the original fast_float C++ library, of course – performance of
which is within noise bounds of this crate). On modern machines like Apple M1, parsing throughput
can reach up to 1.5 GB/s.

In particular, it is faster than Rust standard library's `FromStr::from_str()` by a factor of 2-8x
(larger factor for longer float strings), and is typically 2-3x faster than the nearest competitors.

While various details regarding the algorithm can be found in the repository for the original
C++ library, here are few brief notes:

Expand All @@ -103,55 +104,32 @@ C++ library, here are few brief notes:

## Benchmarks

Below are tables of best timings in nanoseconds for parsing a single number
into a 64-bit float.

#### Intel i7-4771

Intel i7-4771 3.5GHz, macOS, Rust 1.49.

| | `canada` | `mesh` | `uniform` | `iidi` | `iei` | `rec32` |
| ---------------- | -------- | -------- | --------- | ------ | ------ | ------- |
| fast-float | 21.58 | 10.70 | 19.36 | 40.50 | 26.07 | 29.13 |
| lexical | 65.90 | 23.28 | 54.75 | 75.80 | 52.18 | 75.36 |
| from_str | 174.43 | 22.30 | 99.93 | 227.76 | 111.31 | 204.46 |
| fast_float (C++) | 22.78 | 10.99 | 20.05 | 41.12 | 27.51 | 30.85 |
| abseil (C++) | 42.66 | 32.88 | 46.01 | 50.83 | 46.33 | 49.95 |
| netlib (C) | 57.53 | 24.86 | 64.72 | 56.63 | 36.20 | 67.29 |
| strtod (C) | 286.10 | 31.15 | 258.73 | 295.73 | 205.72 | 315.95 |

#### Apple M1

Apple M1, macOS, Rust 1.49.
Below are tables of best timings in nanoseconds for parsing a single number
into a 64-bit float (using the median score).

| | `canada` | `mesh` | `uniform` | `iidi` | `iei` | `rec32` |
| ---------------- | -------- | -------- | --------- | ------ | ------ | ------- |
| fast-float | 14.84 | 5.98 | 11.24 | 33.24 | 21.30 | 17.86 |
| lexical | 47.09 | 16.51 | 43.46 | 56.06 | 36.68 | 55.48 |
| from_str | 136.00 | 13.84 | 74.64 | 179.87 | 77.91 | 154.53 |
| fast_float (C++) | 13.71 | 7.28 | 11.71 | 32.94 | 20.64 | 18.30 |
| abseil (C++) | 36.55 | 24.20 | 38.48 | 40.86 | 35.46 | 40.09 |
| netlib (C) | 47.19 | 14.12 | 48.85 | 52.28 | 33.70 | 48.79 |
| strtod (C) | 176.13 | 21.48 | 165.43 | 187.98 | 132.19 | 190.63 |
### Intel i7-14700K

#### AMD Rome
Intel i7-14700K 3.40GHz, Linux (WSL2), Rust 1.81.

AMD Rome, Linux, Rust 1.49.
| | `canada` | `mesh` | `uniform` | `bi` | `iei` | `rec32` |
| ---------------------- | -------- | -------- | --------- | ----- | ------ | ------- |
| fast-float2 | 9.98 | 5.56 | 10.08 | 56.19 | 14.52 | 15.09 |
| fast-float | 9.77 | 5.04 | 9.05 | 57.52 | 14.40 | 14.23 |
| lexical | 10.62 | 4.93 | 9.92 | 26.40 | 12.43 | 14.40 |
| from_str | 11.59 | 5.92 | 11.23 | 35.92 | 14.75 | 16.76 |
| fast_float (C++) | 12.58 | 6.35 | 11.86 | 31.55 | 12.22 | 11.97 |
| abseil (C++) | 25.32 | 15.70 | 25.88 | 43.42 | 23.54 | 26.75 |
| netlib (C) | 35.10 | 10.22 | 37.72 | 68.63 | 23.07 | 38.23 |
| strtod (C) | 52.63 | 26.47 | 46.51 | 88.11 | 33.37 | 53.36 |
| doubleconversion (C++) | 32.50 | 14.69 | 47.80 | 70.01 | 205.72 | 45.66 |

| | `canada` | `mesh` | `uniform` | `iidi` | `iei` | `rec32` |
| ---------------- | -------- | -------- | --------- | ------ | ------ | ------- |
| fast-float | 25.90 | 12.12 | 20.54 | 47.01 | 29.23 | 32.36 |
| lexical | 63.18 | 22.13 | 54.78 | 81.23 | 55.06 | 79.14 |
| from_str | 190.06 | 26.10 | 102.44 | 239.87 | 119.04 | 211.73 |
| fast_float (C++) | 21.29 | 10.47 | 18.31 | 42.33 | 24.56 | 29.76 |
| abseil (C++) | 44.54 | 34.13 | 47.38 | 52.64 | 43.77 | 53.03 |
| netlib (C) | 69.43 | 23.31 | 79.98 | 72.17 | 35.81 | 86.91 |
| strtod (C) | 123.37 | 65.68 | 101.58 | 118.36 | 118.61 | 123.72 |
Note that the random number generation seems to differ between C/C++ and Rust, since the Rust implementations are slightly faster for pre-determined datasets like `canada` and `mesh`, but equivalent random number generators are slightly slower. Any performance penalty with `fast-float2` occurred due to fixing the UB in [check_len](https://github.com/aldanor/fast-float-rust/issues/28). The massive performance differences between `fast-float` (Rust) and `fast_float` (C++) are expected due to a faster fallback algorithms ([#96](https://github.com/fastfloat/fast_float/pull/96) and [#104](https://github.com/fastfloat/fast_float/pull/104)) used in these cases.

#### Parsers

- `fast-float` - this very crate
- `lexical` – `lexical_core`, v0.7 (non-lossy; same performance as lossy)
- `fast-float2` - this very crate
- `fast-float` - the pre-ported variant
- `lexical` – `lexical_core`, v1.0.05
- `from_str` – Rust standard library, `FromStr` trait
- `fast_float (C++)` – original C++ implementation of 'fast-float' method
- `abseil (C++)` – Abseil C++ Common Libraries
Expand All @@ -163,18 +141,18 @@ AMD Rome, Linux, Rust 1.49.
- `canada` – numbers in `canada.txt` file
- `mesh` – numbers in `mesh.txt` file
- `uniform` – uniform random numbers from 0 to 1
- `iidi` – random numbers of format `%d%d.%d`
- `iei` – random numbers of format `%de%d`
- `rec32` – reciprocals of random 32-bit integers
- `bi` – large, integer-only floats <!-- `big_ints` -- >
- `int_e_int` – random numbers of format `%de%d` <!-- `int_e_int` -->
- `rec32` – reciprocals of random 32-bit integers <!-- `one_over_rand32` -->

#### Notes

- The two test files referred above can be found in
- The two test files referred above can be found in
[this](https://github.com/lemire/simple_fastfloat_benchmark) repository.
- The Rust part of the table (along with a few other benchmarks) can be generated via
the benchmark tool that can be found under `extras/simple-bench` of this repo.
- The C/C++ part of the table (along with a few other benchmarks and parsers) can be
generated via a C++ utility that can be found in
generated via a C++ utility that can be found in
[this](https://github.com/lemire/simple_fastfloat_benchmark) repository.

<br>
Expand Down
3 changes: 3 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Security Policy

This crate is in maintenance mode, so only the latest version is supported and will be receiving bug fixes. If you have a security vulnerability, please reach out to me privately at [[email protected]](mailto:[email protected]). Other forms of communication may not reach me.
20 changes: 20 additions & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
avoid-breaking-exported-api = false
disallowed-macros = [
# Can also use an inline table with a `path` key.
{ path = "std::print", reason = "no IO allowed" },
{ path = "std::println", reason = "no IO allowed" },
{ path = "std::format", reason = "no string allocation allowed" },
{ path = "std::debug", reason = "debugging macros should not be present in any release" },
# NOTE: unimplemented is fine because this can be for intentionally disabled methods
{ path = "std::todo", reason = "should never have TODO macros in releases" },
]
disallowed-methods = [
{ path = "std::io::stdout", reason = "no IO allowed" },
{ path = "std::io::stdin", reason = "no IO allowed" },
{ path = "std::io::stderr", reason = "no IO allowed" },
]
disallowed-types = [
{ path = "std::io::File", reason = "no IO allowed" },
{ path = "std::io::BufReader", reason = "need our own abstractions for reading/writing" },
{ path = "std::io::BufWriter", reason = "need our own abstractions for reading/writing" },
]
2 changes: 1 addition & 1 deletion extras/data-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ license = "MIT OR Apache-2.0"
publish = false

[dependencies]
fast-float = { path = "../.." }
fast-float2 = { path = "../.." }
Loading
Loading