Remove the last `format_args!` macro uses from hot paths
Background
In the early stages of spdlog-rs development a few years ago, we had already noticed that the standard library's write! macros are very slow, and this could be proven by simple benchmarking.
- Related issue in Rust: rust-lang/rust#76490
- There's also a blog "Behind the Scenes of Rust String Formatting: format_args!()" by m-ou-se that explains more details.
In spdlog-rs, we've avoided using write! macros in formatters and sinks as much as possible, and instead use .write_str() to put things together manually, for example
- Commit 9ff8b016f98a202e6f8d627e340893a2f3498ac1: Use
write_allinstead ofwriteln!to improve performance slightly - Commit 913234eea1edeccc97e75989a15136541233f052: Improve performance of built-in patterns
Because of these optimizations, performance in formatting has been significantly improved. However, there are still some remaining uses of format_args! that have not been completely removed, for example
https://github.com/SpriteOvO/spdlog-rs/blob/654b440d51c298983c8c0427665d1164157d33f4/spdlog/src/formatter/full_formatter.rs#L65-L72
https://github.com/SpriteOvO/spdlog-rs/blob/654b440d51c298983c8c0427665d1164157d33f4/spdlog/src/formatter/full_formatter.rs#L85-L92
https://github.com/SpriteOvO/spdlog-rs/blob/654b440d51c298983c8c0427665d1164157d33f4/spdlog/src/formatter/pattern_formatter/pattern/datetime.rs#L326-L340
What they have in common is that they write an integer to buffer and may with padding. However, writing integers without allocation with just stdlib is not possible in stable Rust today. If you're in nightly Rust, you can do this manually and probably improve performance.
use std::fmt::Display as _;
let mut opt = std::fmt::FormattingOptions::new(); // unstable
opt.width(Some(9)).fill('0'); // unstable
nanosecond.fmt(std::fmt::Formatter::new(&mut dest, opt) /* unstable */);
Solution
I found two crates that can convert integers to &str or &[u8] without allocation, which will most likely allow us to get rid of the last uses of format_args! and improve performance a bit further.
numtoa
mmstick/numtoa#5
itoa
dtolnay/itoa#46
They each have some issues and I'm not sure which one is better, so I'll test and benchmark them separately for spdlog-rs and then decide which one to use.
Turns out itoa doesn't support leading zeros at the moment, I have opened a issue for it. dtolnay/itoa#61
Since we are using formats like {:03}, it looks like numtoa is the only option for now for spdlog-rs.
The {integer}_padded functions in numtoa only supports padding length >= {integer}::BUF_LEN, it's still hard to replace std formatting like {:03} for integer type u32 with numtoa.
I have opened a PR to upstream adding new functions {integer}_filled to solve the problem.
mmstick/numtoa#40