Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created August 19, 2012 14:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ahoward/3395045 to your computer and use it in GitHub Desktop.
Save ahoward/3395045 to your computer and use it in GitHub Desktop.
threads are so dang awesome
=begin
READ THIS FOR CONTEXT:
http://tenderlovemaking.com/2012/07/30/is-it-live.html
=end
strategy =
String(ARGV.shift || 'thread')
n =
Integer(ARGV.shift || 4)
mb =
2 ** 20
a = b =
nil
size =
0
case strategy
when /THREAD/i
threads = []
q = Queue.new
a = Time.now.to_f
n.times do
threads << Thread.new do
Leak!(mb)
q.push Thread.current.object_id
sleep
end
end
n.times do
q.pop
end
b = Time.now.to_f
size = Process.rsize
when /FORK/i
pids = []
a = Time.now.to_f
n.times do
r, w = IO.pipe
if pid = fork
pids << pid
w.close
_size = Integer(r.read.strip)
size += _size
else
r.close
Leak!(mb)
w.puts(Process.rsize)
w.close
sleep
end
end
b = Time.now.to_f
size += Process.rsize
end
y(
:n => (n),
:elapsed => (b - a),
:size => ('%2.2fmb' % (size / mb.to_f))
)
BEGIN {
require 'thread'
require 'yaml'
def y o
puts o.to_yaml
end
def Leak! n
Array.new(n){|i| i * rand}
end
module Process
def self.size pid = Process.pid
stdout = `ps wwwux -p #{ pid }`.split(%r/\n/)
vsize, rsize = stdout.last.split(%r/\s+/)[4,2].map{|i| i.to_i * 1024}
end
def self.vsize
size.first
end
def self.rsize
size.last
end
end
}
__END__
cfp:~ $ ruby --version
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin11.4.0]
cfp:~ $ ruby a.rb thread 4
---
:n: 4
:elapsed: 2.40175199508667
:size: 250.02mb
cfp:~ $ ruby a.rb fork 4
---
:n: 4
:elapsed: 1.227113962173462
:size: 246.90mb
cfp:~ $ ruby --version
jruby 1.6.7.2 (ruby-1.8.7-p357) (2012-05-01 26e08ba) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_33) [darwin-x86_64-java]
cfp:~ $ ruby a.rb thread 4
---
:n: 4
:elapsed: 13.164999961853
:size: 186.55mb
@jjb
Copy link

jjb commented Aug 19, 2012

You're growing your memory inside the thread instead of outside of it. Is Array.new(mb){|i| i * rand} meant to simulate the amount of memory used in an app per request?

@ahoward
Copy link
Author

ahoward commented Aug 19, 2012

yes.

i know threads, on paper, use less memory. i was just pointing out that something even this simple will take people hours to reason over. it's also interesting that jruby runs out of memory 1st using threads... meanwhile, i can open 'top' or 'passenger-status' and know precisely how much memory my apps are taking.

@jjb
Copy link

jjb commented Aug 19, 2012

Hmm okay. My mental model of a typical app's static vs. per-request memory usage is something like 10/1 or 20/1, but maybe I am mistaken. Do you know an easy way to benchmark memory usage per request? (or, more directly, the size of a thread inside puma?)

@ahoward
Copy link
Author

ahoward commented Aug 19, 2012

just pushed up an apples-to-apples comparison.

your point about how much memory is shard is well taken: for sure copy-on-write with a fork model deteriorates more easily in that sense. however, competition for resources in threads is a huge potential bottleneck too.

i believe what this shows is that overly simplistic claims like 'threads are fast', 'threads use less memory', 'forking is expensive', etc - are just that: simplistic.

it's not like c vs. ruby where c is just flat out going to win. it'll depend greatly on you application and little things can make it go wildly wrong. for instance jruby is going to use less memory in a threaded setup for one app. however, if you deploy lots of apps and don't grok how the jvm itself works you'll take your box down. contrast that with the a passenger/process model where the kernel manages sharing memory via mmap, shared libraries, and copy-on-write semantics and the app server makes sure quiet processes shutdown/started on demand and you could easily have a situation where a passenger/process box loaded with 100 apps was using way less memory that the same box loaded with 100 jruby apps on mongrel/unicorn/whatever.

it is simply complex.

@jjb
Copy link

jjb commented Aug 19, 2012

your apples-to-apples comparison is appreciated and interesting -- but I still think the main issue is memory sharing amongst processes vs amongst threads, and i haven't seen anything elsewhere that would suggest that CoW is anywhere near as effective at sharing memory as threads are.

That said i don't have any numbers myself regarding per-request and/or per-thread memory usage, so I should come up with a demonstration for that before pushing this further.

@ahoward
Copy link
Author

ahoward commented Aug 19, 2012

wow! https://gist.github.com/3397719

support for leveraging the OS - not replacing it. now if only macruby ran on linux ;-)

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