This is the 28th changelog for Knurling-rs, our push to sustainably build better tooling for developing and debugging Rust software for embedded systems. Knurling-rs includes a suite of tools that make it easier to develop, log, debug, and test your embedded Rust libraries and applications!
Knurling-rs is supported by our sponsors on GitHub. If you're interested in seeing more projects like this, consider becoming a sponsor today!
Highlights 🎉
precedence of inner display hints
The PR defmt#359 by justahero implements precedence of display hints inner types, e.g. u8
, u16
etc., and the display hint precedence rules given in defmt#319.
When formating variables using the defmt::Format
trait, either by implementing it or using the derive
macro, the outer display hint is ignored depending on the inner type & its display hint.
For example, a struct that uses the derive
macro
#[derive(Format)]
struct S { x: u8 }
// derive would expand to
impl defmt::Format for S {
fn format(&self, f: defmt::Formatter) {
write!(f, "S {{ x: {=u8:?} }}", self.x);
}
}
This uses the Debug
display hint (:?
) for the inner type x
of struct S
. When displaying the above struct S
with
defmt::info!("{:#x}", S { x: 42 });
// -> INFO S { x: 0x2a }
the outer display hint :#x
is applied to the inner type x
. In this case the outer display hint hexadecimal is used.
On the other hand if the inner type is one of :x
(hex) and :b
(binary), the outer display hint will be ignored and the inner hint has precedence.
This time the implementation uses the :b
(binary) display hint to format inner type x
.
impl defmt::Format for S {
fn format(&self, f: defmt::Formatter) {
write!(f, "S {{ x: {=u8:b} }}", self.x);
}
}
When logging the struct this time
defmt::info!("{:x}", S { x: 42 });
// -> INFO S { x: 101010 }
the outer display hint :x
is ignored, the inner display hint :b
is used and therefore the value 42
is formatted as a binary string 101010
.
Another precedence rule is related to str
fields of structs. As given in the RFC defmt#319, the following struct with an inner str
field is deriving the defmt::Format
trait as follows.
#[derive(Format)]
struct S { x: &'static str }
// derive now expands to
impl defmt::Format for S {
fn format(&self, f: defmt::Formatter) {
write!(f, "S {{ x: {=str:?} }}", self.x);
}
}
The difference is x: &'static str
is expanded to {=str:?}
instead of a plain {=str}
. This way string in x
is enclosed by double quotes.
defmt::info("hello {:?}", S { x: "world" });
// -> hello "world"
non-default flip-link
flip-link#45 was the second PR of its kind to minimize the dependencies of flip-link
, with the goal of quicker builds and a smaller binary size. The first step was taken in April with PR flip-link#32.
In both PRs we are taking advantage of the fact that we are not using all of the default cargo-features of some of our dependencies. Therefore we can disable the default features and only selectively enable the ones we need. This, in many cases, minimizes the count of sub-dependencies pulled-in during compilation.
Let's see how much each of the PRs gain individually and how much combined!
To do this we use the command-line benchmarking tool hyperfine
(crates-io). hyperfine
has many possibilities to finetune your benchmarks. Among other things, it lets us define one or multiple commands to benchmark, a command to run in preparation of each iteration (-p
) and the minimum amount of iterations to run (-m
). This leaves us with following command, which runs both cargo build
and cargo build --release
at least five times, but always starts from a clean slate by running cargo clean
:
$ hyperfine -p "cargo clean" -m 5 "cargo build" "cargo build --release"
We are going to compare the results of this command for four points in our git history:
- before both PRs (
14a842c
) - directly after #32 (
d0a3199
) - just before #45 (
b44b695
) - directly after #45 (
24fe06b
)
⚠️ warning: Before we dive into the results, two words of warning: Firstly, since the build time heavily depends on the hardware you use, these benchmarks don't show absolute gains, but rather show how the changes will impact the build time in most setups. Secondly, other changes which happened in-between #32 and #45 obviously also impacted the build-time and binary-size.
First we take a look how the build time looked before the two PRs:
Benchmark #1: cargo build
Time (mean ± σ): 9.591 s ± 0.137 s
Range (min … max): 9.406 s … 9.753 s 5 runs
Benchmark #2: cargo build --release
Time (mean ± σ): 17.055 s ± 0.159 s
Range (min … max): 16.910 s … 17.268 s 5 runs
Summary
'cargo build' ran
1.78 ± 0.03 times faster than 'cargo build --release'
So our baseline is 9.591 seconds for the dev- and 17.055 seconds for the release-profile.
#32
gains build-time, through disabling the default features of the object
crate, which removes 6 sub-dependencies from the dependency-chain.
Benchmark #1: cargo build
Time (mean ± σ): 6.158 s ± 0.152 s
Range (min … max): 5.909 s … 6.291 s 5 runs
Benchmark #2: cargo build --release
Time (mean ± σ): 12.297 s ± 0.079 s
Range (min … max): 12.219 s … 12.430 s 5 runs
Summary
'cargo build' ran
2.00 ± 0.05 times faster than 'cargo build --release'
This leaves us at 6.158 seconds for the dev- and 12.297 seconds for the release-profile. This is already a gain of 28% for dev and 36% for release!
Just before #45 the situation looks like following:
Benchmark #1: cargo build
Time (mean ± σ): 6.173 s ± 0.153 s
Range (min … max): 5.925 s … 6.305 s 5 runs
Benchmark #2: cargo build --release
Time (mean ± σ): 11.693 s ± 0.058 s
Range (min … max): 11.627 s … 11.758 s 5 runs
Summary
'cargo build' ran
1.89 ± 0.05 times faster than 'cargo build --release'
This is very similar to the previous benchmark. The slight gain in the release-profile is probably due to updated dependency versions.
#45
disables the default features of env_logger
and therefore removes 8 sub-dependencies from the dependency-chain.
Benchmark #1: cargo build
Time (mean ± σ): 3.871 s ± 0.100 s
Range (min … max): 3.765 s … 3.992 s 5 runs
Benchmark #2: cargo build --release
Time (mean ± σ): 4.882 s ± 0.027 s
Range (min … max): 4.843 s … 4.912 s 5 runs
Summary
'cargo build' ran
1.26 ± 0.03 times faster than 'cargo build --release'
The result for this run is 3.871 seconds for the dev- and 4.882 seconds for the release-profile. This is a gain of 37% for dev and 58% for release!
If we combine the both PRs we improve from 9.591 to 3.871 seconds for dev and from 17.055 to 4.882 seconds for release. This is a total gain of 60% and 71% respectively!
Hopefully you don't have to wait too long, next time you run cargo install flip-link
! 😉
call for feedback: RFC - target-side env_logger
-like filter
In defmt#519 japaric proposes and implements a new approach to target-side log filtering. This shall supersede the current solution using cargo-features.
This RFC uses the DEFMT_LOG
environment variable to specify which crates should emit logs at which level. Its behavior and syntax is the same as for the env_logger
crate.
This code:
fn main() -> ! {
defmt::info!("hello");
foo::foo();
bar::bar();
app::exit()
}
mod foo {
pub fn foo() {
defmt::info!("foo");
}
}
mod bar {
pub fn bar() {
defmt::info!("bar");
}
}
Will behave like following:
$ DEFMT_LOG=hello::foo cargo r --bin hello
0 INFO foo
└─ hello::foo::foo @ src/bin/hello.rs:16
$ DEFMT_LOG=hello::bar cargo r --bin hello
0 INFO bar
└─ hello::bar::bar @ src/bin/hello.rs:22
$ DEFMT_LOG=hello::foo,hello::bar cargo r --bin hello
0 INFO foo
└─ hello::foo::foo @ src/bin/hello.rs:16
1 INFO bar
└─ hello::bar::bar @ src/bin/hello.rs:22
$ DEFMT_LOG=hello cargo r --bin hello
0 INFO hello
└─ hello::__cortex_m_rt_main @ src/bin/hello.rs:8
1 INFO foo
└─ hello::foo::foo @ src/bin/hello.rs:16
2 INFO bar
└─ hello::bar::bar @ src/bin/hello.rs:22
Head over to the PR for more details and discussion.
Please leave your feedback (as comment or emoji-reaction) if you like this approach or would prefer something else!
Improvements 🦀
defmt
- #524
4/n
test: do not depend on custom InternalFormatter. Thanks to Dirbaio! - #522 Replace
µs
hint withus
- #521
3/n
Remove u24. Thanks to Dirbaio! - #514 Extend raw pointer implementation to include
!Format
types - #513
book/duplicates.md
: Fixdiscriminator
->disambiguator
. Thanks to eupn! - #508
5/n
Format trait v2. Thanks to Dirbaio! - #359 Implement precedence of inner display hint
flip-link
- #41
tests
: Verify initial stack-pointer to be insidestatic RAM
Internal Improvements 🧽
defmt
- #526
decoder
: Simplify tests - #523
decoder
: Minimize dependencies - #516
xtask
: Only install additional targets for tests that require them - #512
xtask
: Add overwrite option for snapshot tests
probe-run
- #235 Update to
probe-rs 0.11
. Thanks to Sheap for the support! - #234 Feature-gate windows tests
- #233
README.md
: Some clarifications and corrections. Thank you huntc!
flip-link
Fixes 🔨
knurling-session-20q4
Version Update Notification 🆙
No new releases this week ¯\_(ツ)_/¯
Sponsor this work
Knurling-rs is mainly funded through GitHub sponsors. Sponsors get early access to the tools we are building and help us to support and grow the knurling tools and courses. Thank you to all of the people already sponsoring our work through the Knurling project!