ruby/ruby — pull request #596 by srawlins (2014-04-14 23:21:27+00:00)
Title: Enumerable#each_slice and #each_cons should use fewer Arrays

Currently `#each_slice` creates a new Array each time it yields, to hold the elements (`each_slice_i`). However, if the block given to `#each_slice` immediately separates the elements into multiple block arguments, then that new Array is immediately discarded, when it could have been used for every yield from `#each_slice`. Take these two examples:

```ruby
arrays = []
(1..10).each_slice(3) { |a| arrays << a }

(1..10).each_slice(3) { |a,b,c| sum = a+b+c }
```

In the first example, the yielded Array _is_ `a`, so we can't reuse the allocated Array for the next yield (we must create a new one). In the second example, however, Ruby immediately separates the elements of the yielded Array into `a`, `b`, and `c`, discarding the Array instance. This can be made faster. (Everything above is true for `#each_cons` as well.)

#### The patch

This patch uses `rb_block_arity()` to test how many arguments the given block can take, and only creates new Arrays if the arity is 1 or -1 (this should mean that the block either takes _exactly 1_ argument (no separation), _only a splat_ argument (no separation), or is a lambda with 0 required arguments (unknown separation). [#arity docs](http://www.ruby-doc.org/core-2.1.1/Proc.html#method-i-arity). This performance trick is already used in [Hash#each_pair](https://github.com/ruby/ruby/blob/trunk/hash.c#L1611):

```ruby
if (rb_block_arity() > 1)
rb_hash_foreach(hash, each_pair_i_fast, 0);
else
rb_hash_foreach(hash, each_pair_i, 0);
```

#### Speedup

In a micro-benchmark, the speedup is decent, approximately 30% for arg-separating blocks. Here are some numbers for `#each_cons`. Scores are seconds of user-time (smaller is better) running variants of `200_000.times { (1..100).each_cons(3) { |a,b,c| sum = a+b+c } }`. Some accept 1 arg, and some accept 3 args. Some used a block, some a proc, and one used a lambda:

```
yield recipient trunk patch
block (1 arg) 5.40 4.53
block (3 args) 4.00 3.07
proc (1 arg) 4.77 4.52
proc (3 args) 4.06 3.04
lambda (3 args) 3.86 2.84
```