Skip to content

Implement Rayon traits (optional) #45

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

Merged
merged 13 commits into from
Aug 20, 2019
Merged

Conversation

cuviper
Copy link
Member

@cuviper cuviper commented Nov 20, 2017

  • IntoParallelIterator by value, ref, and ref mut
    • Implements both ParallelIterator and IndexedParallelIterator
  • FromParallelIterator
  • ParallelExtend by value and ref copy

@cuviper cuviper mentioned this pull request Nov 20, 2017
src/rayon.rs Outdated
vec
})
.collect()
}
Copy link
Member

Choose a reason for hiding this comment

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

Loll wut!! LinkedList is used in a real-life PR?!! 🥇 🐫 🎈 :shipit:

(Ahem I'll be back to my regular @bluss in a bit.)

Copy link
Member Author

Choose a reason for hiding this comment

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

You know, it's funny, for all the strategies we benchmarked for Rayon collect, I don't think we actually tried a Vec<Vec<T>> intermediate. I just assumed that a list was best for this purpose because of its O(1) append. I'm going to have to do some testing now...

Copy link
Member Author

Choose a reason for hiding this comment

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

I've kept the LinkedList, but now collect it with map-reduce -- see rayon-rs/rayon#479.

Copy link
Member

@bluss bluss left a comment

Choose a reason for hiding this comment

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

Hi, I'll review this sooner or later. Not Either-PR late. Just not tonight. Thanks for the solid PR.

src/rayon.rs Outdated

// NB: This allows indexed collection, e.g. directly into a `Vec`, but the
// underlying iterator must really be indexed. We should remove this if we
// start having tombstones that must be filtered out.
Copy link
Member

Choose a reason for hiding this comment

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

Great comment.

@bluss bluss self-assigned this Nov 20, 2017
bors bot added a commit to rayon-rs/rayon that referenced this pull request Nov 21, 2017
479:  ParallelExtend with map-reduce  r=cuviper a=cuviper

We were using a `LinkedList<Vec<_>>` intermediary for general `ParallelExtend` implementations, gathered using a `fold` to a vector and `collect` the list -- effectively a `fold`-`fold`-`reduce`.  Now we explicitly `fold`-`map`-`reduce` it for better performance.

Thanks to @bluss for [ridiculing](indexmap-rs/indexmap#45 (comment)) the use of `LinkedList`, which got me to look at this again.  At first I tried `Vec<Vec<_>>` instead, which did perform better, but I had to do this using `fold`-`map`-`reduce` to avoid infinite `collect` recursion.  Then I tried that same tweak on the original code, finding it slightly better yet.  So I stand by the use of `LinkedList`. 😄

```
test map_collect::i_mod_10_to_i::with_linked_list_collect_vec_sized     ... bench:   5,112,790 ns/iter (+/- 78,988)
test map_collect::i_mod_10_to_i::with_linked_list_map_reduce_vec_sized  ... bench:   4,954,336 ns/iter (+/- 103,285)
test map_collect::i_mod_10_to_i::with_vec_vec_sized                     ... bench:   4,965,241 ns/iter (+/- 80,797)
test map_collect::i_to_i::with_linked_list_collect_vec_sized            ... bench:   9,837,088 ns/iter (+/- 283,665)
test map_collect::i_to_i::with_linked_list_map_reduce_vec_sized         ... bench:   9,687,170 ns/iter (+/- 328,236)
test map_collect::i_to_i::with_vec_vec_sized                            ... bench:   9,690,253 ns/iter (+/- 242,379)
test vec_collect::vec_i::with_linked_list_collect_vec_sized             ... bench:  12,639,762 ns/iter (+/- 260,212)
test vec_collect::vec_i::with_linked_list_map_reduce_vec_sized          ... bench:   9,805,660 ns/iter (+/- 170,434)
test vec_collect::vec_i::with_vec_vec_sized                             ... bench:   9,904,740 ns/iter (+/- 226,382)
test vec_collect::vec_i_filtered::with_linked_list_collect_vec_sized    ... bench:  12,486,357 ns/iter (+/- 239,281)
test vec_collect::vec_i_filtered::with_linked_list_map_reduce_vec_sized ... bench:   9,794,605 ns/iter (+/- 306,361)
test vec_collect::vec_i_filtered::with_vec_vec_sized                    ... bench:   9,986,733 ns/iter (+/- 1,142,244)
```
@bluss
Copy link
Member

bluss commented Nov 22, 2017

As awesome as this is, it does interfere with easy 1.0'ing of ordermap.

@cuviper
Copy link
Member Author

cuviper commented Nov 22, 2017

Would you want to wait for rayon-1.0? Or would you hesitate even then?

Another idea is to add as_slice(), as_mut_slice(), and into_vec(), and put a public API on the Bucket, so people could themselves write something like:

omap.as_slice().into_par_iter()
    .map(|b| (b.key(), b.value())))
    .for_each(|(k, v)| ...);

That's not as ergonomic, but at least it would be possible to use Rayon with ordermap efficiently, and maybe enable other cool uses too!
(tombstones would make this uglier though.)

@bluss
Copy link
Member

bluss commented Nov 22, 2017

With rayon-1.0 I would not hesitate, but you're right, one could be worried about the version entanglement even then.

For the record, we've decided that OrderMap will stay as it is with its current properties, indexing, exact size = no tombstones (and probably the same name too, if we don't find a better one). This data structure is useful as it is, so to implement a similar one with tombstones, it will be a new thing. We could end up with both.

@bluss
Copy link
Member

bluss commented Nov 22, 2017

W.r.t order I'm very fond of a deterministic order (hash function independent).

I had a different thought -- would it be possible to implement shared and mutable views for OrderMap. So that it would natively support splitting (by index) into two parts or even slicing. Both the shared and mutable view would mean shared/mutable access to the elements only, not inserting or removing elements. I think that means that the mutable views can all have the same shared immutable reference to the index/hash vector.

The idea includes allowing lookup by key and indexing in a view.

In a mutable view, the implementation peeking into another view's part of the entries vector would be verboten.

@cuviper
Copy link
Member Author

cuviper commented Nov 22, 2017

I think views are a neat idea! It wouldn't directly enable rayon, but it could let there be something like ordermap-parallel, like you did with ndarray and ndarray-parallel.

@vitiral
Copy link

vitiral commented Jan 3, 2018

Just a thought: IMO feature flags should be able to be independent of the crate stability. I.e. you can advertise that "rayon feature flag is supported but is beta".

@bluss
Copy link
Member

bluss commented Jan 3, 2018

Yes, maybe. I think the feature flag must have unstable in its name for it to work. It's different viewpoints among users in the role of docs vs formal API in defining the stable interface.

@cuviper
Copy link
Member Author

cuviper commented Feb 16, 2018

I've now rebased, updated to rayon 1.0, and added the basic set implementations.

@bluss
Copy link
Member

bluss commented Feb 19, 2018

That's cool! I'm fine with merging this, but I haven't looked through it in fine detail. rayon 1.0 is quite the thing, and maybe we can dare to go 1.0 with it as dependency. (Congratulations!)

@cuviper
Copy link
Member Author

cuviper commented Feb 19, 2018

I was also thinking of par_eq, par_is_disjoint, par_is_subset, and par_is_superset, as these are all operations that have to (potentially) iterate the whole set. This might be too much par_ though. 😜

The only thing left would be drain, which we haven't really addressed at all in rayon. I'm thinking about adding a general ParDrain trait, but I'm not sure.

Anyway, I should at least write some documentation and tests here before we merge. 😅

One specific API question for you is whether it's good to have these Par* types re-exported in the main map and set modules, next to their serial counterparts. It might be better in separate modules, perhaps even public from the indexmap::rayon::map/set where they're currently implemented, and remove Par from their names. (like Rayon does for std iterator types)

@bluss
Copy link
Member

bluss commented Feb 21, 2018

This PR is clearly amazing. I think this is a great feature set for a hashmap library and in safe Rust no less (thanks to the giants of std::vec::Vec and rayon).

I think that's a lot of functions. I'd probably drop the comparisons and par_sorted_by (sorted_by would probably not have existed if sort_by was realized first? It is a tad more efficient, than sort_by though..). I'm just wondering if we can get most of the value with less API.

I don't know about the module and name prefix question. Mirroring std fits well in the rayon crate. Here I don't mind either way.

@ConnorGray
Copy link

What's the status of this pull request? I'm a fan of IndexMap, but right now I'm in a situation where I'd really like to be able to use rayon to get a performance boost, and unfortunately can't.

@cuviper
Copy link
Member Author

cuviper commented Oct 29, 2018

@ConnorGray -- It's kind of in that "Does anyone care?" limbo, so your direct request will help motivate me to revisit this. 🙂

I need to rebase it, at least, and then make some decision about those API questions. If you have any input, feel free to share your thoughts (but no worries if you're indifferent to the specifics).

@ConnorGray
Copy link

@cuviper Wow the quick response is much appreciated :)

I don't have a much input -- I'm just hoping to take advantage of rayon's accessible parallelism (where not having to worry about the details is a selling point) with the determinism I get from using an IndexMap everywhere internally.

I imagine there might be others in the same boat, who like IndexMap for it's determinism, but would also like to operate on the values in parallel.

@bluss
Copy link
Member

bluss commented Nov 21, 2018

I think this is good. Rayon is rock solid 😉 and doesn't feel like a risk. The code is simple.

Only review comment that is important to fix, is that the new methods (par_keys etc) need to be listed last in the docs, not first in the docs. I'd enable the rayon feature in docs.rs generation and make sure items are labelled in docs to require that feature (I can do that).

@garro95
Copy link

garro95 commented Apr 29, 2019

What's the state of this? I would like to use this feature

@cuviper
Copy link
Member Author

cuviper commented Aug 20, 2019

I think this is ready for reconsideration.

Copy link
Member

@bluss bluss left a comment

Choose a reason for hiding this comment

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

This is great, ready to merge! When the trivial conflict in .travis.yml is fixed

@cuviper
Copy link
Member Author

cuviper commented Aug 20, 2019

OK, that conflict was just in the compat downgrades, which #97 removed.

Rebased without that, and CI passed -- r=bluss

@cuviper cuviper merged commit c9fb28f into indexmap-rs:master Aug 20, 2019
@bluss
Copy link
Member

bluss commented Aug 20, 2019

Great. I can do the release of 1.1.0

@cuviper cuviper deleted the rayon branch July 23, 2020 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants