Web Images Videos Maps News Shopping Gmail more »
Recently Visited Groups | Help | Sign in
Google Groups Home
FasterGenerator (#66)
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  Messages 1 - 25 of 41 - Collapse all  -  Translate all to Translated (View all originals)   Newer >
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Ruby Quiz  
View profile  
(1 user)  More options Feb 10 2006, 8:53 am
Newsgroups: comp.lang.ruby
From: Ruby Quiz <ja...@grayproductions.net>
Date: Fri, 10 Feb 2006 22:53:06 +0900
Local: Fri, Feb 10 2006 8:53 am
Subject: [QUIZ] FasterGenerator (#66)
The three rules of Ruby Quiz:

1.  Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2.  Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3.  Enjoy!

Suggestion:  A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=

Ruby includes a useful Generator library for switching internal iterators to
external iterators.  It is used like this:

        require 'generator'

        # Generator from an Enumerable object
        g = Generator.new(['A', 'B', 'C', 'Z'])

        while g.next?
          puts g.next
        end

I've heard complaints in the past that this library is slow.  One reason is that
it was implemented with continuations, which have performance issues in the
current version of Ruby.  "Was" is the keyword there though, because I've just
learned that Generator was recently re-implemented.  I learned some good tricks
reading the new version, so let's try fixing Generator ourselves.  (No peeking
at the new version!)

This week's Ruby Quiz is to write FasterGenerator, your own re-implementation of
Generator with an eye towards working faster.  (This is a small library.  My
version is 54 lines.)  It is possible to go even faster than the new
implementation, with certain trade-offs:

        ### Construction ###

        Rehearsal -----------------------------------------------------------
        Current Generator         0.340000   0.480000   0.820000 (  0.828985)
        Old callcc Generator      0.590000   0.840000   1.430000 (  1.439255)
        James's FasterGenerator   0.210000   1.040000   1.250000 (  1.252359)
        -------------------------------------------------- total: 3.500000sec

                                      user     system      total        real
        Current Generator         0.750000   0.880000   1.630000 (  1.630639)
        Old callcc Generator      0.190000   1.170000   1.360000 (  1.375868)
        James's FasterGenerator   0.210000   1.230000   1.440000 (  1.433152)

        ### next() ###

        Rehearsal -----------------------------------------------------------
        Current Generator        16.280000   0.100000  16.380000 ( 16.434828)
        Old callcc Generator      9.260000  33.490000  42.750000 ( 42.997528)
        James's FasterGenerator   0.030000   0.000000   0.030000 (  0.038645)
        ------------------------------------------------- total: 59.160000sec

                                      user     system      total        real
        Current Generator        15.940000   0.140000  16.080000 ( 16.425068)
        Old callcc Generator      6.390000  30.160000  36.550000 ( 36.676838)
        James's FasterGenerator   0.030000   0.000000   0.030000 (  0.036880)

It you want to see the class documentation, you can find it here:

        http://www.ruby-doc.org/stdlib/libdoc/generator/rdoc/classes/Generato...

If you want to make sure your implementation is correct, you can use these tests
straight out of the current implementation:

        require 'test/unit'

        class TC_Generator < Test::Unit::TestCase
          def test_block1
            g = Generator.new { |g|
              # no yield's
            }

            assert_equal(0, g.pos)
            assert_raises(EOFError) { g.current }
          end

          def test_block2
            g = Generator.new { |g|
              for i in 'A'..'C'
                g.yield i
              end

              g.yield 'Z'
            }

            assert_equal(0, g.pos)
            assert_equal('A', g.current)

            assert_equal(true, g.next?)
            assert_equal(0, g.pos)
            assert_equal('A', g.current)
            assert_equal(0, g.pos)
            assert_equal('A', g.next)

            assert_equal(1, g.pos)
            assert_equal(true, g.next?)
            assert_equal(1, g.pos)
            assert_equal('B', g.current)
            assert_equal(1, g.pos)
            assert_equal('B', g.next)

            assert_equal(g, g.rewind)

            assert_equal(0, g.pos)
            assert_equal('A', g.current)

            assert_equal(true, g.next?)
            assert_equal(0, g.pos)
            assert_equal('A', g.current)
            assert_equal(0, g.pos)
            assert_equal('A', g.next)

            assert_equal(1, g.pos)
            assert_equal(true, g.next?)
            assert_equal(1, g.pos)
            assert_equal('B', g.current)
            assert_equal(1, g.pos)
            assert_equal('B', g.next)

            assert_equal(2, g.pos)
            assert_equal(true, g.next?)
            assert_equal(2, g.pos)
            assert_equal('C', g.current)
            assert_equal(2, g.pos)
            assert_equal('C', g.next)

            assert_equal(3, g.pos)
            assert_equal(true, g.next?)
            assert_equal(3, g.pos)
            assert_equal('Z', g.current)
            assert_equal(3, g.pos)
            assert_equal('Z', g.next)

            assert_equal(4, g.pos)
            assert_equal(false, g.next?)
            assert_raises(EOFError) { g.next }
          end

          def test_each
            a = [5, 6, 7, 8, 9]

            g = Generator.new(a)

            i = 0

            g.each { |x|
              assert_equal(a[i], x)

              i += 1

              break if i == 3
            }

            assert_equal(3, i)

            i = 0

            g.each { |x|
              assert_equal(a[i], x)

              i += 1
            }

            assert_equal(5, i)
          end
        end

The Generator library also includes a SyncEnumerator class, but it is written to
use Generator and will work fine with a new version, as long as it is
API-compatible.


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Pit Capitain  
View profile  
 More options Feb 10 2006, 9:24 am
Newsgroups: comp.lang.ruby
From: Pit Capitain <p...@capitain.de>
Date: Fri, 10 Feb 2006 23:24:43 +0900
Local: Fri, Feb 10 2006 9:24 am
Subject: Re: [QUIZ] FasterGenerator (#66)
Ruby Quiz schrieb:

> Ruby includes a useful Generator library for switching internal iterators to
> external iterators.  It is used like this:
> ...
> If you want to make sure your implementation is correct, you can use these tests
> straight out of the current implementation:
> ...

James, thanks for the new quiz. It would be interesting to add a
testcase with an endless internal iterator. I don't know the new
implementation of Generator, so I can't say whether an endless iterator
should be supported.

Regards,
Pit


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 9:34 am
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Fri, 10 Feb 2006 23:34:25 +0900
Local: Fri, Feb 10 2006 9:34 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 8:24 AM, Pit Capitain wrote:

> It would be interesting to add a testcase with an endless internal  
> iterator. I don't know the new implementation of Generator, so I  
> can't say whether an endless iterator should be supported.

That's an interesting point we will certainly talk more about as we  
start to see some solutions.  Generator *can* handle endless  
iterators.  Quiz solvers can decide whether or not to limit  
themselves with that additional requirement...

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Feb 10 2006, 11:08 am
Newsgroups: comp.lang.ruby
From: Matthew Moss <matthew.moss.co...@gmail.com>
Date: Sat, 11 Feb 2006 01:08:20 +0900
Local: Fri, Feb 10 2006 11:08 am
Subject: Re: [QUIZ] FasterGenerator (#66)
Not being familiar with all the various Ruby packages and libs, I
first want to thank ya for posting a good set of test cases that I can
review, but was wondering if you also might post the code used to do
the timing?

    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 11:23 am
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Sat, 11 Feb 2006 01:23:30 +0900
Local: Fri, Feb 10 2006 11:23 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 10:08 AM, Matthew Moss wrote:

> Not being familiar with all the various Ruby packages and libs, I
> first want to thank ya for posting a good set of test cases that I can
> review, but was wondering if you also might post the code used to do
> the timing?

I will, yes, on Sunday.  :)

I pulled down copies on the Generator library, before and after the  
change.  I then modified the class names so they could all peacefully  
coexist, loaded them, and ran a trivial benchemark:

#!/usr/local/bin/ruby -w

require "benchmark"

require "current_generator"
require "callcc_generator"
require "faster_generator"

tests = 1000
enum  = (1..10000).to_a

puts
puts "### Construction ###"
puts

Benchmark.bmbm do |x|
   x.report("Current Generator") do
     tests.times { CurrentGenerator.new(enum) }
   end
   x.report("Old callcc Generator") do
     tests.times { CallCCGenerator.new(enum) }
   end
   x.report("James's FasterGenerator") do
     tests.times { FasterGenerator.new(enum) }
   end
end

puts
puts "### next() ###"
puts

Benchmark.bmbm do |x|
   x.report("Current Generator") do
     generator = CurrentGenerator.new(enum)
     tests.times { generator.next until generator.end? }
   end
   x.report("Old callcc Generator") do
     generator = CallCCGenerator.new(enum)
     tests.times { generator.next until generator.end? }
   end
   x.report("James's FasterGenerator") do
     generator = FasterGenerator.new(enum)
     tests.times { generator.next until generator.end? }
   end
end

__END__

I'll post the modified libraries to go with that after the spoiler  
period.  Obviously, you could go get them yourself, before then.  I  
strongly recommend writing a solution first though.

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 11:28 am
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Sat, 11 Feb 2006 01:28:14 +0900
Local: Fri, Feb 10 2006 11:28 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 10:23 AM, James Edward Gray II wrote:

> I pulled down copies on the Generator library, before and after the  
> change.  I then modified the class names so they could all  
> peacefully coexist, loaded them, and ran a trivial benchemark:

In answering Matthew's question, I found a small mistake in the  
benchmarks posted with the quiz (the timings for constructing my  
FasterGenerator were wrong).  Here are the corrected numbers:

### Construction ###

Rehearsal -----------------------------------------------------------
Current Generator         0.320000   0.320000   0.640000 (  0.642583)
Old callcc Generator      0.610000   0.870000   1.480000 (  1.480780)
James's FasterGenerator   0.000000   0.000000   0.000000 (  0.003751)
-------------------------------------------------- total: 2.120000sec

                               user     system      total        real
Current Generator         0.740000   0.720000   1.460000 (  1.464659)
Old callcc Generator      0.220000   1.500000   1.720000 (  1.714859)
James's FasterGenerator   0.010000   0.000000   0.010000 (  0.003258)

### next() ###

Rehearsal -----------------------------------------------------------
Current Generator        16.610000   0.130000  16.740000 ( 17.032537)
Old callcc Generator      8.070000  32.740000  40.810000 ( 41.072265)
James's FasterGenerator   0.030000   0.000000   0.030000 (  0.037034)
------------------------------------------------- total: 57.580000sec

                               user     system      total        real
Current Generator        16.630000   0.120000  16.750000 ( 16.878429)
Old callcc Generator      7.440000  32.720000  40.160000 ( 40.336902)
James's FasterGenerator   0.040000   0.000000   0.040000 (  0.035432)

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 11:32 am
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Sat, 11 Feb 2006 01:32:32 +0900
Local: Fri, Feb 10 2006 11:32 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 10:28 AM, James Edward Gray II wrote:

> ### Construction ###
> James's FasterGenerator   0.000000   0.000000   0.000000 (  0.003751)
> ### next() ###
> James's FasterGenerator   0.030000   0.000000   0.030000 (  0.037034)

Oh and don't be too impressed with those, Pit Capitain has already  
told you one of the flaws with my approach...  ;)

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Feb 10 2006, 11:34 am
Newsgroups: comp.lang.ruby
From: Matthew Moss <matthew.moss.co...@gmail.com>
Date: Sat, 11 Feb 2006 01:34:52 +0900
Local: Fri, Feb 10 2006 11:34 am
Subject: Re: [QUIZ] FasterGenerator (#66)

> > Not being familiar with all the various Ruby packages and libs, I
> > first want to thank ya for posting a good set of test cases that I can
> > review, but was wondering if you also might post the code used to do
> > the timing?

> I will, yes, on Sunday.  :)

But you just posted it.  :)

Sorry, guess I wasn't clear. I wasn't looking to see your
implementation of Generator, I was to see your benchmarking code.


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 11:43 am
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Sat, 11 Feb 2006 01:43:37 +0900
Local: Fri, Feb 10 2006 11:43 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 10:34 AM, Matthew Moss wrote:

> I wasn't looking to see your implementation of Generator, I was to  
> see your benchmarking code.

It's not my implementation I am trying to hide, it's the current  
Generator code the benchmark uses as a baseline.  I realize it's  
readily available, but I think solving this one is more fun if you  
don't peek.  :)

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Feb 10 2006, 12:36 pm
Newsgroups: comp.lang.ruby
From: Matthew Moss <matthew.moss.co...@gmail.com>
Date: Sat, 11 Feb 2006 02:36:25 +0900
Local: Fri, Feb 10 2006 12:36 pm
Subject: Re: [QUIZ] FasterGenerator (#66)

> It's not my implementation I am trying to hide, it's the current
> Generator code the benchmark uses as a baseline.  I realize it's
> readily available, but I think solving this one is more fun if you
> don't peek.  :)

Okay....   ummm....  sure....  I won't peek.  No, you can't make me.

(In case I still wasn't clear, I wasn't asking to peek at ANY
implementation of Generator, but rather wanted exactly what you
posted, which has nothing to do with Generator but everything to do
with benchmark, i.e., the thing I wanted to know about.)


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Dave Lee  
View profile  
 More options Feb 10 2006, 1:45 pm
Newsgroups: comp.lang.ruby
From: Dave Lee <davelee....@gmail.com>
Date: Sat, 11 Feb 2006 03:45:33 +0900
Local: Fri, Feb 10 2006 1:45 pm
Subject: Re: [QUIZ] FasterGenerator (#66)
On 2/10/06, James Edward Gray II <ja...@grayproductions.net> wrote:

Off topic a bit, but what os/hardware are you using to get these numbers?

Dave


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 10 2006, 1:49 pm
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Sat, 11 Feb 2006 03:49:45 +0900
Local: Fri, Feb 10 2006 1:49 pm
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 10, 2006, at 12:45 PM, Dave Lee wrote:

A dual processor G5 at 2.0 Ghz (each processor), running Mac OS X  
(10.4.4).  The box has 2 Gigs of RAM.

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Jacob Fugal  
View profile  
 More options Feb 12 2006, 12:24 am
Newsgroups: comp.lang.ruby
From: Jacob Fugal <lukf...@gmail.com>
Date: Sun, 12 Feb 2006 14:24:06 +0900
Local: Sun, Feb 12 2006 12:24 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On 2/10/06, Ruby Quiz <ja...@grayproductions.net> wrote:

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

Here's the benchmark results from my own implementation:

galadriel:~/ruby/qotw/66$ ruby benchmark.rb

### Construction ###

Rehearsal -------------------------------------------------------------
Old callcc Generator        7.380000   1.000000   8.380000 (  8.726668)
lukfugl's FasterGenerator   0.020000   0.000000   0.020000 (  0.070048)
---------------------------------------------------- total: 8.400000sec

                                user     system      total        real
Old callcc Generator        8.580000   0.960000   9.540000 (  9.765350)
lukfugl's FasterGenerator   0.020000   0.000000   0.020000 (  0.020035)

### next() ###

Rehearsal -------------------------------------------------------------
Old callcc Generator       10.750000  17.010000  27.760000 ( 28.587567)
lukfugl's FasterGenerator   0.680000   0.000000   0.680000 (  0.744570)
--------------------------------------------------- total: 28.440000sec

                                user     system      total        real
Old callcc Generator       11.490000  17.390000  28.880000 ( 29.853396)
lukfugl's FasterGenerator   0.650000   0.000000   0.650000 (
0.694442)

My machine is obviously painfully slower than James', and it looks
like my implementation isn't quite as quick as his, but it was fun
figuring out a few tricks. Now I'm gonna go look at the new code and
see if my implementation beats it too!

(I'll post my solution after the spoiler period expires tomorrow morning.)

Jacob Fugal


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Ross Bamford  
View profile  
 More options Feb 12 2006, 1:51 am
Newsgroups: comp.lang.ruby
From: Ross Bamford <ros...@roscopeco.co.uk>
Date: Sun, 12 Feb 2006 15:51:21 +0900
Local: Sun, Feb 12 2006 1:51 am
Subject: Re: [QUIZ] FasterGenerator (#66)

On Sun, 2006-02-12 at 14:24 +0900, Jacob Fugal wrote:
> On 2/10/06, Ruby Quiz <ja...@grayproductions.net> wrote:
> > This week's Ruby Quiz is to write FasterGenerator, your own
> > re-implementation of Generator with an eye towards working faster.

> Here's the benchmark results from my own implementation: [...]

Here's mine up to now. Being greedy I made two versions, because of the
infinite block problem mentioned on list. These are with 10000 elements
(the posted code) against the old Generator (I'm going to try the new
one later on, I want to try a few more optimisations first):

### Construction ###

Rehearsal ------------------------------------------------------------------
1.8.4-2005-12-24 Generator       0.070000   0.100000   0.170000 (  0.204462)
Basic (fast) generator           0.010000   0.000000   0.010000 (  0.006223)
Enhanced (***********) generator 0.650000   0.050000   0.700000 (  0.702831)
--------------------------------------------------------- total: 0.880000sec

                                     user     system      total        real
1.8.4-2005-12-24 Generator       0.080000   0.070000   0.150000 (  0.146903)
Basic (fast) generator           0.010000   0.000000   0.010000 (  0.008860)
Enhanced (***********) generator 1.050000   0.020000   1.070000 (  1.066803)

### next() ###

Rehearsal ------------------------------------------------------------------
1.8.4-2005-12-24 Generator       4.470000   0.240000   4.710000 (  4.754501)
Basic (fast) generator           0.060000   0.000000   0.060000 (  0.055710)
Enhanced (***********) generator 0.120000   0.000000   0.120000 (  0.122150)
--------------------------------------------------------- total: 4.890000sec

                                     user     system      total        real
1.8.4-2005-12-24 Generator       4.480000   0.090000   4.570000 (  4.606803)
Basic (fast) generator           0.060000   0.000000   0.060000 (  0.060831)
Enhanced (***********) generator 0.110000   0.000000   0.110000 (  0.119554)

I don't know how this compares with James's benchmark really, these Macs
are a bloody mystery to me... :) This is on a 2Ghz P4 / 1gig RAM.

--
Ross Bamford - ro...@roscopeco.REMOVE.co.uk


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Christoffer Lernö  
View profile  
 More options Feb 12 2006, 4:23 am
Newsgroups: comp.lang.ruby
From: Christoffer Lernö <le...@dragonascendant.com>
Date: Sun, 12 Feb 2006 18:23:14 +0900
Local: Sun, Feb 12 2006 4:23 am
Subject: Re: [QUIZ] FasterGenerator (#66)
Newbie jumping in here.

On Feb 10, 2006, at 17:23 , James Edward Gray II wrote:

> On Feb 10, 2006, at 10:08 AM, Matthew Moss wrote:

>> Not being familiar with all the various Ruby packages and libs, I
>> first want to thank ya for posting a good set of test cases that I  
>> can
>> review, but was wondering if you also might post the code used to do
>> the timing?

> I will, yes, on Sunday.  :)

> I pulled down copies on the Generator library, before and after the  
> change.  I then modified the class names so they could all  
> peacefully coexist, loaded them, and ran a trivial benchemark:

..snip..

> Benchmark.bmbm do |x|
>   x.report("Current Generator") do
>     generator = CurrentGenerator.new(enum)
>     tests.times { generator.next until generator.end? }
>   end

I have small problem with this. Won't this mean the loop will only  
run 1 time?

The first time it correctly loops, but the next time "generator.end?"  
will be true, and the following 999 runs will consequently does not  
execute generator.next at all, right?

If we change the row to

test.times do
   generator.rewind
   generator.next until generator.end?
end

Isn't that giving us the desired test-behaviour?

/Christoffer


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Christoffer Lernö  
View profile  
 More options Feb 12 2006, 4:43 am
Newsgroups: comp.lang.ruby
From: Christoffer Lernö <le...@dragonascendant.com>
Date: Sun, 12 Feb 2006 18:43:11 +0900
Local: Sun, Feb 12 2006 4:43 am
Subject: Re: [QUIZ] FasterGenerator (#66)

With the same trade-offs as James' version(?) (i.e. no infinite block  
iterators) and with the hope that I don't have any bugs in the code,  
I get something like this on a G4 1.67Mhz (after changing the tests  
somewhat)

### next() ###

Rehearsal -----------------------------------------------------
Current Generator  20.360000  14.330000  34.690000 ( 51.788014)
My Generator        0.080000   0.010000   0.090000 (  0.116704)
------------------------------------------- total: 34.780000sec

                         user     system      total        real
Current Generator  23.200000  14.610000  37.810000 ( 53.145604)
My Generator        0.080000   0.010000   0.090000 (  0.129965)

(and this code)

8<----

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

puts
puts "### next() ###"
puts

Benchmark.bmbm do |x|
   x.report("Current Generator") do
     generator = Generator.new(enum)
     tests.times {
         generator.rewind
         generator.next until generator.end?
     }
   end
   x.report("My Generator") do
     generator = MyGenerator.new(enum)
     tests.times {
         generator.rewind
         generator.next until generator.end?
     }
   end
end

8<-----

I originally wanted to use the original values for tests and enum,  
but I got bored waiting.

/Christoffer


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
H. Yamamoto  
View profile  
 More options Feb 12 2006, 6:30 am
Newsgroups: comp.lang.ruby
From: H.Yamamoto <oc...@m2.ccsnet.ne.jp>
Date: Sun, 12 Feb 2006 20:30:07 +0900
Local: Sun, Feb 12 2006 6:30 am
Subject: Re: [QUIZ] FasterGenerator (#66)
Hello. Please add this to quiz's rule.

Generator should suspend current calculation, and
resume it when next() is called. (I uses @queue as
array, but this is just for insurance. normally
@queue should not contain multiple values)

//////////////////////////////////////////

require "test/unit"
require "generator"

class TestGenerator < Test::Unit::TestCase

  class C
    def value=(x)
      @value = x
    end
    def each
      loop do
        yield @value
      end
    end
  end

  def test_realtime
    c = C.new
    g = Generator.new(c)
    3.times do |i|
      c.value = i
      assert_equal(i, g.next())
    end
  end

end

//////////////////////////////////////////

And python supports generator natively,
this behaves like HEAD's Generator class.

def generator():
  global value
  while True:
    yield value

g = generator()
for i in xrange(3):
  value = i
  print g.next()

////////////////////////////////////////////

And this one is Java version.

abstract class CoRoutine
{
    private final Thread _thread;

    private final Object _lock = new Object();

    private java.util.LinkedList _list = new java.util.LinkedList();

    private boolean _terminated = false;

    public CoRoutine()
    {
        final Object first_lock = new Object();

        _thread = new Thread()
        {
            public void run()
            {
                synchronized(_lock)
                {
                    synchronized (first_lock)
                    {
                        first_lock.notify();
                    }

                    try
                    {
                        _lock.wait();
                    }
                    catch (InterruptedException e)
                    {
                        throw new RuntimeException(e); // ???
                    }

                    try
                    {
                        CoRoutine.this.run();
                    }
                    finally
                    {
                        _terminated = true;

                        _lock.notify();
                    }
                }
            }
        };

        _thread.setDaemon(true);

        synchronized(first_lock)
        {
            _thread.start();

            try
            {
                first_lock.wait();
            }
            catch (InterruptedException e)
            {
                throw new RuntimeException(e); // ???
            }
        }
    }

    protected abstract void run();

    protected final void yield(Object value)
    {
        synchronized(_lock)
        {
            _list.add(value);

            _lock.notify();

            try
            {
                _lock.wait();
            }
            catch (InterruptedException e)
            {
                throw new RuntimeException(e); // ???
            }
        }
    }

    private void ready() // must be called in synchronized(_lock)
    {
        if (!_terminated && _list.isEmpty())
        {
            _lock.notify();

            try
            {
                _lock.wait();
            }
            catch (InterruptedException e)
            {
                throw new RuntimeException(e); // ???
            }
        }
    }

    public boolean hasNext()
    {
        synchronized(_lock)
        {
            ready();

            return !_list.isEmpty();
        }
    }

    public Object next()
    {
        synchronized(_lock)
        {
            ready();

            if (_list.isEmpty())
            {
                throw new RuntimeException("EOF");
            }

            return _list.removeFirst();
        }
    }

}

//////////////////////////////////////////////////
// Main

class Test extends CoRoutine
{
    public int value;

    protected void run()
    {
        while (true)
        {
            yield(value);
        }
    }

}

class Main
{
    public static void main(String[] args)
    {
        Test t = new Test();

        for (int i = 0; i < 3; ++i)
        {
            t.value = i;

            System.out.println(t.next());
        }
    }


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Jacob Fugal  
View profile  
 More options Feb 12 2006, 11:42 am
Newsgroups: comp.lang.ruby
From: Jacob Fugal <lukf...@gmail.com>
Date: Mon, 13 Feb 2006 01:42:47 +0900
Local: Sun, Feb 12 2006 11:42 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On 2/12/06, Luke Blanshard <l...@blanshard.us> wrote:

Actually, there's no way (I can think of) to get the output you
expect, and it has nothing to do with an initial advance or anything.
My implementation doesn't advance to the yields until required, yet
has the same output. This is because of these two lines:

 x = 10 if g.current > 2
 puts g.next

g.current returns the current value. g.current is not greater than two
until g.current == 3. g.next then returns the same as g.current, while
also advancing the internal counter. The behavior your describing
would require changing the value of g.current by assigning to x. Can't
be done.

Jacob Fugal


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Feb 12 2006, 12:07 pm
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Mon, 13 Feb 2006 02:07:31 +0900
Local: Sun, Feb 12 2006 12:07 pm
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 12, 2006, at 3:23 AM, Christoffer Lernö wrote:

>> Benchmark.bmbm do |x|
>>   x.report("Current Generator") do
>>     generator = CurrentGenerator.new(enum)
>>     tests.times { generator.next until generator.end? }
>>   end

> I have small problem with this. Won't this mean the loop will only  
> run 1 time?

Yes, you are right.  That was silly of me.

I like your fix.  Adding rewind() fixes things up.

Thank you.

James Edward Gray II


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Christoffer Lernö  
View profile  
 More options Feb 12 2006, 1:58 pm
Newsgroups: comp.lang.ruby
From: Christoffer Lernö <le...@dragonascendant.com>
Date: Mon, 13 Feb 2006 03:58:02 +0900
Local: Sun, Feb 12 2006 1:58 pm
Subject: Re: [QUIZ] FasterGenerator (#66)
On Feb 12, 2006, at 19:23 , James Edward Gray II wrote:

> On Feb 12, 2006, at 11:10 AM, Jacob Fugal wrote:

>> Ok, since last night I've corrected my implementation to not evaluate
>> ahead, as per H. Yamamoto's and Luke Blanshard's comments.

> Mine does, of course, pull all the items in at once and has all the  
> problems discussed with that approach.  Attached is my  
> implementation and the altered versions I have been using in  
> benchmarks.

> James Edward Gray II

Mine looks like a clone of James':

class MyGenerator
     attr_reader :index
     def initialize(enum = nil)
         if enum then
             @array = enum.to_a
         else
             @array = Array.new
             yield self
         end
         @index = 0
     end
     def current
         raise EOFError unless next?
         @array[@index]
     end
     def next
         value = current
         @index += 1
         return value
     end
     def next?
         return @index < @array.length
     end
     def rewind
         @index = 0
         self
     end
     def each(&block)
         @array.each(&block)
     end
     def yield(value)
         @array << value
     end
     def pos
         return @index
     end
     def end?
         return !next?
     end
end

/Christoffer


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Luke Blanshard  
View profile  
 More options Feb 12 2006, 2:33 pm
Newsgroups: comp.lang.ruby
From: Luke Blanshard <l...@blanshard.us>
Date: Mon, 13 Feb 2006 04:33:08 +0900
Local: Sun, Feb 12 2006 2:33 pm
Subject: Re: [QUIZ] FasterGenerator (#66)

You are correct.  My bad.

My point is that the API is pointlessly difficult.  I think, actually,
that the C# iterator API is the cleanest I've seen: there is one
operation that attempts to advance to the next position, returning false
if it has reached the end, and another that returns the current value,
throwing an exception if it's still before the first advance.

And my other point is that, while you're going down the road of
coroutines, you might as well go the whole way.

Luke Blanshard


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Dave Lee  
View profile  
 More options Feb 12 2006, 11:51 pm
Newsgroups: comp.lang.ruby
From: Dave Lee <davelee....@gmail.com>
Date: Mon, 13 Feb 2006 13:51:31 +0900
Local: Sun, Feb 12 2006 11:51 pm
Subject: Re: FasterGenerator (#66)

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 blocks
- 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

  my_generator.rb
1K Download

    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Discussion subject changed to "[SOLUTION] FasterGenerator (#66)" by horndud...@gmail.com
horndud...@gmail.com  
View profile  
 More options Feb 13 2006, 12:49 am
Newsgroups: comp.lang.ruby
From: horndud...@gmail.com
Date: 12 Feb 2006 21:49:34 -0800
Local: Mon, Feb 13 2006 12:49 am
Subject: [QUIZ][SOLUTION] FasterGenerator (#66)
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


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Discussion subject changed to "FasterGenerator (#66)" by Jesse Yoon
Jesse Yoon  
View profile  
 More options Feb 13 2006, 4:07 am
Newsgroups: comp.lang.ruby
From: Jesse Yoon <ze...@hotmail.com>
Date: Mon, 13 Feb 2006 18:07:28 +0900
Local: Mon, Feb 13 2006 4:07 am
Subject: Re: FasterGenerator (#66)
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? && !...@thread.alive?
  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)

--
Posted via http://www.ruby-forum.com/.


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Jacob Fugal  
View profile  
 More options Feb 13 2006, 11:14 am
Newsgroups: comp.lang.ruby
From: Jacob Fugal <lukf...@gmail.com>
Date: Tue, 14 Feb 2006 01:14:31 +0900
Local: Mon, Feb 13 2006 11:14 am
Subject: Re: [QUIZ] FasterGenerator (#66)
On 2/12/06, Ross Bamford <ros...@roscopeco.co.uk> wrote:

> (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


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Messages 1 - 25 of 41   Newer >
« Back to Discussions « Newer topic     Older topic »

Create a group - Google Groups - Google Home - Terms of Service - Privacy Policy
©2009 Google