Optimize str.escape_default().to_string() - #159916
Conversation
|
rustbot has assigned @Mark-Simulacrum. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Escape default to string
escape_default.to_string()str.escape_default().to_string()
d84886a to
b174232
Compare
|
💔 Test for cc7c194 failed: CI. Failed job:
|
This comment has been minimized.
This comment has been minimized.
b174232 to
7607658
Compare
|
@bors try |
This comment has been minimized.
This comment has been minimized.
Optimize `str.escape_default().to_string()`
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (04d7a20): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 4.1%, secondary -1.9%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary 2.3%, secondary 0.0%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeResults (primary 0.5%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Bootstrap: 489.346s -> 491.932s (0.53%) |
|
A perhaps obvious question: would it be worth it to perform to check first whether escape is required, and only then allocate a It's only pertinent, of course, if this function is regularly enough called with symbols which do not require escaping. But the idea of allocating a Implementation-wise, this can still be single-pass:
|
|
Sadly, no, |
I'm confused as to how that relates to escaping I'm not talking about in-place escaping, but avoiding allocating a new string when there's nothing to escape... |
|
Oh, for |
|
I also had that idea, but forgot to benchmark it. I'll try it. |
|
Skipping the escaping is a win for |
This seems to be benchmarking a slightly different algorithm. The one I have in mind would be: fn requires_escape(b: &u8) -> bool {
match *b {
b'\\' |
b'\'' |
b'"' => true,
b'\x20'..=b'\x7e' => false,
_ => true
}
}
let Some(position) = s.as_bytes().iter().position(requires_escape) else {
return symbol;
};
let (prefix, suffix) = s.split_at(position);
let mut escaped = String::with_capacity(s.len());
escaped.push_str(prefix);
escaped.extend(suffix.escape_default());Which has 2 essential differences:
So I do think it'd be worth benchmarking again. (Side note: why checking |
|
It might be possible to do this as a specialization for the |
Good point, I thought about that too, I'll try it. Though my previous point still stands, there's no free lunch. I'll try to benchmark cases where escaping has to happen, to see how much would the heuristic slow it down. |
In particular, I find annoying that the logic of which characters (well, bytes) require escaping is duplicated between Ideally, we'd want a function like
I would hope it only speeds things up (modulo a few cycles), since every byte but the one boundary byte is only examined once. |
|
#112049 (comment) the perf. wins are much better with your method. I'll try once more with using |
|
The results hold even with Neat. |
|
It is not because of the specialization, both because the benchmark was not based on this PR, and also because this specialization probably doesn't even apply unless you use |
It also applies to |
|
I did some benchmarks locally. I took the 30 MiB file from The data below shows instruction counts. In the unlucky (unlikely?) case where the string is long, and escaping must happen from the start, using So I think that we should land the start skipping, and then either continue using manual escaping, or add support for @matthieu-m Since it is essentially your code, do you want to send a PR? :) |
|
@Kobzol I have fairly little time available at the moment, so I'm happy for anyone to pick this up rather than wait on me. (I'm also unsure which approach is best, I feel like @the8472's suggestion of having the standard library returning |
|
Ok, opened #160453. Added you as a co-author of the commit. |
Add fast path to `escape_string_symbol` Discussed in rust-lang#159916. So far used the manual escaping variant. CC @matthieu-m r? the8472
|
☔ The latest upstream changes (presumably #160506) made this pull request unmergeable. Please resolve the merge conflicts by rebasing. |
Rollup merge of #160453 - Kobzol:include-blob-opt, r=the8472 Add fast path to `escape_string_symbol` Discussed in #159916. So far used the manual escaping variant. CC @matthieu-m r? the8472
View all comments
reverts #159609 and implements the optimization in std instead.
bench results in my machine: