Add bytecode cache to Ruby
github.com
github.com
Speaking of compiler, the JIT for Ruby development has been very quiet for months.
The Lua community has found that bytecode is actually slower to load than it is to generate from source: The extra latency of loading the (larger) bytecode from disk/ssd/flash, exceeds the cpu time to lex/parse.
(slightly out of date: http://programmingisterrible.com/post/42432568185/how-to-par...)
Ouch.... Perhaps a lesson in making your language's grammar too complex: if you do, you'll eventually have to pre-compile.
I know Perl is in that category.
e.g. is "foo" a method call or an instance variable? You can't know in isolation, but it doesn't matter at parse time, as if it's part of a larger construct that is only valid as a method call, such as if there's an argument list after "foo", it is parsed as a method call. E.g:
foo = 1
foo(42)
will result in: test.rb:4:in `<main>': undefined method `foo' for main:Object (NoMethodError)
I think all of the potential cases that might have otherwise made Ruby impossible to formally parse are resolved in similar ways.Now, there are certainly layering violations. The aforementioned example of "foo" by itself can only be resolved by determining whether or not "foo" is in scope as a local variable at the point it is referenced, for example, but you can opt to defer the decision until after parsing.
No I don't think anyone has suggested that, have they?
However, IIRC, no one has been able to re-implement parse.y; jruby and the other ruby re-implementations copied it and made what adaptations were necessary.
Our syntax probably isn't simpler :)
- you don't drop the disk cache; so you're loading from RAM (echo 3 > /proc/sys/vm/drop_caches)
- you don't have very complex code
- you timing includes starting/loading the intepreter itself.Yes that is why I said the benchmark measures "just CPU impacts."
I don't think dropping cache alone would be sufficient to simulate loading of a large project -- I think the benchmark would also need to split the input across many files to recreate the seek time effects.
> you don't have very complex code
Given what I know about parsers, I highly doubt that the performance profile of parsing real code differs from my benchmark very much (ie. >30%). But I'm happy to be proven wrong on this, if anyone wants to try.
> you timing includes starting/loading the intepreter itself
That is why the benchmark makes the files pretty big, so the constant interpreter startup overhead is not a significant factor. Again, I don't think that you're going to be able to significantly change the shape of the results by adjusting for this.
The actual time spent loading/parsing files is in most cases a tiny fraction of the startup time of any large project using rubygems and bundler.
I counted several hundred thousand unnecessary stat calls on the biggest app I have, and ended up with a ugly hack where we trimmed the load path around each set of require's to only paths needed by that specific gem.
export PYTHONDONTWRITEBYTECODE=true is the first thing anyone should be doing.
I guess it figures that we copy each others' mistakes.
The overhead is tiny, less than milliseconds for sane modules. It's a useless optimization, especially when it can be done at install time only, say, as opposed to for every module import, but even that is somewhat silly.
Not sure about the exact conditions when this occurs, but it's definitely happened to me when code is refactored and you pull the newest version in with git.
https://speakerdeck.com/a_matsuda/the-recipe-for-the-worlds-...
50 million unique user / month 15,000 Req/Sec
I posted this awhile ago but didn't pulled much attention
This would just improve the time to initially start a Ruby process.
Ruby web apps on the other hand tend to be run in a loop - the script never exits after a request, it just goes back to the top of the loop to accept the next request to serve.
e.g. https://github.com/rack/rack/blob/master/lib/rack/handler/fa...
FCGI.each { |request|
serve request, app
}
It's nice because it completely decouples your app's startup time from request processing time, so you can do expensive setup stuff up-front without slowing down each request. The disadvantage to that is there's not much pressure to keep startup time low, since it's only happening once.