Skip to content

Display impl for u128 and i128 is slow #44583

Closed

Description

@dtolnay

@henninglive has a significantly faster one in dtolnay/itoa#10 (comment). Let's provide the faster one in std::fmt!

Unoptimized:
	test bench_itoa::bench_u128_max ... bench:       3,009 ns/iter (+/- 99)

Manual linking:
	test bench_itoa::bench_u128_max ... bench:       1,537 ns/iter (+/- 59)

Inlined:
	test bench_itoa::bench_u128_max ... bench:       1,289 ns/iter (+/- 54)

Activity

  1. henninglive commented on Sep 15, 2017

    @henninglive

    The implementation is copied from std::fmt, the only change is that I have inlined the __udivmodti4() implementation to work around #44545 until LLVM is smart enough combine the calls. This is probably not relevant for std.

  2. dtolnay commented on Sep 15, 2017

    @dtolnay
    MemberAuthor

    I guess I haven't looked at the two implementations carefully. Can we not inline __udivmodti4() in std?

  3. henninglive commented on Sep 15, 2017

    @henninglive

    It’s possible, but we probably don’t want to do that. __udivmodti4() is an intrinsic passed to LLVM to be used to implement division for i128/u128 on architectures without native 128-bit support. It is an implementation detail of the architecture and is currently defined separately from the compiler itself. Std is going to nearly as fast as the inlined version when #44545 is eventually fixed. We should proberly wait for that or we could let std link __udivmodti4() as it did in the past, rust-lang/rfcs#914.

  4. cuviper commented on Sep 15, 2017

    @cuviper
    Member

    For platforms that don't have native 128-bit division, it may be faster to break it into parts that can do native division from there. Bases 2, 8, and 16 can split directly to 64-bit parts, and base-10 can split into three base-1013 parts. If some 32-bit targets don't have native 64-bit division, they may do even better with 5 base-108 parts stored in 32-bit, etc.

  5. cuviper commented on Sep 15, 2017

    @cuviper
    Member

    Hmm, I just read the implementation, and it already chunks it by 10000, so that much is probably pretty good already.

  6. henninglive commented on Sep 15, 2017

    @henninglive

    I haven’t tried it, but I believe the current implementation is going to be pretty terrible on platforms without u64. If I understand this code correctly, the __udivmodti4() implementation as going use __udivmoddi4 and its brothers to do u64 operations, which a lot of non inlined functions calls to divide two u128s.

  7. dtolnay commented on Sep 16, 2017

    @dtolnay
    MemberAuthor

    I have yet another implementation in dtolnay/itoa#12. This one is 13x faster than std::fmt on my machine.

  8. added
    I-slowIssue: Problems and improvements with respect to performance of generated code.
    T-libs-api[DEPRECATED; DO NOT USE]
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on Sep 17, 2017
  9. added
    E-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.
    on May 27, 2018
  10. steveklabnik commented on Jan 21, 2020

    @steveklabnik
    Contributor

    Triage: this appears to have gotten even more extreme over time:

    running 2 tests
    test itoa   ... bench:         216 ns/iter (+/- 28)
    test stdlib ... bench:       1,377 ns/iter (+/- 123)
    

    The reproduction involves cargo features and so cannot be done on the playground, details below.

    Details

    Cargo.toml:

    [package]
    name = "itoatest"
    version = "0.1.0"
    authors = ["Steve Klabnik <steve@steveklabnik.com>"]
    edition = "2018"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies.itoa]
    version = "*"
    features = ["std", "i128"]

    in benches/bench.rs:

    #![feature(test)]
    extern crate test;
    extern crate itoa;
    
    use test::Bencher;
    
    #[bench]
    fn stdlib(b: &mut Bencher) {
        b.iter(|| {
            let s = format!("{}", u128::max_value());
            std::hint::black_box(s);
        });
    }
    
    #[bench]
    fn itoa(b: &mut Bencher) {
    
        b.iter(|| {
            let mut s = String::new();
            itoa::fmt(&mut s, u128::max_value()).unwrap();
            std::hint::black_box(s);
        });
    }
  11. added a commit that references this issue on Oct 4, 2020
  12. mdibaiee commented on Jan 9, 2022

    @mdibaiee
    Contributor

    Is this still an open issue? I see a PR for this has been merged.

  13. mati865 commented on Jan 15, 2022

    @mati865
    Member

    Using @steveklabnik example (with added #![feature(bench_black_box)]) I've got this:

    running 2 tests
    test itoa   ... bench:          34 ns/iter (+/- 0)
    test stdlib ... bench:          54 ns/iter (+/- 0)
    

    I think there is still some room for improvements but it's much better now.

  14. dtolnay commented on Jan 15, 2022

    @dtolnay
    MemberAuthor

    That's likely the best std::fmt can do. The remaining 20ns discrepancy is not algorithmic, it is the overhead of the Formatter machinery.

  15. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Jan 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-enhancementCategory: An issue proposing an enhancement or a PR with one.E-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.I-slowIssue: Problems and improvements with respect to performance of generated code.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions