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

Smarter HashMap/HashSet pre-allocation for extend/from_iter #38017

Merged
merged 1 commit into from
Dec 7, 2016

Conversation

arthurprs
Copy link
Contributor

@arthurprs arthurprs commented Nov 26, 2016

HashMap/HashSet from_iter and extend are making totally different assumptions.

A more balanced decision may allocate half the lower hint (rounding up). For "well defined" iterators this effectively limits the worst case to two resizes (the initial reserve + one resize).

cc #36579
cc @bluss

@rust-highfive
Copy link
Collaborator

r? @aturon

(rust_highfive has picked a reviewer for you, use r? to override)

let iter = iter.into_iter();
let hint = iter.size_hint().0;
self.reserve((hint / 2) + (hint & 1));
for (k, v) in iter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not compile :P

Might be simpler to just call down to self.map.extend(iter.into_iter().map(|k| (k, ())).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally butchered the set part... will amend in a bit.

@arthurprs arthurprs force-pushed the hm-extend branch 2 times, most recently from e16ca98 to a3e58ca Compare November 26, 2016 19:25
@arthurprs
Copy link
Contributor Author

I wonder if it should try to take into consideration the upper bound.

// will only resize twice in the worst case.
let iter = iter.into_iter();
let hint = iter.size_hint().0;
self.reserve((hint / 2) + (hint & 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually to round up you add (denom-1) before the division, as in (hint + 1) / 2.
(Note that the current code is correct, it is just a little unusual and it would require nontrivial changes if the divisor was changed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes much more sense, will fix.

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Nov 28, 2016
@bluss
Copy link
Member

bluss commented Nov 29, 2016

What I wanted to do here was to try this on our favourite hashmap workload (rustc) and see what the effect is. Haven't had time to do it yet.

@arthurprs
Copy link
Contributor Author

That's a great idea, thanks!

@bluss
Copy link
Member

bluss commented Nov 30, 2016

It's noisy, results are a bit all over the place (So they are about the same).

./compare.py ~/src/rust/build-before-hashmap/x86_64-unknown-linux-gnu/stage1/bin/rustc ~/src/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc
futures-rs-test  5.152s vs  5.029s --> 1.024x faster (variance: 1.006x, 1.021x)
helloworld       0.251s vs  0.279s --> 0.899x faster (variance: 1.645x, 1.277x)
html5ever-2016-  6.668s vs  6.813s --> 0.979x faster (variance: 1.029x, 1.009x)
hyper.0.5.0      5.995s vs  5.935s --> 1.010x faster (variance: 1.015x, 1.034x)
inflate-0.1.0    4.583s vs  4.560s --> 1.005x faster (variance: 1.032x, 1.008x)
issue-32062-equ  0.341s vs  0.373s --> 0.913x faster (variance: 1.182x, 1.242x)
issue-32278-big  2.052s vs  2.076s --> 0.988x faster (variance: 1.054x, 1.033x)
jld-day15-parse  1.654s vs  1.624s --> 1.018x faster (variance: 1.098x, 1.059x)
piston-image-0. 13.449s vs 13.717s --> 0.981x faster (variance: 1.010x, 1.024x)
regex-0.1.80    10.920s vs 10.854s --> 1.006x faster (variance: 1.016x, 1.009x)
regex.0.1.30     2.928s vs  2.862s --> 1.023x faster (variance: 1.014x, 1.020x)
rust-encoding-0  2.359s vs  2.360s --> 1.000x faster (variance: 1.011x, 1.039x)
syntex-0.42.2    0.069s vs  0.077s --> 0.900x faster (variance: 1.312x, 1.156x)

For example, we can retry piston-image and syntex:

piston-image-0. 13.462s vs 13.443s --> 1.001x faster (variance: 1.012x, 1.025x)
syntex-0.42.2    0.071s vs  0.081s --> 0.887x faster (variance: 1.241x, 1.216x)

Why is syntex so quick, what happened with that?

I also looked at memory usage in time-passes for piston-image and it is the same before and after.

@arthurprs
Copy link
Contributor Author

Thanks @bluss!

If we want to be extra sure not to regress we can revert the from_iter part, or,

reserve the entire hint on extend if the hashmap is empty, since it's essentially the same runtime cost (except memory of course).

@bluss
Copy link
Member

bluss commented Nov 30, 2016

According to the result, something is regressing in the super small crates (hello world, issue-32062-equ, and syntex) and the rest are as good as unchanged.

@arthurprs
Copy link
Contributor Author

I changed it to reserve everything if the map is empty, so if the regression was due to slower from_iter it should now be fixed.

@bluss
Copy link
Member

bluss commented Dec 6, 2016

@bors r+

Let's go with this; extend will reserve the size hint's lower bound by half.

@bors
Copy link
Contributor

bors commented Dec 6, 2016

📌 Commit 2c5d240 has been approved by bluss

@bluss
Copy link
Member

bluss commented Dec 6, 2016

@bors r-

Oops, I guess T-libs means that the libs team want to discuss it first?

@alexcrichton
Copy link
Member

@bors: r=bluss

oh I actually just meant that as categorization, this is fine to go through without libs discussion :)

@bors
Copy link
Contributor

bors commented Dec 6, 2016

📌 Commit 2c5d240 has been approved by bluss

@bluss
Copy link
Member

bluss commented Dec 6, 2016

great, thanks for the info

@bors
Copy link
Contributor

bors commented Dec 6, 2016

⌛ Testing commit 2c5d240 with merge 5f128ed...

bors added a commit that referenced this pull request Dec 6, 2016
Smarter HashMap/HashSet pre-allocation for extend/from_iter

HashMap/HashSet from_iter and extend are making totally different assumptions.

A more balanced decision may allocate half the lower hint (rounding up). For "well defined" iterators this effectively limits the worst case to two resizes (the initial reserve + one resize).

cc #36579
cc @bluss
@bors bors merged commit 2c5d240 into rust-lang:master Dec 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants