
What's Going On (on "Why Python, Ruby, and Javascript are Slow") - ANTSANTS
http://jmoiron.net/blog/whats-going-on/
======
NateDad
Everyone keeps saying "Use Python and write the slow parts in C"..... except
that writing GOOD (secure, correct, safe) C is really hard. That's actually
the main reason why the Go authors came up with Go. Not to replace Python etc,
but to replace C/C++.

I'm kind of surprised that all the "Python: let's get stuff done" people are
suggesting writing a whole bunch of code in C, one of the least "Getting stuff
done" languages there is.

For those who have asked what the productivity of Go is like vs. Python + C:
Go is very productive. When newbies to Go say it's "verbose" they mean "I have
to write out some 3 line loops that in other languages are one line". If
that's the major cause of slowness in my development cycle, I'd be ecstatic.

Go is also very easy to write "correct" code in... no buffer overflows, no
memory leaks, no access of random memory, no unnecessary allocations, and a
very clear and defined behavior from clear, concise code.

~~~
officemonkey
>"I have to write out some 3 line loops that in other languages are one line"

I think every vim, emacs and decent IDE user would say "I has a text editor.
Your argument is invalid."

~~~
stouset
On the flip side of this argument, code should be optimized for _reading_ ,
not writing. Short, idiomatic code IMHO optimizes for reading. Code like

    
    
      array.map(&:to_s)
    

parses instantaneously. Whereas the procedural equivalent

    
    
      strings = []
      for (element in array)
        strings.push element.to_s
      end
    

requires scanning through the loop to extract out the logic specific to this
case. And it's easy to accidentally overlook something when mentally ignoring
the "boilerplate".

Not to make a mountain out of a molehill, and not commenting on Go
specifically, but the extra "cost" of this style of code adds up when the
language itself gets in the way of using high-level abstractions. Methods that
are three meaningful lines long are inherently more understandable than
equivalent methods that are twelve. Multiply this cost across an entire class,
file, and/or project, and it can have a significant impact on one's ability to
grok the expected behavior.

~~~
pacala
On the flip side, I had to scratch my head on what "&:" is supposed to mean.
Or perhaps it's "&" ":". I know, I'll Google it </yeahright>.

More seriously, "for ..." maps into "map", "foldl", "foldl'", "foldr" and
perhaps other constructs. They do pretty much the same thing, but I don't
necessarily see a real cognitive advantage of either style.

~~~
Falling3
stouset is obviously not arguing that the ruby version with "&:" is
immediately obvious to any programmer. The point is if you know ruby, you will
understand that line a lot quicker than the second example.

~~~
pacala
What is the "fold" variant in Ruby? Maps are trivial either way, but folds are
a pita in my functional programming experience.

~~~
bjeanes

      [1, 2, 3, 4, 5].reduce(0, &:+) #=> 15
    

Which is short hand for:

    
    
      [1, 2, 3, 4, 5].reduce(0) { |memo, item| memo + item } #=> 15

~~~
pacala
Yes and no. In practice, you're going to fold into a different domain, which
causes the types of the reduce function arguments to be different. Moreover,
you are not going to have your reduce function conveniently in a library. You
are going to have to write a loop body. At which point, choosing a block over
a for loop becomes purely religious.

~~~
krichman
Different domain is fine.

    
    
        [1,2,3].reduce([], &:unshift) #=> [3,2,1]
    

I don't see a problem with writing a for loop, but I think it's not as
composable and it can be a little harder to read. So I don't think it's purely
religious but it's close to that.

~~~
pacala
Randomly searching for foldr on code.google.com

    
    
        inserts :: [(VarId,VarId)] -> Env -> Env
        inserts vvs env = foldr (\(a,b) acc -> M.insert a b acc) env vvs
    

or

    
    
        inserts(vvs, env) {
          for ((a, b) : vvs) {
            env = M.insert(a, b, env)
          }
          return env
        }
    

Pick your poison, but I'll stick with fors. Go blues!

~~~
krichman
If you want inscrutable Haskell you should go with the <$> operators :) I'd
personally still rather have a

    
    
        vvs.reduce(env){|env, (a,b)| M.insert(a, b, env)}
    

but the for loop is perfectly readable also because it fits on a single line.
I think for longer bodied reduces I might prefer a for loop actually.

------
d0m
Time of "Getting that shit done" and cost of long-term maintenance are two of
the main reasons why people use Ruby/Python, not for the raw performance. I
thought that was an old debate. And anyway, from my experience, Python has
never been the cause of the slowness.. It's more about tweaking that DB query,
smartly packaging the assets or using a better algorithm. (I know it's not
always the case. I.e. If my program has to run in the space where bits could
be randomly shifted, some additional thoughts would be needed, obviously.)

~~~
jes5199
Yeah: your real bottlenecks are going to be 1) getting data out of your
database and 2) sending packets across the internet. These are 100 times and
1000 times slower than your program, the 10x penalty to use ruby/python
instead of C/C++/go/rust isn't going to matter.

~~~
coldtea
> _Yeah: your real bottlenecks are going to be 1) getting data out of your
> database and 2) sending packets across the internet. These are 100 times and
> 1000 times slower than your program, the 10x penalty to use ruby/python
> instead of C/C++/go/rust isn't going to matter._

Only if you're talking about the typical CRUD, website, etc program.

Why should we? People use Python for numerical analysis, scientific computing,
etc. Why not let them be able to do that, while still writing in Python
(instead of using libraries that delegate to C/Fortran/etc)?

And even a website can do intensive computation work. Why HAVE to write it in
2 languages?

~~~
anonymous
The people who use python for numerical analysis use solutions like numpy,
with which their programs spend most of their time executing optimised numeric
routines.

~~~
yummyfajitas
If you can structure your program in such a way that all your inner loops are
handled by BLAS/LAPACK/FFTW, then python works great.

The sad fact is you can't always do this. Then you are forced to drop down to
C/C++/Fortran.

It would be great if you didn't always have to do this.

~~~
travisoliphant
This is exactly what Numba is designed to help with. And it already works
extremely well for such cases. Numba lets you have ease of use (un-typed
containers) and speed (with typed-containers) in the same run-time and the
same syntax. Here is are some IPython notebooks which illustrate:

<https://www.wakari.io/nb/travis/Using_Numba>
<https://www.wakari.io/nb/travis/numba>

And the slides from PyCon Talk: [https://speakerdeck.com/pyconslides/numba-a-
dynamic-python-c...](https://speakerdeck.com/pyconslides/numba-a-dynamic-
python-compiler-for-science-by-travis-e-oliphant-jon-riehl-mark-florisson-and-
siu-kwan-lam)

~~~
yummyfajitas
Numba is great for numerical expressions, but I'd be surprised if it works for
trickier bits.

For example, consider the problem here:

<http://arxiv.org/abs/0903.1817>

Ultimately I needed to run a math formula on some surfels and connect an edge
in a graph if the formula came out True. Following that I had to prune a bunch
of edges in the graph. In 2009 (when I did it), I had to drop down to c++.
(Admittedly, this was also partly due to a lack of a good python graph lib.)

I'd be very surprised if Numba is helpful with something like that - the
branching and complex data structures don't seem like a great fit for it.

------
kalkin
It's too bad that this discussion is ending up mostly in language wars,
because Alex' talk was about how to improve the situation, not how to prove
that your favored technology is always the right tool for the job and
everybody else is wrong.

Let's say that you're doing some NLP and you want to pre-process your strings
in Ruby because its easy and elegant. Idiomatically you might write something
like "my_objs.map(&:to_s).map { |s| s.gsub(/\W\S/+, '') }.reject(&:blank?)" -
perhaps broken up into multiple lines - but you're allocating three arrays and
re-allocating every string. And in real life you're probably doing more pre-
processing steps which involve additional arrays and possibly additional
string re-allocations.

You could switch to gsub! which does the non-word stripping in place, but it
"Performs the substitutions of String#gsub in place, returning str, or nil if
no substitutions were performed", so you're adding an extra conditional. And
it's going to be harder to avoid re-constructing a bunch of arrays.

There's no intrinsic reason you couldn't have basic Ruby language APIs which
let you write code which chains operations just as naturally and obviously,
but don't wastefully re-allocate. Think of how ActiveRecord relations defer
the actual SQL execution until it's unavoidable.

(Haskell-style laziness might solve the main issue above, but I've run into
analogous unavoidable-allocation problems with Haskell. If I'm depth-first
searching a board game state-tree I shouldn't have to re-allocate the whole
state representation at each step, but it's pretty hard to avoid while writing
elegant Haskell since modifying memory in-place has side-effects.)

I don't know Go, but maybe instead of talking about why people who still use
Python or Ruby are or are not stupid, we can talk about what it gets right
that other languages can adapt?

------
joe_the_user
_"Programmers of elegant scripting languages desire similarly elegant looking
code with flexible data structures even where no flexibility is in play. Some
who know better will understand that technical limitations make the difference
between hashes and other types of named lookups essentially nil for many
popular language runtimes."_

OK, if no flexibility is in play in many usages of hashmaps, should not an
intelligent interpreter/compiler be able to _detect this_ and modify the use
of the hashmap accordingly? Maybe even use a different lookup system in those
cases? It seems like that would be ... elegant.

The reason VMs don't need to be slow is that you don't need to treat an eval
call literally, you don't always need to create a full environment to parse a
twenty character string, right? But if you can get around that, should you not
be able to get around hash-map's problems? Am I missing something.

~~~
jes5199
Doesn't the V8 javascript engine do something like this? Where it builds
C-structs instead of hashmaps if it can detect that your objects are being
built in a consistent way

~~~
maggit
Yes. They call it "hidden classes":
<https://developers.google.com/v8/design#prop_access>

------
voidlogic
Since the author uses Go as a basis of comparison. I thought these benchmarks
from The Computer Language Benchmarks Game might be interesting:

Go vs. Ruby:
[http://benchmarksgame.alioth.debian.org/u64/benchmark.php?te...](http://benchmarksgame.alioth.debian.org/u64/benchmark.php?test=all&lang=go&lang2=yarv)

Go vs. Python:
[http://benchmarksgame.alioth.debian.org/u64/benchmark.php?te...](http://benchmarksgame.alioth.debian.org/u64/benchmark.php?test=all&lang=go&lang2=python3)

Note: These results are from the 64-bit single core machine since it has
already been updated to Go 1.1 beta 1. There is also a faster Go regex program
(#7) that hopefully can be made to work again after 1.1 is released (in place
of #1).

Although still using Go 1.0.X ,the recent TechEmpower benchmarks might be
interesting if you have not seen them:
[http://www.techempower.com/blog/2013/03/28/framework-
benchma...](http://www.techempower.com/blog/2013/03/28/framework-benchmarks/)
(It looks like Go 1.1 _might be_ 3x faster:
<https://gist.github.com/errnoh/5320784>)

P.S. As always your own program is always the best benchmark to use, your
millage may very, take your gain of salt, etc etc.

~~~
acqq
On the same site, the advantages of Go vs good dynamic language implementation
like JavaScript V8 aren't so impressive:

[http://benchmarksgame.alioth.debian.org/u64/benchmark.php?te...](http://benchmarksgame.alioth.debian.org/u64/benchmark.php?test=all&lang=go&lang2=v8)

~~~
melling
Being 2-10x faster and using a lot less memory isn't impressive? I'm sure the
Go optimizer still needs a lot of work. It's going to get much better. The V8
optimizer is great but we're probably closer to the limit of how much more can
be done than with Go.

~~~
army
I think there's a bit of a disconnect here between people who work on
applications where, say, a 25% reduction in CPU usage would be a big win, and
people who don't worry about performance as long as their cluster isn't
melting down.

------
ilaksh
I am sure I am just going to have this comment buried because people don't
like to have Python and Ruby compared unfavorably, but I have to try to help
people out.

Looks like a lot of people STILL haven't caught on to how big of a performance
advantage Node.js's V8 engine gives you over Python and Ruby. Look at
benchmarks like this one:
[http://fengmk2.github.io/blog/2011/fibonacci/nodejs-
python-p...](http://fengmk2.github.io/blog/2011/fibonacci/nodejs-python-php-
ruby-lua.html)

Most of the time modern JavaScript is compiled to native code. Its much faster
than you realize.

The cleanest code is a new language called CoffeeScript which compiles to
JavaScript. Its more Pythonic than Python. Its the closest to pseudocode that
you can get (especially if you avoid a few Rubyisms).

The best APIs available are in Node.js modules (see npmjs.org). Best meaning
most advanced API design in that the modules are focused, decoupled, very
straightforward. This is also by far the best design for package management
and dependencies since it is very open and flexible.

On top of that, Node.js is built from the ground up to support highly
concurrent input/output. It just amazes me that people haven't figured out
what a big advantage Node.js and CoffeeScript provide over Python or Ruby.

I actually think that a big thing holding people back is that Node.js and
CoffeeScript make things so much easier, people think its cheating, and are
afraid their programmer friends will think less of them if they take advantage
of something that really simplifies their jobs.

~~~
TazeTSchnitzel
CoffeeScript is horribly... stabby? Its ambiguous syntax makes it easy to hurt
yourself.

And node.js, while great, is not magic.

I would also much rather use Python's classes than do JS OOP or use CS
classes. That said, I use node.js more often than Python.

~~~
ilaksh
How much time have you spent using CoffeeScript? It might take a week or two
to get used to it. I think if you just stick with some basic stuff and write
it like Python with less punctuation then it is pretty straightforward and CS
classes are very straightforward, what's wrong with them?

Just leave out the outer parenthesis and put them in after that.

~~~
TazeTSchnitzel
I've spent plenty of time with CoffeeScript, and I don't like it. It's not
explicit enough, and the syntax is too permissive. It's easy to get bitten by
things like omitting parentheses when calling a function, which creates hard-
to-spot bugs. CoffeeScript also creates poorly performing code by default, by
treating everything as an expression and returning it unless you tell it not
to.

------
pjmlp
Overall nice article, it just looses a bit by focusing too much on Go vs
Python issue.

1 - The are quite quite a few strong type languages with native code
implementations since the mid-80's, even GC enabled, Go is not the only one.

2 - There are dynamic languages like Self, Dylan, Lisp even the young Julia
that have implementations which achieve C like speeds when compiled to native
code, especially when the developers make use of type annotations and use the
right data structures.

Self is specially important given that the research work, in a dynamic
language, ended up being the heart of Sun's JIT compiler.

~~~
batgaijin
Yeah I don't sympathize with ruby/python/go people. CL can be interpreted &
compiled. Why the fuck is it okay to literally choose the worst options just
because people don't like seeing an explicit AST?

~~~
pekk
Why specifically are these "literally the worst" options?

------
Moto7451
I've noticed this in some code I've been refactoring at work recently (a feed
generation system). It's easy in Perl to suck in a large amount of data into
one array and map/grep it into another array of hashes and output
filtered/formatted data in a few lines. The problem of course is that your
business'/project's own growth overwhelms the algorithm as the data structure
balloons. This is further compounded by libraries which are designed to accept
an array as input (reinforcing the idiom causing the first problem) since you
end up with thousands of additional iterations.

I refactored the code to lazy load each item in the collection and
map/filter/generate the XML at the end of each node's iteration. The end
result is a nice flat memory space (save for the odd leak here and there)
that's about 1/10th of the original program's, at the 'expense' of only a few
more lines of code.

------
Xcelerate
This thread has got me cracking up. I'm sure all of you are very smart, yet
you all treat each other as though the other person is a stupid, incompetent
programmer. It's a trend I observe everywhere in online programming
communities.

~~~
teeja
But all the others are stupid, incompetent programmers. It started at MIT.

Just kidding! (sort of). But have a look at the Jargon Appendix
(<http://catb.org/jargon/html/pt03.html>) - especially The Story of Mel
(<http://catb.org/jargon/html/story-of-mel.html>).

------
cjh_
Very interesting read.

I have only written a small amount of Go, I have been through tour.golang.org,
just waiting for the right project for me to use it on.

Currently whenever a new project comes along I reach for C or Perl, hoping to
find a niche between them for Go.

~~~
berntb
The feature with scripting languages compared with system languages (C, Java,
etc) is that they are fast -- not to run code, but to develop functionality.

The article argued that idiomatic Go gave at least as good runtime speedup as
a normal scripting language (Perl, Ruby, etc) with the (small) time critical
parts rewritten in C.

But it didn't touch the important question -- is the development time for Go
faster than for scripting languages with critical sections in C?

Go looks fun, but I'd personally want information on that metric before trying
it on a project. [Edit: Yeah, as the article noted, Go excels re
parallelization etc. I might Go for it in those use cases.]

(What I'd really want is to find a place which use Lisp, since development
speed is roughly as fast as for scripting languages and there has been good
compilers for decades.)

Edit: Spelling, must start going over the room for the keyboard when using the
iPad.

~~~
szopa
> But it didn't touch the important question -- is the development time for Go
> faster than for scripting languages with critical sections in C?

At YouTube (which is a predominantly Python shop) we use Go for some elements
of our infrastructure, and I spend approximately half of my time working in
each language (main project in Go, integration tests in Python). In my
experience easy things (say, split and massage some strings) are more
complicated/annoying in Go than in Python (and much more verbose). However,
_difficult_ things usually end up being _much_ simpler in Go than in Python –
especially if they involve concurrency (Python threading is a trainwreck). The
difficult parts of our code consume most of our brainpower, so that's a net
gain from our point of view. What is more, for some weird reason Go code seems
to be less susceptible to bitrot (go fmt FTW).

~~~
mratzloff
Even string splitting in Go isn't that bad, frankly.

    
    
        words := strings.Split(wordList, " ")
        foo := words[0]
        if len(words) > 1 {
          bar := words[1]
        }
    

Where things can get a bit tedious is data conversion from byte buffers, like
reading in data from disk or Redis or something. But that's no different from
any other statically-typed language.

I would like to see more libraries in Go, though. Much of what's complex is
there, but one of the benefits of languages like Python and Ruby is there are
lots of libraries for the easy stuff, too. Having to write a data model
validation library yourself is boring.

------
alexchamberlain
On the to garbage collect or not issue briefly mentioned at the end, the best
reason to use an unmanaged language is predictability of performance. You know
the GC won't step in at a critical moment. I regularly get frustrated by
Eclipse becoming unresponsive for several seconds.

~~~
pjmlp
There are real time GCs for many languages that compile to native code.

The way Eclipse is coded is not a good example of GC performance.

------
bm1362
I'm currently hitting some performance issues trying to use Python to make a
naive physics engine- does anyone have any reading recommendations on best
practices for python performance?

~~~
travisoliphant
I'd love to see how Numba helps you. Give it a try: <http://numba.pydata.org>

Slide deck: [https://speakerdeck.com/pyconslides/numba-a-dynamic-
python-c...](https://speakerdeck.com/pyconslides/numba-a-dynamic-python-
compiler-for-science-by-travis-e-oliphant-jon-riehl-mark-florisson-and-siu-
kwan-lam)

IPython notebook examples: <https://www.wakari.io/nb/travis/numba>
<https://www.wakari.io/nb/travis/Using_Numba>

------
plg
the endless debate continues...

choose two:

a. fast execution of the code on the machine

b. fast code generation by the programmer

c. fine-grained control over every aspect of the code

I know which two I'd rather have, esp in the long run.

~~~
travisoliphant
Numba is trying to let you make a different choice for different sections of
the same program so that you can do different styles of programming in code
meant to be a library than you might do in other sections of code meant to be
more "ocassional programmer" facing.

Some of you might find the roadmap particularly interesting. It's an open-
source project eager for input from others...

<http://numba.pydata.org/numba-doc/dev/roadmap.html>

------
g2e
The video has been posted for anyone interested:

<http://vimeo.com/61044810>

------
YesThatTom2
Wouldn't adding goland-style slices to Python be awesome?

~~~
almost
It's not quite the same but some of the use cases (and a bunch of other stuff)
would be covered by numpy arrays. You can create multiple views on the same in
memory data with different start and end points and different strides.

~~~
dagw
Agreed. I think numpy arrays a rather overlooked by most python programmers.
They allow all kinds of useful tricks like that that are quite handy even if
you're not doing strictly numeric work.

------
RegRegReg
The biggest confusion is seeing Python/Ruby/JS as a programming languages.
They are not. They are scripting languages. Something you put on top of your
core code.

~~~
pekk
Does the phrase "scripting language" have any hard technical meaning or is it
just another subjective label?

~~~
voidlogic
I've always informally thought of a scripting language as anything the
executes by interpreting source code or bytecode (lacking JIT compilation).

------
corresation
Both the submission and the target of the submission dismiss dynamic typing as
a source of slowdowns (in the unfortunately haughty, completely unsupported
manner that is too common in the industry. It is, in effect, the head-in-the-
sand approach: If you don't want something to be true, simply keep saying it
isn't and somehow that will become reality), yet I guess we need to define
what "slow" is then, using as an example an empirical test (ASM.JS) that
certainly serves better than some people waving their hands about innovations
in JIT compilers.

The primary optimization that ASM.JS brings is its static type system. With
that, and the obvious benefits to the code path, intensive activities can be
run at 1/2 native speed, versus the 1/12th of so native speed available with
dynamic typing.

Is 1/12th native speed "slow"? In most cases -- where the code is the duct-
tape that glues together native code (which is the case on web servers, admin
scripts, etc), no, it isn't slow at all. But from a basic optimization
perspective, asm.js sure seems to indict dynamic typing if you really care
about squeezing every cycle's maximal output.

~~~
gsnedders
No, the primary optimization asm.js brings (at least in OdinMonkey) is AOT
compilation.

Fully typed code should produce the same code from IonMonkey without
OdinMonkey: the problem is you have to get up to 10k calls (at least last I
looked!) of a function before you get there; but once you are there it is just
as good. It's the build-up, through the interpreter and the less efficient
code-generators (in terms of code generated, that is; they're more efficient
in terms of their own runtime), which really hurts.

~~~
corresation
_No, the primary optimization asm.js brings (at least in OdinMonkey) is AOT
compilation._

But it can do that precisely because of the static typing, which loops back to
exactly what I said. Looser, more dynamic language always have a cost
associated with that flexibility.

~~~
gsnedders
Arbitrary JS can be as statically typed as asm.js is (because, well, asm.js is
just a subset). There's no reason why one couldn't compile AOT something like:

    
    
      function add_and_double(a, b) {
        a = a | 0;
        b = b | 0;
        var sum = (a + b) | 0;
        return (sum + sum) | 0;
      }
    

Yes, it'd be trivial to fall off the AOT-compilation path (esp. without a
defined subset that got AOT compilation and hence likely would differ between
implementations), so static verification that you're on the AOT-compilation
path is valuable (which asm.js provides), but it doesn't fundamentally alter
the semantics of the language.

~~~
pifflesnort
You seem to be saying that the AOT path requires constrained typing and a
strict subset of the language semantics, which agrees with the parent's point
that static type semantics matter in terms of performance.

~~~
gsnedders
They matter, yes. But they aren't in any way specific to asm.js — it was
something that was already true in generic JS.

~~~
corresation
But it is extremely specific to asm.js -- the optimizations possible can only
happen because static typing is mandated. It is absolutely a subset of JS, but
JS can't make those optimizations because the guarantees aren't there.

