Node.js vs Python vs PyPy – A Simple Performance Comparison

NQueensGraph

IMPORTANT NOTE: The NodeJS algorithm had a slight discrepancy in it.  See this article for a correction to the performance comparison section of this article.

The Algorithm

Yesterday, I decided to try translate my algorithm for calculating N-Queens to JavaScript.  I’ve implemented the same single-thread, brute force, recursive algorithm in many different languages with the biggest difference being the syntax of the language.  Once I completed the JavaScript Implementation, I ran the program with the latest version of Node.js.

The Findings

I knew Node was fast but it still surprised me.  As you can see by the included charts, the performance difference between Node.js and out-of-the-box Python is pretty significant.  Its not until the algorithms complexity and recursion depth hit certain limits that Node.js’s performance starts to falter.

Node.js and CPython – What’s The Difference?

You might ask what is behind this performance difference.  The answer is actually pretty simple.  It all boils down to how the code is being executed.  Node.js uses the V8 JavaScript Engine (Wikipedia | Google) written by Google and a part of the Chrome Browser.  V8 includes a just-in-time compiler that compiles the JavaScript to machine code before execution and then continuously optimizes the compiled code.  Python is a bytecode interpreter; meaning that the default interpreter (CPython) doesn’t execute Python scripts directly.  Instead, it first generates a intermediate file that will later be interpreted at runtime.

Ways To Get Better Performance

If you want to use Python, we can overcome the differences between Node.js and vanilla Python by using PyPy, an alternative implementation of Python that includes a just-in-time compiler.  For the algorithm I wrote, you can see a pretty good performance boost over Node.js when using PyPy.

Special Notes

  • I’ve placed my source on GitHub at the following url: https://github.com/chaddotson/puzzles
  • This is just with one type of algorithm, the best solution might and probably does change depending on what type of application you are researching.  For webserver performance, Node.js is slightly better than PyPy running Tornado.
  • This algorithm is a simple brute force algorithm, there are many faster and better ones out there.
  • At a board size of 15, Node.js could no longer run the algorithm due to its maximum recursion limit.
memory_usage
memory_usage_chart

Edit – A Follow-Up

The original focus of this article was shear performance, but I’ve received a question regarding the memory footprint of the 3 methods.  I think that is a very good and valid question.  So, I reran the tests to capture the peak memory utilization by each.  For this test I used “/usr/bin/time -l” to capture the maximum resident set size.  While this isn’t exactly the peak amount of memory utilized by the algorithm, it is sufficiently close to report on.

New Findings

Upon rerunning the tests for capturing memory utilization, I found that for the most part memory utilization contrasts performance.  A higher memory utilization isn’t really unexpected, if you think about it.  Essentially, the jit is sacrificing memory for performance.  In most cases, this isn’t really that bad.  Using a jit is just a cheap way of boosting performance of code written in an interpreted language.  The boost in performance, speed of which it was written and the maintainability of it outweigh memory utilization concerns in many cases.

The Oddity

As you can see, I’ve included a chart covering all the solutions for boards 8×8 to 14×14.  During most increments in board size, the memory utilization seems to increase exponentially; however, when we hit the 14×14 board size we see all the cases level off at relatively the same memory utilization of around 300 MB.  At this time, I really don’t have a good answer for this.  I could certainly speculate, but I’d rather not until I know more.

 

Posted by Chad Dotson

A small town Computer Scientist / Software Engineer. Chad enjoys writing Python and JavaScript as well as tinkering with his Raspberry Pi and Arduino. When not programming, he enjoys Photography (especially lightning) and Sci-Fi.

28 comments

Thanks for the article. What was the memory footprint of the three systems?

Chad Dotson

Excellent question. I will post an edit with the details shortly.

Chad Dotson

I’ve just posted an edit focusing on your question.

I have checked out your source code. Particularlly, the node.js and pypy one, the algorithm used are not the same. pypy has a early escape in valid_position(), while javascript do not. After correction, node.js beat pypy consistantly.

what is more, the inclusion of luajit in repository is quite interesting. It is a shame that hardly found comparison between luajit and other language/runtime after Computer Language Benchmarks Game exclude luajit.

Nice find. I can’t believe I did that. I’ve merged the pull request and will update this article and data.

[…] is a followup to my original post: Node.js vs Python vs PyPy – A Simple Performance Comparison.  This article corrects a discrepancy caused by a slight difference in the JavaScript […]

Void Star

I am puzzled by this!
I tried to do a similar performance comparison between python and node.js and found python to be 8-10 times faster. It is a simple string permutations finding program implementing an recursive algorithm. I tried keeping it simple but I feel it’s still complex enough to get a good comparison (it has strings, string manipulation and recursion).
Here is the code:
Node:
var toPermute=”123456789″;
function permut(perm, remaining){
if(remaining.length==0)
console.log(perm);
for(var i=0; i<remaining.length; i++){
permut(perm+remaining[i],remaining.slice(0,i)+remaining.slice(i+1));
}
}
var p=''
permut(p,toPermute);

Python:
toPermute='123456789'

def permut(perm, remaining):
if len(remaining)==0 :
print(perm)
for i in range(len(remaining)):
permut(perm+remaining[i],remaining[0:i]+remaining[i+1:])

p=''
permut(p,toPermute)

So what's going on here?

Chad Dotson

First guess is costly IO and how it impacts the different interpreters. Simply replacing the console and prints with a summation results in a vast speedup.

$ time node perm.js
permutations = 362880

real 0m0.143s
user 0m0.116s
sys 0m0.020s

$ time python perm.py
permutations = 362880

real 0m1.043s
user 0m0.937s
sys 0m0.011s

$ time pypy perm.py
permutations = 362880

real 0m0.605s
user 0m0.316s
sys 0m0.054s

You are right, removing console and print does change things (dramatically I believe) but now the node version seems to be faster than a pure C implementation. Maybe it has something to do with the way I handled the string operations in C but it shouldn’t be slower than an interpreted language (I used -O2 flag for gcc) should it? How is this possible?
Results on my Intel C2D laptop:
$time node permut.js
permutations=362880

real 0m0.238s
user 0m0.224s
sys 0m0.016s
$time ./permutc
permutations = 362880
real 0m0.335s
user 0m0.332s
sys 0m0.000s
$time python permut.py
permutations=362880

real 0m1.015s
user 0m1.004s
sys 0m0.008s
$

I did some modifications to the C code (used memcpy instead of sprintf to do string slicing) and also compiled with gcc -O3 flag and now node is roughly as fast as the C version which is still amazing IMHO(python is about 6 times slower). I increased the permutation length to 10(“1234567890”) to better see the differences.
$time ./permutc
permutations=3628800
real 0m1.579s
user 0m1.564s
sys 0m0.000s
$time node permut.js
permutations=3628800

real 0m1.564s
user 0m1.564s
sys 0m0.008s
$time python permut.py
permutations=3628800

real 0m9.333s
user 0m9.289s
sys 0m0.024s

Chad Dotson

Keep in mind that it maybe an interpreted language, but the V8 engine that node uses includes a just-in-time compiler. So, technically once its executing its no longer interpreted. Strictly speaking of course.

Does that mean it executes some kind of native code?
But that would mean every execution would have the overhead of compiling the code. Is the compiler so fast that it doesn’t impact execution time significantly?
I must do some research, I don’t think I understand exactly how JIT works….
Google coders never cease to impress me, I recently was blown away by the voice recognition in the latest versions of Android. It understands me almost flawlessly and I’m not a native English speaker. GJ Google!

Venkateswaran

I think you need to revise your title as follows “Js vs python vs pypy”, Just because it is written in JS, it doesn’t mean it’s NodeJS. Please revise your code with async functionality (this is what makes node faster than others). Until then don’t blame NodeJS.

Chad Dotson

I prefer the current title as the engine will have a significant impact on the execution. Also, each algorithm can be speed up in different ways depending on language; the goal was to implement the algorithms as similarly as possible. Even in the initial test, I believe it performed really well and I intended for that to come across in the article. I’ve since written a follow-up with new findings. Turns out my JavaScript implementation did not have an optimization that my python implementation did. You can find the followup here: . Personally, I am really impressed with the performance of Node and JavaScript in general.

Great article!

>this is what makes node faster than others
ORLY? Lol

So, as i understand even after correction typo in js pypy beats node. And yes, this is exactly that i see in my practice (python/js is not my cup of tea, so i just use it as script languages for condold tools). Unfortunately.

Sorry for typo *console tools :)

Chad Dotson

Actually node still outperforms python, see the follow-up I did. http://www.cdotson.com/2014/12/node-js-vs-python-vs-pypy-a-simple-performance-comparison-updated/

Uákiti Pires

Very nice and important results

Leave a Reply