Article

Knurling-rs changelog #28

Published on 8 min read
Knurling icon
Knurling
A tool set to develop embedded applications faster.
❤️ Sponsor

    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"
    

    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:

    1. before both PRs (14a842c)
    2. directly after #32 (d0a3199)
    3. just before #45 (b44b695)
    4. 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.

    https://github.com/knurling-rs/defmt/pull/519

    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 with us
    • #521 3/n Remove u24. Thanks to Dirbaio!
    • #514 Extend raw pointer implementation to include !Format types
    • #513 book/duplicates.md: Fix discriminator -> disambiguator. Thanks to eupn!
    • #508 5/n Format trait v2. Thanks to Dirbaio!
    • #359 Implement precedence of inner display hint
    • #41 tests: Verify initial stack-pointer to be inside static 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!
    • #45 Cargo.toml: Disable default features of env_logger
    • #44 Add helper crate xtest

    Fixes 🔨

    knurling-session-20q4

    • #19 Fix spelling mistake and markdown formatting. Thanks to oddstr13!

    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!