Skip to content
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

String::new() is slower than "".to_string() #86106

Closed
wooster0 opened this issue Jun 7, 2021 · 15 comments · Fixed by #106036
Closed

String::new() is slower than "".to_string() #86106

wooster0 opened this issue Jun 7, 2021 · 15 comments · Fixed by #106036
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. I-slow Issue: Problems and improvements with respect to performance of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@wooster0
Copy link
Contributor

wooster0 commented Jun 7, 2021

I tried this code:

pub fn a() -> String {
    String::new()
}

pub fn b() -> String {
    "".to_string()
}

I expected to see this happen: both functions contain identical code.

Instead, this happened: https://rust.godbolt.org/z/P53MMG4W6.

For String::new() it does one more instruction: mov rcx, qword ptr [rip + .L__unnamed_1]. This rcx is then read further below at mov qword ptr [rdi], rcx.
The "".to_string() version however does not do this extra step and immediately reads from a constant: mov qword ptr [rdi], 1.
This means that String::new() does slightly more work than "".to_string() but I expect them to be identical.

Before 1.52.0 the functions generated identical code: https://rust.godbolt.org/z/8647E6YhY.

oli noted the following:

The problem is probably because it copies its value from the constant

pub const NEW: Self = Self::new();
instead of immediately creating the appropriate value

Meta

This currently happens on nightly.

@wooster0 wooster0 added the C-bug Category: This is a bug. label Jun 7, 2021
@jonas-schievink jonas-schievink added the I-slow Issue: Problems and improvements with respect to performance of generated code. label Jun 7, 2021
@wooster0
Copy link
Contributor Author

wooster0 commented Jun 7, 2021

@rustbot label regression-untriaged

@rustbot rustbot added regression-untriaged Untriaged performance or correctness regression. I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Jun 7, 2021
@JohnTitor
Copy link
Member

JohnTitor commented Jun 7, 2021

Regressed since 1.52.0, between nightly-2021-03-04 and nightly-2021-03-05 so I assume it's caused (revealed?) by #81451.

@JohnTitor JohnTitor added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. regression-from-stable-to-stable Performance or correctness regression from one stable version to another. and removed regression-untriaged Untriaged performance or correctness regression. labels Jun 7, 2021
@cynecx
Copy link
Contributor

cynecx commented Jun 7, 2021

Reduced llvm-ir:

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%"std::string::String" = type { [0 x i64], %"std::vec::Vec<u8>", [0 x i64] }
%"std::vec::Vec<u8>" = type { [0 x i64], { i8*, i64 }, [0 x i64], i64, [0 x i64] }

@0 = private unnamed_addr constant <{ [16 x i8] }> <{ [16 x i8] c"\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" }>, align 8

define void @abc(%"std::string::String"* %0) unnamed_addr {
start:
  %1 = load i8*, i8** bitcast (<{ [16 x i8] }>* @0 to i8**), align 8
  %ptr = bitcast %"std::string::String"* %0 to i8**
  store i8* %1, i8** %ptr, align 8
  ret void
}

; This works by replacing the pointer type with an integral type

define void @cba(%"std::string::String"* %0) unnamed_addr {
start:
  %1 = load i64, i64* bitcast (<{ [16 x i8] }>* @0 to i64*), align 8
  %ptr = bitcast %"std::string::String"* %0 to i64*
  store i64 %1, i64* %ptr, align 8
  ret void
}

LLVM fails to figure out that the load can be replaced with a constant (Probably because of the load bitcast pattern for pointer types).

@klensy
Copy link
Contributor

klensy commented Jun 7, 2021

It isn't only in String main thing there in const. The same can be seen in vec:

pub fn foo()->Vec<u8>{
    vec![]
}

pub fn foo()->Vec<u8>{
    vec![].clone()
}
example::foo:
        mov     rax, rdi
        mov     rcx, qword ptr [rip + .L__unnamed_1]
        mov     qword ptr [rdi], rcx
        xorps   xmm0, xmm0
        movups  xmmword ptr [rdi + 8], xmm0
        ret

.L__unnamed_1:
        .asciz  "\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"


example::foo:
        mov     rax, rdi
        mov     qword ptr [rdi], 1
        xorps   xmm0, xmm0
        movups  xmmword ptr [rdi + 8], xmm0
        ret

@JohnTitor JohnTitor added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jun 8, 2021
@apiraino
Copy link
Contributor

apiraino commented Jun 8, 2021

Assigning priority as discussed in the Zulip thread of the Prioritization Working Group.

@rustbot label -I-prioritize +P-high

@rustbot rustbot added P-high High priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Jun 8, 2021
@klensy
Copy link
Contributor

klensy commented Jul 9, 2021

This can be somewhat solved by using RawVec::new() directly, without pub const NEW: Self = Self::new(); hack, i.e. for Vec:

    pub const fn new() -> Self {
        Vec { buf: RawVec::new(), len: 0 }
    }

instead of

    pub const fn new() -> Self {
        Vec { buf: RawVec::NEW, len: 0 }
    }

but i been hit by

error: `RawVec::<T>::new` is not yet stable as a const fn
   --> library\alloc\src\vec\mod.rs:420:20
    |
420 |         Vec { buf: RawVec::new(), len: 0 }
    |                    ^^^^^^^^^^^^^
    |
    = help: Const-stable functions can only call other const-stable functions

error: aborting due to previous error

Tried solving this by placing rustc_allow_const_fn_unstable in some places, but no luck. Any suggestions?

@nbdd0121
Copy link
Contributor

nbdd0121 commented Jul 9, 2021

Seems like you hit #75794.

@lqd
Copy link
Member

lqd commented Jul 9, 2021

RawVec::new is pub, so maybe not ?

It could be because these stability changes are seen by the bootstrap compiler and not your own version of liballoc.

There are a few examples of attributes needing to be cfg_attred out until a new version of the compiler with your changes is used for bootstrap (and then these attributes are removed when that happens.)

Depending on whether you need to target the bootstrap compiler, or to "temporarily hide" your changes from it, it can look like #![cfg_attr(not(bootstrap), ...)].

@klensy
Copy link
Contributor

klensy commented Jul 9, 2021

@lqd
Tried to change Vec to:

    #[inline]
    #[rustc_const_stable(feature = "const_vec_new", since = "1.39.0")]
    #[stable(feature = "rust1", since = "1.0.0")]
    #[cfg(bootstrap)]
    pub const fn new() -> Self {
        Vec { buf: RawVec::NEW, len: 0 }
    }

    #[inline]
    #[rustc_const_stable(feature = "const_vec_new", since = "1.39.0")]
    #[stable(feature = "rust1", since = "1.0.0")]
    #[cfg(not(bootstrap))]
    pub const fn new() -> Self {
        Vec { buf: RawVec::new(), len: 0 }
    }

and RawVec to

    #[rustc_allow_const_fn_unstable(const_fn)]
    #[cfg_attr(not(bootstrap), rustc_const_unstable(feature="rawvec_const_new_fake_stable", issue="99999"))]
    pub const fn new() -> Self {
        Self::new_in(Global)
    }

with adding #![feature(rawvec_const_new_fake_stable)]

But this clearly wrong (tried few permutations of cfg's), as error the same.

@lqd
Copy link
Member

lqd commented Jul 9, 2021

Asking about how to handle this on zulip, e.g. in the t-libs stream, would probably be best then.

@klensy
Copy link
Contributor

klensy commented Jul 9, 2021

Found workaround, but this bloats rcgu size a lot, sadly.

@nikic
Copy link
Contributor

nikic commented Jan 21, 2022

Fixed upstream by llvm/llvm-project@6a19cb8.

@SaadiSave
Copy link

Fixed upstream by llvm/llvm-project@6a19cb8.

I assume that this will make its way into Rust with LLVM 14? Or will it become a patch release?

@nikic
Copy link
Contributor

nikic commented Jan 21, 2022

Fixed upstream by llvm/llvm-project@6a19cb8.

I assume that this will make its way into Rust with LLVM 14? Or will it become a patch release?

This will be part of the LLVM 14 upgrade.

@klensy
Copy link
Contributor

klensy commented Feb 19, 2022

Fixed by #93577 https://rust.godbolt.org/z/vMdfPzfT5, but test should be added, to check regressions in future.

@nikic nikic added E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. and removed P-high High priority regression-from-stable-to-stable Performance or correctness regression from one stable version to another. labels Feb 19, 2022
@nikic nikic removed their assignment Feb 19, 2022
JohnTitor added a commit to JohnTitor/rust that referenced this issue Dec 22, 2022
Signed-off-by: Yuki Okushi <jtitor@2k36.org>
jyn514 added a commit to jyn514/rust that referenced this issue Dec 31, 2022
Add regression test for rust-lang#86106

Closes rust-lang#86106
r? `@nikic`

Signed-off-by: Yuki Okushi <jtitor@2k36.org>
JohnTitor added a commit to JohnTitor/rust that referenced this issue Jan 7, 2023
Signed-off-by: Yuki Okushi <jtitor@2k36.org>
@bors bors closed this as completed in ee0412d Jan 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. I-slow Issue: Problems and improvements with respect to performance of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.