polyester 0.1.0

Parallel iterator adaptors that accept arbitrary iterators
Documentation
# polyester

some parallel iterator adapters for rust

~~Documentation~~ (coming soon!) | [(manually-updated docs for master)][doc-dev]

[doc-dev]: https://tonberry.quietmisdreavus.net/polyester-dev/polyester/

This work-in-progress library features an extenstion trait, `Polyester`, that extends `Iterator` and
provides some parallel operations that allow you to spread consumption of the iterator across
multiple threads.

To use this crate in your own project, add the following to your Cargo.toml:

```toml
[dependencies]
polyester = "0.1.0"
```

...and the following to your crate root:

```rust
extern crate polyester;

use polyester::Polyester;
```

## but wait, isn't that what `rayon` does?

Not quite. `rayon` is built on creating adaptors that fit its own `ParallelIterator` trait, which
can only be created from fixed-length collections. `polyester` wants to work from arbitrary
`Iterator`s and provide arbitrary `Iterator`s back (or consume them and provide some appropriate
output). Basically, it's purely an extension trait for anything that already implements `Iterator`.
This means that there are distinct design goals for `polyester` that give it different performance
characteristics from `rayon`.

## architecture overview

The internal design of this library changes pretty rapidly as i consider the ramifications of
various design decisions, but you're welcome to take a look and offer suggestions to improve
performance or suggest additional adaptors. For the moment, this is the basic idea, as of this
writing (2017-10-30):

The major problem with wanting to spread an iterator's values across threads is that the iterator
itself becomes a bottleneck. There is only one source of iteration state, and it requires a `&mut
self` borrow to get the next item. The way that `polyester` currently deals with this is placing the
iterator into a background thread so it can be loaded into per-thread queues, which the worker
threads pick up at the same time.

There's a major drawback to this approach, though: If the worker threads are not expected to be
performing a lot of per-item work, this will always be slower than just doing it sequentially.
Therefore, the current version of `polyester` is only recommended if you need to perform intensive
(or erratically intensive) work per item, or cannot afford to work sequentially or collect the
iterator beforehand (to hand off to `rayon` instead). Note that if you have an expensive
*sequential* operation in the iterator before you hand it off to `polyester`, that will impact how
fast the cache-filler can generate items.

Anyway, once this "hopper" is prepared, handles to it are given to a number of worker threads, so
that they can run user-supplied closures on the items. Each thread has its own queue to load items
from, and if its own queue is empty it will begin walking forward through other thread's queues to
attempt to load more items before waiting. Each queue has an associated `SignalEvent` (from
`synchronoise`) which the cache-loader worker will signal periodically while filling items or once
the iterator has been exhausted.

From here, each adaptor has its own processing:

### `par_fold`

`par_fold` performs a basic folding operation with its items: Start from a `seed` accumulator, and
iteratively add items to it until you're done. However, since there are multiple folds happening at
the same time, there needs to be an additional step where all the "intermediate" accumulators are
brought together. This is done by offering each worker thread a channel to hand their finished
accumulator back to the calling thread, which performs the "outer fold" to merge all the
accumulators together.

### `par_map`

`par_map` has a simpler goal with a slightly more complicated implementation: Since it wants to turn
the parallel iterator back into a sequential one, it performs the given map in each thread before
handing each item back to another channel, whose `Receiver` is used in the corresponding `ParMap`
`Iterator` implementation.

Note that due the nature of the hopper creation and processing, **order is not guaranteed** for the
items that come out of either adapter. (In fact, for `par_fold` each thread is basically getting
every Nth item, and the `outer_fold` will need to reconstruct the ordering if it wants it.)