seattlerb/minitest — pull request #360 by tenderlove (2013-10-31 21:27:10+00:00)
Title: Use a Queue for scheduling parallel tests

I've changed the parallelized tests to use a Queue and removed ParallelEach. Currently, minitest uses N*N threads to do its work (N `ParallelEach` objects spawn N more `ParallelEach` objects). Using N*N threads causes contention on machines that don't have a perfect square number of cores (e.g. 2 core or 8 core machines). The implementation in this PR scales linearly, so you can run it on any number core machines with minimal contention.

In the current implementation, if you call `parallelize_me!` it would monkey patch the recorder object such that it [had to synchronize on every call to `record`](https://github.com/seattlerb/minitest/blob/931fa486aa251302483e73859e82b6ec462613a2/lib/minitest/parallel_each.rb#L101-L105). In this implementation, the [serial tests are all run before the parallel tests](https://github.com/tenderlove/minitest/compare/ts#diff-efcebb0b0e7d5c021792f400fd891d9fR163). This makes it so the serial tests never have to lock (or check for a lock) and the parallel tests always lock.

I did the same thing with the IO locks, we only lock when we need to.

I think the merits of this pull request are:

1. It's a smaller implementation
2. The number of threads scale linearly
3. Calling `parallelize_me!` does not punish test classes that are run in serial
4. Only lock when we need to

Other fun parts is that since we use a central queue, we can write extensions that change the queue implementation (say to DRb or some other distribution thing).

Downside:

1. I removed `ParallelEach`, so anyone that is using the class directly cannot use it.
2. I'm not super thrilled about [my `parallelize_me!` implementation](https://github.com/tenderlove/minitest/compare/ts?expand=1#diff-444fa8babd8886c012733b064d709db5L49).

I've been using this test to demo CPU contention:

```ruby
require 'minitest/autorun'
require 'zlib'

S = ("a".."z").to_a.join * 20_000_000

4.times do
Class.new(MiniTest::Test) do
parallelize_me!

6.times do |i|
define_method("test_zlib_#{i}") do
Zlib::Deflate.deflate(S)
end
end
end
end
```

On an 8 core machine, my implementation will do the same work with 8 threads that master does with 16 threads.

One thing I think we could do in the future is to always use a queue, but change the number of threads in the thread pool depending on whether we're running parallel or serial tests. That would eliminate most of the code in `parallelize_me!` and simplify the superclass.