[QUIZ] FasterGenerator (#66)

C

Caleb Clausen

------=_Part_628_1431551.1139777415689
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Here's my solution for this week's quiz. Normally, I just watch the
quizzing, but this one happens to be one that I'm already doing, as
part of a larger library of external iterators.

The key insight is to realize that Continuations were used in the
original because an independant call stack is needed (to run #each
in), separate from the call stack of the user of Generator. However,
Continuations are only one way to get another call stack; you could
also use a Thread, which doesn't have the same performance problems
in ruby.

In order to implement Generator#current, I had to invent Queue#peek,
which tells you what the next element is without taking it from the
Queue. Actually, I'm using a SizedQueue, not a plain Queue. Otherwise,
the memory used by the queue could grow without bounds.

#rewind was also a small challenge, until I realized that you could
just restart the Thread. (Hopefully, the enum or block will return
the same results the second time through.)

It's not allowed in the original, but this version permits you to
pass both an enum and a block to the generator. The results of the
block are passed up once the enum is exhausted.

Another interesting new feature is Generator#<<, which allows you to
add more items to the Generator once it has been created. This was
originally a misunderstood implementation of yield.

It's clear that this version is faster than the original callcc-
based Generator, but I'm not sure how to compare with James'
results. I was unable to run his benchmark to completion on my
machine. Somewhat modified, I see differences of around 3 orders
of magnitude, but performance of the callcc-based version seems
non-linearly dependant on input size.

I also found that bumping the queue size up to 400 from the original
32 made about a 4x difference in running time. I guess context
switches are expensive...

I am curious to see how James made his own solution so blazing fast.

The requirement for synchronous generators took me by surprise at
the last minute. It was easy enough to add synchronicity, but it
looks like there would be a performance cost from the extra
context switches. And is maybe not needed in the majority of cases.
So, I put the synchronous capability in a subclass. Sure enough,
when I ran the benchmark, the synchronous version pretty much
wipes out any performance gain from the bigger queue.

Benchmarks: (take with a grain of salt)

### Construction ###

Rehearsal -----------------------------------------------------------------
Caleb's Generator 0.020000 0.000000 0.020000 ( 0.015167)
Caleb's Synchronous Generator 0.000000 0.000000 0.000000 ( 0.003251)
Old callcc Generator 0.000000 0.000000 0.000000 ( 0.004067)
-------------------------------------------------------- total: 0.020000sec

user system total real
Caleb's Generator 0.010000 0.000000 0.010000 ( 0.014414)
Caleb's Synchronous Generator 0.010000 0.000000 0.010000 ( 0.003384)
Old callcc Generator 0.000000 0.000000 0.000000 ( 0.004027)

### next() ###

Rehearsal -----------------------------------------------------------------
Caleb's Generator 0.050000 0.000000 0.050000 ( 0.092768)
Caleb's Synchronous Generator 0.270000 0.000000 0.270000 ( 0.306566)
each 0.000000 0.000000 0.000000 ( 0.000732)
Old callcc Generator 8.410000 0.960000 9.370000 ( 10.738060)
-------------------------------------------------------- total: 9.690000sec

user system total real
Caleb's Generator 0.030000 0.000000 0.030000 ( 0.069023)
Caleb's Synchronous Generator 0.200000 0.000000 0.200000 ( 0.256574)
each 0.000000 0.000000 0.000000 ( 0.000392)
Old callcc Generator 7.400000 0.960000 8.360000 ( 8.679449)

------=_Part_628_1431551.1139777415689
Content-Type: application/octet-stream; name="mygenerator.rb"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="mygenerator.rb"
X-Attachment-Id: file0

CnJlcXVpcmUgJ3RocmVhZCcKCmNsYXNzIFF1ZXVlCiAjIFJldHJpZXZlcyBuZXh0IGRhdGEgZnJv
bSB0aGUgcXVldWUsIHdpdGhvdXQgcHVsbGluZyBpdCBvZmYgdGhlIHF1ZXVlLgogIyBJZiB0aGUg
cXVldWUgaXMgZW1wdHksIHRoZSBjYWxsaW5nIHRocmVhZCBpcwogIyBzdXNwZW5kZWQgdW50aWwg
ZGF0YSBpcyBwdXNoZWQgb250byB0aGUgcXVldWUuIAogIyBJZiArbm9uX2Jsb2NrKyBpcyB0cnVl
LCB0aGUKICMgdGhyZWFkIGlzbid0IHN1c3BlbmRlZCwgYW5kIGFuIGV4Y2VwdGlvbiBpcyByYWlz
ZWQuCiAgZGVmIHBlZWsobm9uX2Jsb2NrPWZhbHNlKQogICAgcmFpc2UgVGhyZWFkRXJyb3IsICJx
dWV1ZSBlbXB0eSIgaWYgbm9uX2Jsb2NrIGFuZCBlbXB0eT8KICAgIFRocmVhZC5wYXNzIHdoaWxl
IChlbXB0eT8pCiAgICBUaHJlYWQuY3JpdGljYWw9dHJ1ZQogICAgICByZXN1bHQ9QHF1ZS5maXJz
dAogICAgVGhyZWFkLmNyaXRpY2FsPWZhbHNlCiAgICByZXN1bHQKICBlbmQKZW5kCgoKY2xhc3Mg
TXlHZW5lcmF0b3IKICAgIGRlZiBpbml0aWFsaXplKGVudW09W10scXNpemU9NDAwLCZibG9jaykK
ICAgICAgQGV4dHJhcz1bXQogICAgICBAZXh0cmFzbXV0ZXg9TXV0ZXgubmV3CiAgICAgIGluaXQo
ZW51bSxxc2l6ZSwmYmxvY2spCiAgICBlbmQKICAgICAgICAKICAgIGRlZiBpbml0KGVudW0scXNp
emUsJmJsb2NrKQogICAgICBAYmxvY2s9YmxvY2sKICAgICAgQHBvcz0wCiAgICAgIEBlbnVtPWVu
dW0KICAgICAgQHE9cT1TaXplZFF1ZXVlLm5ldyhxc2l6ZSkKICAgICAgQHRocmVhZD1UaHJlYWQu
bmV3ewogICAgICAgIHN0b3BfdGhyZWFkCiAgICAgICAgZW51bS5lYWNoe3xpdGVtfCAKICAgICAg
ICAgIHE8PGl0ZW0gCiAgICAgICAgICBzdG9wX3RocmVhZAogICAgICAgIH0KICAgICAgICBibG9j
a1tzZWxmXSBpZiBibG9jawogICAgICAgIGk9MAogICAgICAgIHdoaWxlIGk8QGV4dHJhcy5zaXpl
CiAgICAgICAgICBxLnB1c2ggQGV4dHJhc211dGV4LnN5bmNocm9uaXplIHsgQGV4dHJhc1tpXSB9
CiAgICAgICAgICBpKz0xCiAgICAgICAgZW5kCiAgICAgIH0KICAgIGVuZAoKICAgICNubyBzeW5j
aHJvbml6YXRpb24gd2l0aCB0aHJlYWQgYnkgZGVmYXVsdAogICAgZGVmIHN0b3BfdGhyZWFkOyBl
bmQKICAgIGRlZiBzdGFydF90aHJlYWQ7IGVuZAogICAgICAgIAogICAgI3Nob3VsZCBvbmx5IGJl
IGNhbGxlZCBmcm9tIGluc2lkZSBjb25zdHJ1Y3RvcidzIGJsb2NrCiAgICBkZWYgeWllbGQoaXRl
bSkKICAgICAgQHEucHVzaCBpdGVtICAgIAogICAgICBzdG9wX3RocmVhZAogICAgZW5kCgogICAg
ZGVmIDw8KGl0ZW0pCiAgICAgIEBleHRyYXNtdXRleC5zeW5jaHJvbml6ZSB7IEBleHRyYXM8PGl0
ZW0gfQogICAgZW5kCgogICAgZGVmIGJlZ2luIQogICAgICBAdGhyZWFkLmtpbGwKICAgICAgaW5p
dChAZW51bSxAcS5tYXgsJkBibG9jaykKICAgICAgMAogICAgZW5kCiAgICAKICAgIGRlZiByZWFk
YWhlYWQxCiAgICAgIHJhaXNlIEVPRkVycm9yIGlmIGVvZj8KICAgICAgc3RhcnRfdGhyZWFkCiAg
ICAgIEBxLnBlZWsKICAgIHJlc2N1ZSBUaHJlYWRFcnJvcjoKICAgICAgcmFpc2UgRU9GRXJyb3IK
ICAgIGVuZAoKICAgIGRlZiByZWFkMQogICAgICBzdGFydF90aHJlYWQKICAgICAgcmVzdWx0PUBx
LnBvcAojICAgICAgcmFpc2UgRU9GRXJyb3IgaWYgIXJlc3VsdCAmJiBlb2Y/CiAgICAgIEBwb3Mr
PTEKICAgICAgcmVzdWx0CiAgICByZXNjdWUgVGhyZWFkRXJyb3I6CiAgICAgIHJhaXNlIEVPRkVy
cm9yCiAgICBlbmQKICAgIAogICAgZGVmIGVvZj8KICAgICAgc3RhcnRfdGhyZWFkIHdoaWxlIEBx
LmVtcHR5PyBhbmQgQHRocmVhZC5hbGl2ZT8KICAgICAgQHEuZW1wdHk/IGFuZCAhQHRocmVhZC5h
bGl2ZT8KICAgIGVuZAoKICAgIGRlZiBlYWNoKCZibG9jaykKICAgICAgYmVnaW4hCiAgICAgIHdo
aWxlKHNlbGYubmV4dD8pCiAgICAgICAgYmxvY2suY2FsbCByZWFkMQogICAgICBlbmQKICAgIGVu
ZCAgICAKICAgIGluY2x1ZGUgRW51bWVyYWJsZQogICAgCiAgICBhdHRyIDpwb3MKCiAgICAjbWV0
aG9kcyBmb3IgR2VuZXJhdG9yIGNvbXBhdGliaWxpdHk6CiAgICBkZWYgcmV3aW5kOyBiZWdpbiE7
IHNlbGYgZW5kCiAgICBhbGlhcyBjdXJyZW50IHJlYWRhaGVhZDEKICAgIGFsaWFzIG5leHQgcmVh
ZDEKICAgIGFsaWFzIGVuZD8gZW9mPwogICAgYWxpYXMgaW5kZXggcG9zCiAgICBkZWYgbmV4dD87
ICFlbmQ/IGVuZAplbmQKCmNsYXNzIE15U3luY2hyb25vdXNHZW5lcmF0b3IgPCBNeUdlbmVyYXRv
cgogIGRlZiBzdG9wX3RocmVhZAogICAgVGhyZWFkLnN0b3AKICBlbmQKICAKICBkZWYgc3RhcnRf
dGhyZWFkCiAgICBpZiBAcS5lbXB0eT8KICAgICAgQHRocmVhZC53YWtldXAgCiAgICAgIFRocmVh
ZC5wYXNzCiAgICBlbmQKICBlbmQKCmVuZAo=
------=_Part_628_1431551.1139777415689--
 
D

Dave Lee

------=_Part_3662_14849808.1139806272125
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

This submission:

- passes the tests
- runs a couple orders faster than the continuation generator using
the provided bench
- runs the generator block in a separate thread
- intended to handle both stateful and stateless (functional) generator blo=
cks
- provides a Generator::stateless constructor that allows for the
generator block to run for more than one callback to Generator#yield
- is uncommented

thanks,
Dave

------=_Part_3662_14849808.1139806272125
Content-Type: application/octet-stream; name=my_generator.rb
Content-Transfer-Encoding: 7bit
X-Attachment-Id: f_ejmb5sks
Content-Disposition: attachment; filename="my_generator.rb"

class MyGenerator

def self.stateless(enum = nil, &block)
g = new(enum, &block)
g.instance_variable_set:)@stateless, true)
g
end

def initialize(enum = nil, &block)
@index = 0
@stateless = false
if enum
if enum.respond_to? :to_ary
@array = enum.to_ary
else
block = proc { |g| enum.each { |x| g.yield x } }
end
elsif block
@array = Array.new
@block = block
[ :current, :end? ].each do |symbol|
method = method(symbol)
metaclass.define_method(symbol) { |*args| fill_from_block; method.call(*args) }
end
else
raise ArgumentError, 'Generate nothing?'
end
end

def current
raise EOFError if spent?
@array[@index]
end

def each(&each_block)
if @array
@array.each(&each_block)
else
x = Object.new
def x.yield(value); each_block.call(value); end
@block.call(x)
end
self
end

def end?
spent?
end

attr_reader :index
alias_method :pos, :index

def next
result = current
@index += 1
result
end

def next?
not end?
end

def rewind
@index = 0
if @block
@array = Array.new if @block
@thread = new_fill_thread
end
self
end

def yield(value)
@array << value
Thread.stop unless @stateless
self
end

private

def spent?
@index >= @array.size
end

def metaclass
class << self
public_class_method :define_method
self
end
end

def new_fill_thread
Thread.new { Thread.stop; @block.call(self) }
end

def fill_from_block
return if not spent? or @block_exhausted
@thread ||= new_fill_thread
return unless @thread.alive?
@thread.wakeup
if @stateless
@block_exhausted = @thread.join(1).nil?
else
Thread.pass while spent? and @thread.alive?
end
@thread.stop unless @thread.stop?
end

end

------=_Part_3662_14849808.1139806272125--
 
H

horndude77

I'd been wanting to mess with writing ruby extensions so I wrote the
whole thing in c. I do realize one thing already: I'm using the
'entries' method to pull out what to iterate over instead of each. This
comes from something that I'm totally clear on: The contract for the
method is that we receive in an Enumrable object so can we assume that
this method will be implemented since it's part of the Enumerable
mixin? Anyways here's the code:

#include "ruby.h"

static ID id_entries;
typedef struct _gen
{
int curr;
VALUE values;
} Generator;

//Is there a built in way to do this?
#define TEST(t) t?Qtrue:Qfalse

static VALUE t_init(int argc, VALUE *argv, VALUE self)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
if(argc > 0)
{
VALUE arr = rb_funcall(argv[0], id_entries, 0);
gen->values = arr;
}
else
{
VALUE arr = rb_ary_new();
gen->values = arr;
rb_yield(self);
}
gen->curr = 0;
return self;
}

static VALUE t_end_q(VALUE self)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
int size = RARRAY(gen->values)->len;
int curr = gen->curr;
return TEST(curr >= size);
}

static VALUE t_next_q(VALUE self)
{
return TEST(!t_end_q(self));
}

static VALUE t_pos(VALUE self)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
int curr = gen->curr;
return INT2NUM(curr);
}

static VALUE t_rewind(VALUE self)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
gen->curr = 0;
return self;
}

static VALUE t_yield(VALUE self, VALUE element)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
rb_ary_push(gen->values, element);
return gen->values;
}

static VALUE t_current(VALUE self)
{
if(t_end_q(self))
{
rb_raise(rb_eEOFError, "no more elements available");
return Qnil;
}
Generator* gen;
Data_Get_Struct(self, Generator, gen);
int curr = gen->curr;
return rb_ary_entry(gen->values, curr);
}

static VALUE t_next(VALUE self)
{
if(t_end_q(self))
{
rb_raise(rb_eEOFError, "no more elements available");
return Qnil;
}
Generator* gen;
Data_Get_Struct(self, Generator, gen);
int curr = gen->curr++;
VALUE temp = rb_ary_entry(gen->values, curr);
return temp;
}

static VALUE t_each(VALUE self)
{
Generator* gen;
Data_Get_Struct(self, Generator, gen);
gen->curr = 0;
rb_iterate(rb_each, gen->values, rb_yield, 0);
return self;
}

static void gen_free(void* p)
{
free(p);
}

static void gen_mark(void* p)
{
Generator* g = p;
rb_gc_mark(g->values);
}

static VALUE gen_alloc(VALUE klass)
{
Generator* gen = malloc(sizeof(Generator));
VALUE obj;
obj = Data_Wrap_Struct(klass, gen_mark, gen_free, gen);
return obj;
}

VALUE cGen;
void Init_generator_j()
{
id_entries = rb_intern("entries");
cGen = rb_define_class("GeneratorJ", rb_cObject);
rb_define_method(cGen, "initialize", t_init, -1);
rb_define_method(cGen, "next?", t_next_q, 0);
rb_define_method(cGen, "next", t_next, 0);
rb_define_method(cGen, "end?", t_end_q, 0);
rb_define_method(cGen, "pos", t_pos, 0);
rb_define_method(cGen, "index", t_pos, 0);
rb_define_method(cGen, "rewind", t_rewind, 0);
rb_define_method(cGen, "yield", t_yield, 1);
rb_define_method(cGen, "current", t_current, 0);
rb_define_method(cGen, "each", t_each, 0);

rb_define_alloc_func(cGen, gen_alloc);
}

I haven't written much c in a long while (let alone ruby in c) so be
kind. It doesn't seem to be much of a speed improvement at all over the
same code written in straight ruby, but it was educational for me.

I did mess with trying to get infinite generators to work, but I
cheated and looked at the code after a bit so I was tainted after that.
Here is a test case I made however if it's of any use. (require
'matrix' of course):

def fib(n) (Matrix[[1,1],[1,0]]**(n-1))[0,0] end
def test_fib
g = Generator.new do |g|
a, b = 0, 1
while true
g.yield a
a, b = b, a+b
end
end
100.times do |i|
assert_equal(i, g.pos)
assert_equal(fib(i), g.next)
end
end

Thanks!

-----Horndude77
 
J

Jesse Yoon

Here's my solution. Mine is pretty much similar in spirit to Caleb's
solution, except that we use different lock mechanisms. My first take
was to replace the Continuation with a thread and a SizedQueue. That is,
in the #initialize, I create a new thread with the given block (or the
one I generate with the given enum), which will eventually be blocked
when writing to the queue with the size of 1 in #yield. Once #next
dequeues the head, the #yield thread continues, etc.

This passed James's TC_Generator test suite, but miserably failed on
Luke's "shared state" test, although the code was supposed to handle the
case.

It turned out, if SizedQueue of size one is the only blocking mechanism,
you have two values waiting at the queue's door; one in the queue, the
other right before the queue, waiting to be unlocked. This made the
reponse to Luke's test 1, 1, 2, 3, 4 (and then 10, 10, 10, ... if I
increase the repetition). I needed to make the thread stop right after
it enqueued the value until #next consumes it.

My solution was to get rid of SizedQueue and to use a Mutex and a
ConditionVariable to accomplish just that. At that point I saw Caleb's
solution and thought that starting and stopping a thead should be much
slower than using Mutexes and Cond_Vars. To my surprise, that wasn't the
case. Mutex mechanism was much slower than Caleb's thread switching
solution.

Anyways, here's the code. Benchmark follows the code (I ran on a slower
notebook).

Thanks James for the nice quiz.

Jesse

#!/usr/bin/env ruby
require 'thread'

class Generator
include Enumerable

def initialize(enum = nil, &block)
if enum
@block = proc { |g|
enum.each { |x| g.yield x }
}
else
@block = block
end

@index = 0
@queue = []
@q_access = Mutex.new
@q_consumed = ConditionVariable.new

@thread = Thread.new(self, &@block)

self
end

def yield(value)
@q_access.synchronize {
@queue << value
@q_consumed.wait(@q_access)
}

self
end

def end?()
Thread.pass while @queue.empty? && @thread.alive?
@queue.empty? && [email protected]?
end

def next?()
!end?
end

def index()
@index
end

def pos()
@index
end

def next()
if end?
raise EOFError, "no more elements available"
end
ret = nil
@q_access.synchronize {
@index += 1
ret = @queue.shift
@q_consumed.signal
}

ret
end

def current()
if end?
raise EOFError, "no more elements available"
end

@queue.first
end

def rewind()
initialize(nil, &@block) if @index.nonzero?

self
end

def each
rewind

until end?
yield self.next
end

self
end
end

###
### tests = 10
### enum = (1..1000).to_a
###

### Construction ###

Rehearsal
----------------------------------------------------------------
Old callcc Generator 0.000000 0.000000 0.000000 (
0.003000)
Caleb's SynchronousGenerator 0.000000 0.000000 0.000000 (
0.003000)
Jesse's FasterGenerator 0.010000 0.010000 0.020000 (
0.016000)
------------------------------------------------------- total:
0.020000sec

user system total
real
Old callcc Generator 0.000000 0.010000 0.010000 (
0.003000)
Caleb's SynchronousGenerator 0.000000 0.000000 0.000000 (
0.002000)
Jesse's FasterGenerator 0.000000 0.000000 0.000000 (
0.003000)

### next() ###

Rehearsal
----------------------------------------------------------------
Old callcc Generator 4.116000 0.270000 4.386000 (
4.438000)
Caleb's SynchronousGenerator 1.181000 0.010000 1.191000 (
1.194000)
Jesse's FasterGenerator 2.674000 0.000000 2.674000 (
2.831000)
------------------------------------------------------- total:
8.251000sec

user system total
real
Old callcc Generator 4.066000 0.010000 4.076000 (
4.099000)
Caleb's SynchronousGenerator 1.212000 0.000000 1.212000 (
1.222000)
Jesse's FasterGenerator 2.704000 0.000000 2.704000 (
2.706000)
 
J

Jacob Fugal

(Incidentally, Jacob, Under 1.8 yours was testing about twice as fast as
mine, but it seems to deadlock about half of the time? I have to
interrupt after leaving it for up to a minute. I'm on Ruby 1.8.4
i686-linux).

Hmm, while I didn't run into any deadlocks with my testing, I'm not
too surprised. I'm not very threading-savvy and could easily have made
some critical mistake. :)

I also realized that the implementation I posted to the list doesn't
rewind correctly. Currently it just drops back the @position marker,
but doesn't reset the @values array or generating block. This is bad
if the generator is rewound and the block should generate different
values on different runs (e.g. it's time dependent or sensitive to the
environment).

Jacob Fugal
 
C

Caleb Clausen

My solution was to get rid of SizedQueue and to use a Mutex and a
ConditionVariable to accomplish just that. At that point I saw Caleb's
solution and thought that starting and stopping a thead should be much
slower than using Mutexes and Cond_Vars. To my surprise, that wasn't the
case. Mutex mechanism was much slower than Caleb's thread switching
solution.

A confession: I had just the tiniest peek at Jacob's entry before I
wrote that part of mine. I wasn't trying to, but my eyes did pass over
the phrase 'Thread.stop' in his code, so any credit for a clever
implementation should go to him.
 
J

James Edward Gray II

My solution was to get rid of SizedQueue and to use a Mutex and a
ConditionVariable to accomplish just that. At that point I saw Caleb's
solution and thought that starting and stopping a thead should be much
slower than using Mutexes and Cond_Vars. To my surprise, that
wasn't the
case. Mutex mechanism was much slower than Caleb's thread switching
solution.

I'm pretty sure they learned the same lesson changing the standard
library. Have a look at these commit messages:

* lib/generator.rb: uses Mutex instead of Thread.critical.
[ruby-dev:28184]

Then later:

Sorry, reverted. Mutex is damn slow.....

:)

James Edward Gray II
 
J

James Edward Gray II

This week's Ruby Quiz is to write FasterGenerator, your own re-
implementation of
Generator with an eye towards working faster.

Is anyone willing to benchmark the submitted solutions, the old
callcc generator, and the new threaded generator for me? I"m not
usually much of a fan, but it's probably worth seeing them this time,
and I'm horribly busy right now.

A big thank you in advance to anyone who takes up the call...

James Edward Gray II
 
J

Jesse Yoon

James said:
I'm pretty sure they learned the same lesson changing the standard
library. Have a look at these commit messages:

* lib/generator.rb: uses Mutex instead of Thread.critical.
[ruby-dev:28184]

Then later:

Sorry, reverted. Mutex is damn slow.....

:)

James Edward Gray II

Aha! Well, then we have a strong candidate for the next quiz, namely
BetterMutex? :)

Jesse
 
C

Caleb Clausen

### next() ### ...
CalebClausenSyncGenerator 0.660000 0.000000 0.660000 ( 0.710609)
CalebClausenGenerator 0.440000 0.000000 0.440000 ( 0.495331)

Interesting. I saw much larger speedups (4-10x) from using the
non-Sync version on my system. Maybe my results weren't statistically
significant.... If it's only 1/3 faster, it hardly seems worth it to
have the non-Sync version at all.

(Also, I would have thought that mine should pass the realtime
tests.... but I never tried it.)

Thanks for this summary, Ross.
 
D

Dave Lee

Of course I can't rule out a buggy test but I've not noticed it on the
others. Let me know what you think after you take a look.

I made a couple fixes, which eliminates the problem (of premature
exit), only now I'm running into a very strange problem:

NoMethodError: undefined method `stop' for #<Thread:0xb7f0ebe4 sleep>

I don't know how this is happening. I'll try looking later tonight.

Dave
 
D

Dave Lee

------=_Part_2399_833396.1140021947431
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

It seems to be jumping early out of the endless test judging by the
number of missing assertions. It only happens sometimes (1561 is a full
pass) and it does happen with the old version too (only just noticed
it).

I hope I've fixed the thread bugs I had. At least, I can no longer
duplicate this problem.

In the tests file you sent me, I made the following change:

- c =3D 0
- until t.stop?
- if c >=3D 30
- t.kill
- fail "Endless iterators unsupported"
- end
-
- c +=3D 1
- sleep(1)
- end
+ fail "Endless iterators unsupported" unless t.join(30)

Dave

------=_Part_2399_833396.1140021947431
Content-Type: application/octet-stream; name=my_generator.rb
Content-Transfer-Encoding: 7bit
X-Attachment-Id: f_ejpvlltr
Content-Disposition: attachment; filename="my_generator.rb"

class MyGenerator

def self.stateless(enum = nil, &block)
g = new(enum, &block)
g.instance_variable_set:)@stateless, true)
g
end

def initialize(enum = nil, &block)
@index = 0
@stateless = false
if enum
if enum.respond_to? :to_ary
@array = enum.to_ary
else
block = proc { |g| enum.each { |x| g.yield x } }
end
end
if block
@array = Array.new
@block = block
[ :current, :end?, :next?, :next ].each do |symbol|
method = method(symbol)
metaclass.define_method(symbol) { |*args| fill_from_block; method.call(*args) }
end
end
raise ArgumentError, 'Generate nothing?' unless @array
end

def current
@array.fetch(@index) rescue raise EOFError
end

def each(&each_block)
if @array
@array.each(&each_block)
else
x = Object.new
def x.yield(value); each_block.call(value); end
@block.call(x)
end
self
end

def end?
@index >= @array.size
end

attr_reader :index
alias_method :pos, :index

def next
begin
result = @array.fetch(@index)
rescue Exception => e
puts e if @index != 4
raise EOFError
end
@index += 1
result
end

def next?
@index < @array.size
end

def rewind
@index = 0
if @block
@array = Array.new if @block
@thread = new_fill_thread
end
self
end

def yield(value)
@array << value
Thread.stop if not @stateless
self
end

private

def spent?
@index >= @array.size
end

def metaclass
class << self
public_class_method :define_method
self
end
end

def new_fill_thread
Thread.new { @block.call(self) }
end

def fill_from_block
return if not spent? or @block_exhausted
@thread ||= new_fill_thread
return unless @thread.alive?
if @stateless
@block_exhausted = @thread.join(1)
else
while spent? and @thread.alive?
@thread.wakeup
Thread.pass
end
end
end

end

------=_Part_2399_833396.1140021947431--
 
J

Jim Weirich

Ross said:
Hi,

Well, I've snapped a string on my guitar and my computer won't play MP3s
today for some reason,

I've come to believe that having a backup set of guitar strings is
nearly as important as having a backup for your hard disk.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,241
Latest member
Lisa1997

Latest Threads

Top