Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

Fibur

Fibur is a library that allows concurrency during Ruby I/O operations without needing to make use of callback systems. Traditionally in Ruby, to achieve concurrency during blocking I/O operations, programmers would make use of Fibers and callbacks. Fibur eliminates the need for wrapping your I/O calls with Fibers and a callback. It allows you to write your blocking I/O calls the way you normally would, and still have concurrent execution during those I/O calls.

Example

Say you have a method that fetches data from a network resource:

require 'net/http'
def network_read uri
  Net::HTTP.get_response uri
end

We need to fetch that data say 100 times, so we'll wrap it in a loop:

100.times { network_read }

If we benchmark this code:

require 'benchmark'
require 'net/http'
require 'uri'

def network_read uri
  Net::HTTP.get_response uri
end

uri = URI('http://google.com/')

Benchmark.bm do |x|
  x.report('loop') { 100.times { network_read uri } }
end

On my machine it takes about 5 seconds:

$ ruby test.rb
       user     system      total        real
loop  0.210000   0.070000   0.280000 (  5.731776)

Now lets modify our benchmark to wrap each call to network_read in a Fibur:

require 'benchmark'
require 'net/http'
require 'uri'
require 'fibur' # use the Fibur gem.

def network_read uri
  Net::HTTP.get_response uri
end

uri = URI('http://google.com/')

Benchmark.bm(5) do |x|
  x.report('loop') { 100.times { network_read uri } }
  x.report('fibur') {
    100.times.map {
      Fibur.new { network_read uri }
    }.map(&:join)
  }
end

Output from our benchmark:

$ ruby -I. test.rb
            user     system      total        real
loop    0.220000   0.070000   0.290000 (  5.732683)
fibur   0.110000   0.050000   0.160000 (  0.197434)

Wrapping each call to network_read in a fibur brought the time down to 0.2 seconds! Using Fiburs, we were able to gain full concurrency during our I/O operations, and we didn't have to modify our network_read method.

Installation

Fibur only works on Ruby 1.9, and you can get it by installing the fibur gem.

How does it work?

I encourage you to check out the source.

@wycats

This comment has been minimized.

Show comment Hide comment
@wycats

wycats Dec 19, 2011

I tried Fibur in 1.8 and it worked fine.

wycats commented Dec 19, 2011

I tried Fibur in 1.8 and it worked fine.

@tenderlove

This comment has been minimized.

Show comment Hide comment
@tenderlove

tenderlove Dec 19, 2011

@wycats you're right! I should have tested on 1.8.

Owner

tenderlove commented Dec 19, 2011

@wycats you're right! I should have tested on 1.8.

@akahn

This comment has been minimized.

Show comment Hide comment
@akahn

akahn Dec 19, 2011

I don't feel comfortable using this without a full suite of unit tests.

akahn commented Dec 19, 2011

I don't feel comfortable using this without a full suite of unit tests.

@mattyoho

This comment has been minimized.

Show comment Hide comment
@mattyoho

mattyoho Dec 19, 2011

While this is clearly alpha stage, I really like where it's headed. The implementation seems very clean so far.

While this is clearly alpha stage, I really like where it's headed. The implementation seems very clean so far.

@mislav

This comment has been minimized.

Show comment Hide comment
@mislav

mislav Dec 19, 2011

I'm a little worried that the source code isn't commented. It may be hard to understand what's going on in the implementation.

mislav commented Dec 19, 2011

I'm a little worried that the source code isn't commented. It may be hard to understand what's going on in the implementation.

@rickhull

This comment has been minimized.

Show comment Hide comment
@rickhull

rickhull Dec 19, 2011

Pots rub if Fibur stop

Pots rub if Fibur stop

@RevoHoffman

This comment has been minimized.

Show comment Hide comment
@RevoHoffman

RevoHoffman Dec 19, 2011

Clearly a step-forward for the entire Ruby community! Without a comp-sci degree, I struggle to understand it completely, but it looks very sophisticated.

Clearly a step-forward for the entire Ruby community! Without a comp-sci degree, I struggle to understand it completely, but it looks very sophisticated.

@bleything

This comment has been minimized.

Show comment Hide comment
@bleything

bleything Dec 19, 2011

@akahn: please vote up my pull request: tenderlove/fibur#2

@akahn: please vote up my pull request: tenderlove/fibur#2

@akahn

This comment has been minimized.

Show comment Hide comment
@akahn

akahn Dec 19, 2011

@lifo, check out @bleything's pull request. be sure to upvote so @tenderlove accepts it!!

akahn commented Dec 19, 2011

@lifo, check out @bleything's pull request. be sure to upvote so @tenderlove accepts it!!

@bleything

This comment has been minimized.

Show comment Hide comment
@bleything

bleything Dec 19, 2011

Thanks for the support, guys, @tenderlove has accepted my patch!

Thanks for the support, guys, @tenderlove has accepted my patch!

@therealadam

This comment has been minimized.

Show comment Hide comment
@therealadam

therealadam Dec 19, 2011

I think that, per the widespread adoption of, vis a vis, evented programming, as heretofore java.util.concurrent and its expansive support for locked and lockless data types, pursuant to the need for reentrancy and full processor utilization that you'll find the necessity, herein, of implementing callbacks concordant with harmonizing processor caches, instruction-level parallelism, the traveling salesman problem, and transactional memory scaling; QED some sort of reactor loop will need decoupling from your central abstraction and exposed via a dependency injection container, with bytecodes.

I think that, per the widespread adoption of, vis a vis, evented programming, as heretofore java.util.concurrent and its expansive support for locked and lockless data types, pursuant to the need for reentrancy and full processor utilization that you'll find the necessity, herein, of implementing callbacks concordant with harmonizing processor caches, instruction-level parallelism, the traveling salesman problem, and transactional memory scaling; QED some sort of reactor loop will need decoupling from your central abstraction and exposed via a dependency injection container, with bytecodes.

@samullen

This comment has been minimized.

Show comment Hide comment
@samullen

samullen Dec 19, 2011

When will Matz recognize the geniusness of this and bring it in to Ruby core?

When will Matz recognize the geniusness of this and bring it in to Ruby core?

@jgn

This comment has been minimized.

Show comment Hide comment
@jgn

jgn Dec 19, 2011

Sounds great, but it's not gluten-free.

jgn commented Dec 19, 2011

Sounds great, but it's not gluten-free.

@ryanjm

This comment has been minimized.

Show comment Hide comment
@ryanjm

ryanjm Dec 19, 2011

I really need to improve my reaction time to these things.

ryanjm commented Dec 19, 2011

I really need to improve my reaction time to these things.

@scottburton11

This comment has been minimized.

Show comment Hide comment
@scottburton11

scottburton11 Dec 19, 2011

Does it works in JQuery?

Does it works in JQuery?

@steveklabnik

This comment has been minimized.

Show comment Hide comment
@steveklabnik

steveklabnik Dec 19, 2011

@ryanjm write something with Fibur to grab Tweets off of Twitter, and then pipe them to say.

At least, that's what I do.

@ryanjm write something with Fibur to grab Tweets off of Twitter, and then pipe them to say.

At least, that's what I do.

@steeve

This comment has been minimized.

Show comment Hide comment
@steeve

steeve Dec 19, 2011

does it work with ruby 2 too ?

edit: it does.

steeve commented Dec 19, 2011

does it work with ruby 2 too ?

edit: it does.

@craigmcnamara

This comment has been minimized.

Show comment Hide comment
@craigmcnamara

craigmcnamara Dec 19, 2011

Is the point of this to let people know it's ok to use threads in ruby now?

Is the point of this to let people know it's ok to use threads in ruby now?

@mperham

This comment has been minimized.

Show comment Hide comment
@mperham

mperham Dec 19, 2011

Threads are hard, stick with Fiburs.

mperham commented Dec 19, 2011

Threads are hard, stick with Fiburs.

@indexzero

This comment has been minimized.

Show comment Hide comment
@indexzero

indexzero Dec 19, 2011

Let X represent the maximum amount of RAM present on the system. Let Y represent the memory consumed by a single thread (or "fibur"). If a single thread is allocated per incoming connection then the maximum concurrency of the system is defined by X/Y. A proof by contradiction is left to the reader.

Let X represent the maximum amount of RAM present on the system. Let Y represent the memory consumed by a single thread (or "fibur"). If a single thread is allocated per incoming connection then the maximum concurrency of the system is defined by X/Y. A proof by contradiction is left to the reader.

@Optimiza

This comment has been minimized.

Show comment Hide comment
@Optimiza

Optimiza Dec 19, 2011

lmao!

lmao!

@alexeypetrushin

This comment has been minimized.

Show comment Hide comment
@alexeypetrushin

alexeypetrushin Dec 19, 2011

I believe there should be more detailed comments in the source code. It's hard to understand what's going on there.

I believe there should be more detailed comments in the source code. It's hard to understand what's going on there.

@wycats

This comment has been minimized.

Show comment Hide comment
@wycats

wycats Dec 19, 2011

@indexzero nope.

ruby-1.9.3-p0 :001 > Fibur = Thread
=> Thread
ruby-1.9.3-p0 :002 > def memory() ps -orss #{Process.pid}.split("\n")[1].strip end
=> nil
ruby-1.9.3-p0 :003 > original = memory.to_i
=> 10928
ruby-1.9.3-p0 :004 > 2000.times { Fibur.new { sleep } }
=> 2000
ruby-1.9.3-p0 :005 > final = memory.to_i
=> 52496
ruby-1.9.3-p0 :006 > (final - original) / 2000
=> 20

A Fibur does not use up 8MB of memory simply by existing. It starts off small and then grows as stack size is consumed.

wycats commented Dec 19, 2011

@indexzero nope.

ruby-1.9.3-p0 :001 > Fibur = Thread
=> Thread
ruby-1.9.3-p0 :002 > def memory() ps -orss #{Process.pid}.split("\n")[1].strip end
=> nil
ruby-1.9.3-p0 :003 > original = memory.to_i
=> 10928
ruby-1.9.3-p0 :004 > 2000.times { Fibur.new { sleep } }
=> 2000
ruby-1.9.3-p0 :005 > final = memory.to_i
=> 52496
ruby-1.9.3-p0 :006 > (final - original) / 2000
=> 20

A Fibur does not use up 8MB of memory simply by existing. It starts off small and then grows as stack size is consumed.

@indexzero

This comment has been minimized.

Show comment Hide comment
@indexzero

indexzero Dec 19, 2011

@wycats Semantics? Ok; sure.

Let X represent the maximum amount of RAM present on the system. Let Y represent the memory consumed by a single average thread (or "fibur"). The calculation of Y is determined by running a given simulation to steady-state under expected load and dividing actual memory used, Z by number of real threads, Y'. If a single thread is allocated per incoming connection then the maximum concurrency of the system is defined by X/Y. A proof by contradiction is left to the reader.

@wycats Semantics? Ok; sure.

Let X represent the maximum amount of RAM present on the system. Let Y represent the memory consumed by a single average thread (or "fibur"). The calculation of Y is determined by running a given simulation to steady-state under expected load and dividing actual memory used, Z by number of real threads, Y'. If a single thread is allocated per incoming connection then the maximum concurrency of the system is defined by X/Y. A proof by contradiction is left to the reader.

@strmpnk

This comment has been minimized.

Show comment Hide comment
@strmpnk

strmpnk Dec 20, 2011

@indexzero, likewise, any allocated state for event emitters, event sources, tick queues, and all the other overhead node uses for tracking state in the system. One might be a bit cheaper (it's actually much more complicated than it looks to decide this case) but it's optimizing a constant (both are linear cost) and arguing that efficient threads aren't scalable is just FUD.

It'd be a lot more effective if proponents of thread alternatives wouldn't point out that threads don't work. Rather, why not show how other concepts have strengths. Event loops are, for example, awesome and people should use them where appropriate.

strmpnk commented Dec 20, 2011

@indexzero, likewise, any allocated state for event emitters, event sources, tick queues, and all the other overhead node uses for tracking state in the system. One might be a bit cheaper (it's actually much more complicated than it looks to decide this case) but it's optimizing a constant (both are linear cost) and arguing that efficient threads aren't scalable is just FUD.

It'd be a lot more effective if proponents of thread alternatives wouldn't point out that threads don't work. Rather, why not show how other concepts have strengths. Event loops are, for example, awesome and people should use them where appropriate.

@wycats

This comment has been minimized.

Show comment Hide comment
@wycats

wycats Dec 20, 2011

@indexzero @strmpnk that was exactly my point. Your argument holds true for threads and fibers equally, except that fibers might be a (small) constant multiple cheaper. People assume that threads consume 8MB of RAM, while fibers consume only 4k, and therefore threads are 2000 times less efficient. My comment disproved that.

wycats commented Dec 20, 2011

@indexzero @strmpnk that was exactly my point. Your argument holds true for threads and fibers equally, except that fibers might be a (small) constant multiple cheaper. People assume that threads consume 8MB of RAM, while fibers consume only 4k, and therefore threads are 2000 times less efficient. My comment disproved that.

@argent-smith

This comment has been minimized.

Show comment Hide comment
@argent-smith

argent-smith Dec 20, 2011

U ROOL!!!

U ROOL!!!

@kschiess

This comment has been minimized.

Show comment Hide comment
@kschiess

kschiess Dec 20, 2011

Event loops are the goto of the new century.

Event loops are the goto of the new century.

@cris

This comment has been minimized.

Show comment Hide comment
@cris

cris Dec 20, 2011

@tenderlove: will you ship it with upcoming Rails 3.2?

cris commented Dec 20, 2011

@tenderlove: will you ship it with upcoming Rails 3.2?

@gerhard

This comment has been minimized.

Show comment Hide comment
@gerhard

gerhard Dec 20, 2011

This is F U C K I N G genius! Best gem of 2011.

gerhard commented Dec 20, 2011

This is F U C K I N G genius! Best gem of 2011.

@chrisfinne

This comment has been minimized.

Show comment Hide comment
@chrisfinne

chrisfinne Dec 20, 2011

Best library I've seen since https://github.com/lazyatom/acts_as_hasselhoff

@jwoertink

This comment has been minimized.

Show comment Hide comment
@jwoertink

jwoertink Dec 20, 2011

HAHAH. awesome. I don't know what some of you talking about there's no tests. Looks like a full test coverage to me :p

HAHAH. awesome. I don't know what some of you talking about there's no tests. Looks like a full test coverage to me :p

@joelparkerhenderson

This comment has been minimized.

Show comment Hide comment
@joelparkerhenderson

joelparkerhenderson Dec 21, 2011

Really excited to hear about the feature roadmap for Fibur 2.0.

Really excited to hear about the feature roadmap for Fibur 2.0.

@ZhangHanDong

This comment has been minimized.

Show comment Hide comment
@ZhangHanDong

ZhangHanDong Dec 22, 2011

太坑爹了!

HaHaHa. The test is awesome.

太坑爹了!

HaHaHa. The test is awesome.

@zekus

This comment has been minimized.

Show comment Hide comment
@zekus

zekus Feb 2, 2012

LOOOOL this is the best library ever made!

zekus commented Feb 2, 2012

LOOOOL this is the best library ever made!

@hemanth

This comment has been minimized.

Show comment Hide comment
@hemanth

hemanth Jun 27, 2012

Fibur = Thread 👍

hemanth commented Jun 27, 2012

Fibur = Thread 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment