Use a simple heap / sift down implementation to optimize kmerge
Use a simple heap / sift down implementation to optimize kmerge

A hand rolled heap implementation allows us to improve kmerge. This
follows Frank McSherry's implementation (differential-dataflow project).

Use a sift down that places elements eagerly (alternate strategy would
be to sift down all the way to the bottom, then sift back up).

Keep the iterator in place, and sift it down from the top. This assumes
the current minimal iterator is unlikely to move very far to adjust for
its new element. This should perform well if there is a run of least
elements from the same iterator.

Benchmark

Large wins on the small (2 iterators) bench because we can avoid lots of
swaps. The "tenway" bench uses random elements, so iterators should
alternate a lot, so it's somewhat of a worst case.

```
before

test kmerge_default        ... bench:     123,363 ns/iter (+/- 15,518)
test kmerge_tenway         ... bench:     908,695 ns/iter (+/- 270,372)

after

test kmerge_default        ... bench:      13,738 ns/iter (+/- 3,318)
test kmerge_tenway         ... bench:     380,020 ns/iter (+/- 9,921)
```

imp/rust-itertools,rust-itertools/itertools