Why Python, Ruby, and Javascript are Slow
speakerdeck.com
speakerdeck.com
But all those things take time that is not spent running your program. On average, you can do pretty well. But it's still overhead. As you get farther along in your JIT, optimization algorithms get trickier and trickier, your heuristics, more complex. You will eventually hit the wall, and need to spend more time doing JIT'ing than doing real work to make optimizations to some code. This happens to every single JIT, of course. This is why they try to figure out which code to optimize. But even then, you may find there is too much of it.
Because of this, the languages are slower, it's just the overhead of better JIT algorithms, not slower code. In practice, you hope that you can optimize enough code well enough that nobody cares, because the ruby code takes 8ms, and the C code takes 5ms.
For example: Almost all of the allocations and copying can be optimized, but depending on the language, the algorithms to figure out what you can do safely may be N^3.
Also, PyPy is still pretty young in its life cycle (in this iteration of PyPy:P) for folks to say that they can make stuff much faster if they only had a few things. It really needs a very large set of production apps being rin by a very large set of folks for quite a while to see where the real bottlenecks still are. Past a certain point, you run out of optimization algorithm bullets. The way compilers get the last 20% is by tuning the algorithms for 10 years.
Of course, i'm not trying to slag on PyPy, I think they've done an amazing job of persevering through multiple rewrites to get somewhere that seems to be quite good now. I just am a little wary of a fairly young JIT saying that all big performance problems fall into a few categories.
The issue with benchmarks is surely well known, also by the PyPy authors; I wonder what the biggest application is that they have benchmarked or that runs on PyPy.
Your point on the JIT compiler interrupting program execution is certainly valid, too, but not necessarily so. One could easily do the code generation in a separate background thread and let execution switch over only if necessary. But, as you have already said, a latency issue certainly exists. This is one of the cases where interpreters usually have a leg up, and there are promising ways of optimizing interpreters.
1. On most current CPU's, this will cause really bad cache/memory thrashing, enough to probably impact the program.
2. This may actually cause significant slowdown, depending on how long it takes to optimize a given set of code (IE it may be better to spend 100ms paused optimizing than 5000ms in the background). This is, of course, a latency issue.
3. State of the art for most JIT's is still to use one thread. The number of folks doing actual parallel code generation is nil. So sadly, even if you had 4 cores, 3 empty, you'll still, at best, get to use one of them for the background thread doing the optimizing. There are parts that are trivial to parallelize if you've structured the JIT "right", but they aren't always the parts that are slow.
I'm actually curious if you have any stats on how much of the time this is being done on actual busy machines where it's going to compete for L1/etc resources vs how often it's able to be offloaded onto an otherwise empty core.
IE i expect their to be a significant difference in the use cases for JIT's like PyPy, which are probably going to sit on shared servers that folks are trying to maximize utilization of, vs desktops where I imagine most browsing probably doesn't use all cores at 100%.
Don't HotSpot and JRockit also do background (de)compilation & swapping of generated code?
Again, AFAIK, the server VM still has a significantly different set of tuning than the client VM. In particular, it runs some significantly more complex opts that the client VM does not.
ad 3) I am well aware of that. However, I remember that at PLDI'11 there was a talk from Univ. of Edinburgh chaps doing parallel trace-based dynamic binary translation. Obviously, DBT is less work than a high level, full-blown JIT, but at least it's not nil :)
speed.pypy.org has benchmark info on Django, Twisted and some other large, non-trivial codebases.
What I actually wanted to know, what the biggest application is, i.e., a not benchmark.
For example: Most compilers started doing structure layout and reorg optimizations (transforming structures with arrays into arrays of structures, and vice versa) to tackle specific benchmarks. In some cases, they discovered it also was useful generally.
But whatever the benchmarks are, that's often what gets targeted. You can't optimize in a vacuum, you need to know what you are trying to develop optimization algorithms to do. This usually occurs by taking user complaints/programs/whatever, finding out why they are slow, seeing if there is a common solution, and developing an optimization to do it.
So yeah, if you are targeting certain patterns, and the patterns change, ..
[1] IE You've removed all possible redundant computations, made it so everything is only computed among paths it is actually used, the calculations occur in lifetime optimal fashion, etc.
As for the "single thing" - it's just the next thing on the infinite list of things that can be optimized better. Having a better assembler backend would be good (but smaller) win, etc. etc. For now and for quite a bit in the future, it's clear what to do to make X Y or Z faster.
P.S.: "hash_set" in the slides should be "hash_map."
Lua is a fairly simple language for the user, in the sense that it uses a few general mechanisms rather than a plethora of more specialized ones. However this "simplicity" can be misleading because these general mechanisms are very powerful, and can generally be used to implement most things other languages have specialized abstractions for. This sort of powerful and general mechanism is actually fairly hard to write an optimizing compiler for, because there's little stated explicitly about what's going to happen at runtime, and few obvious constraints about what's allowed to happen.
The reason LuaJIT (and most modern JIT compilers, although LuaJIT seems better than average) is so fast is because it uses very very local (in terms of both location and time) runtime context to know when to specialize operations that are conceptually much richer, e.g., "this number is really an integer" (Lua does not have a separate integer type), or "I know what function/operator will be called here (even though it conceptually dispatches through a table or metatable), so I can inline it or use a direct call."
I'm less sure about python (never used it much), but almost all the differences seem be user-facing ones -- large amounts of syntax-sugar for things that Lua offers sufficient mechanism for, but no built-in UI (in the programming-language sense). As it's the power/complexity of the mechanism that matters, not the UI, such user-facing richness doesn't really affect a JIT compilers.
I won't argue about the JS (although DOM interaction comes to mind as a big headache), but in Python syntax is trivial. It's all the semantics and not just how they're, but how much of it. descriptors, metaclassses, new/old style classes, tons of builtin types, tons of builtin modules, all of it the user will expect to seamlessly integrate with the JIT compiler.
... and this is pretty much his point: He can keep optimizing, but the moment you start passing complex objects around and copying them all over the place, instead of passing raw buffers around and operating on them in place, you've massively raised the bar in terms of the complexity of the necessary optimizations needed.
.) Any profilers providing information reg. the heap-allocs as part of the execution costs?
.) Any Runtimes / VM actually optimizing the layout of those omni-present List/Array/Hashtable/Bag/Set/Dict of MyObjectTypes to have elements laid out as close as possible in memory? (The position of the actual objects that is not only the pointers to the objects within the containers)
Are we "safe" as heap allocs / gc / memory handling is really of no significant impact compared to other issues? Or is memory handling an large part of the "What Andy giveth, Bill taketh away." story?
Almost all of our allocation/freeing costs are cheap O(1). Pretty much everything is zero-copy. That is why I cringe a little when I hear that GC's are "faster than manual MM". Because manual MM done well can eliminate almost all of the costs of memory management.
I also cringe a little when I see "malloc" deep inside C functions.
I really like the basic convention in C that you should pass needed allocations in as arguments (and it is virtually always possible) allowing these allocations to be members of of members of other allocations, aggregating allocations so there are much fewer.
.) Information regarding heap-allocs is relatively easy to obtain with "oprofile", look up the time spent in "malloc" and "free" and the related functions underneath them.
.) C makes it very easy to use an array of the values you want, rather than an indirection. For Lists, I don't think that is a useful idea. For hash tables, given that many entries are empty, I think it is a better trade-off to have your hash array as an indirection, though the buckets could be explicitly put together. And so on.
I often encountered dynamic allocation overheads are noticeable or even huge chunks of my runtime when I had to work with code that used them. Reducing the dynamic allocation really helps. We're not safe, we need to work towards it explicitly.
var structure = "dog" + "house"
print structure
does not need to allocate or move memory. That's how the script languages do it now, but should they? All the really need to do is print "dog" and then print "house", there's nothing that says those 8 letters need to be ever adjacent in memory.Since it's so darn easy (and sloppy) to call malloc() and memcpy() the script-engine writers are going to keep doing so until they're given a MacrocosmicGod-like environment that won't allow malloc() or memcpy() and so will have to come up with a clever workaround. maybe it will be something like linked-lists of immutable blocks of memory? Something like ropes? Something smarter than that?
Anyway, script-engine writers, please forget that malloc() and memcpy() exist, and see what you come up with. It'll be wonderful. (P.S. I was in the script-engine business for a dozen years and didn't solve the problem, but that's just because I'm not smart enough.)
Say a compiler author needs twice as much effort and cleverness to make programs in language X run fast than for language Y. That means that—all other things being equal—implementations of X will be twice as slow as Y for the same basic quality of implementation.
* http://en.wikipedia.org/wiki/Deforestation_%28computer_scien...
* http://www.haskell.org/haskellwiki/Short_cut_fusion
Deforestation is basically eliminating intermediate data structures, which is similar to what the "int(s.split("-", 1)[1])" versus "atoi(strchr(s, '-') + 1)" slides are about. If you consider strings as just lists of characters, then it's basically a deforestation problem: the goal is to eliminate all the intermediate lists of lists that are constructed. (It's something of a peculiar case though, because in order to transform into the C code you need to not only observe that indexing an rvalue via [1] and throwing the rest away means that the list doesn't have to be constructed at all, but you also need to allow strings to share underlying buffer space—the latter optimization isn't deforestation per se.)
I don't know if there's been much effort into deforestation optimizations for dynamic languages, but perhaps this is an area that compilers and research should be focusing on more.
On another minor note, I do think that the deck is a little too quick to dismiss garbage collection as an irrelevant problem. For most server apps I'm totally willing to believe that GC doesn't matter, but for interactive apps on the client (think touch-sensitive mobile apps and games) where you have to render each frame in under 16 ms, unpredictable latency starts to matter a lot.
As for GC, it would be nice to have good real time GCs in runtimes.
After decades of GC research, I think the conclusion is, "Yeah, that would be nice." Current state of the art gives us some very nice GCs that penalize either throughput or predictability. One of my favorite stories about GC is here:
http://samsaffron.com/archive/2011/10/28/in-managed-code-we-...
Deforestation is /more useful/ in strict languages, because allocation of temporary structures costs more. So fusion on strict arrays is better than on lazy streams.
You just can't do it unless you can freely reorder multiple loops, and to do that you need a proof there are no side effects. Haskell just makes that trivial.
You can also do it in stream-based or data-flow-based languages. Or in pretty much any DSL you decide to implement, if the semantics of the language itself is reasonable.
When incompetent programmers go away perhaps. C in and of itself is not the issue. And neither is scaring people away from it with horror stories.
I've run into young engineers recently who thing that pretty much any 'C style' system call is necessarily dangerous, because OMG the developer has to remember to pass in the length of the buffer they're passing in as well as the buffer itself. No, you just have to not be a frickin' idiot.
We need languages that are not designed to allow security exploits by accident, like Ada, Modula-2 or any other in the same school of thought.
Things that take time
•Hash table lookups
•Allocations
•Copying
Interestingly, that's exactly how you write fast C++ code. His point is that languages like Python lack good API's for preallocating memory.In higher-level programming languages, it's just a bit harder to control the number of reads and writes because you're working at several layers of abstraction above them, and are concerned with solving higher-level problems. Use the language that provides the appropriate level of abstraction for the problem you're trying to solve.
The problem is that we haven't yet implemented those abstraction layers in this smart way - for example, Haskell can implement 'fusion' of multiple string operations so that they are merged together and executed without intermediate copies; and the abstraction layer for that is exactly as high-level as the Python examples in original poster's slides. Sure, it's objectively hard to change core Python like that - but it theoretically can be done, so it should&will be done.
I think Alex Gaynor is correct and that basically what is wrong at the moment is that dynamic languages lack API's that have any sensitivity to performance concerns. There's always going to be a hard limit based on the nature of using a JIT vs. a static multi-pass compiler. There's always going to be a hard limit based on fundamental language choices (implementations of primitives, mutable vs. immutable strings, amount of overhead in object instantation, etc.) But we're nowhere near those limits right now.
For any language the compiler may know which variables won't ever be used - for example in pseudocode
b = a.lowercase()
c = b.replace("x","y")
d = a.lowercase.replace("x","y")
both 'b' and the intermediate result in 'd' are strings, but the compiler can flag these two 'throw-away' variables as mutable strings (while still maintaining the promise that all programmer-visible strings will be immutable); and you may have a special version of 'replace' standard function that does no-copy, in-place replacement in such cases. It means extra work in building API/stdlib, but brings better performance for the same programs.I'm almost never waiting on my python code. I'm waiting on network or disk or database or joe to check in his changes or etc.
I'm sure there are people who do wait. But that's why numpy, c extensions, all the pypy, psycho, and similar things exist.
Python and more broadly "scripting" languages are for speed of development. Something else can take on speed of execution faster than 90% of people need it to be.
EDIT: removed an additional comment about scientific computing that is now relevant as someone replied to it.
Python is slower than idiomatic C/C++ for solving comparable problems
And when io and especially network is involved, that is not true. Your efficient C code can't make up for time lost elsewhere in the system. No one is clamoring for curl to be rewritten in assembly.
It is also more likely to be possible to use efficient platform-specific APIs for things like zero-copy I/O in C than in a scripting language.
If you're trying to make a fast program you obviously avoid too much expensive I/O. You can't avoid some latency, but you can often avoid a lot, and cache a lot, and place the rest closer to the consumer.
When you say I/O dominated, are you sure you don't mean: interactive website? Because I think the real saving grace there is that's it's OK for websites to be very slow - from the perspective of a CPU. It's not that the I/O needs to take a lot of time, it's that you have 100ms (and that's before ajax and relatives, which can hide even more latency), and you just don't need a lot of optimization to get into that restriction. And once you have, the difference between 100ms and 1ns just doesn't matter nearly as much; so sure, then you start to accept very inefficient I/O setups even though much more efficient ones could be readily available.
We (the people using Python for Scientific Computing) like Python for the following reasons:
1. Numpy+Scipy+matplotlib+cvxopt is a very speedy environment. Its only real competitor for what it provides is MatLab. I have a colleague who bench marked Python vs. Matlab for our workload. Python is faster. (often because some of the algorithms used are newer than the equivalents in Matlab.)
2. It is a very productive environment. We do a lot of evolutionary changes and prototyping. Doing in this in C would slow us down in dev. time. This is academic work and mostly the code isn't important the analysis is.
3. We generally know where the "hot loops" are. Which is what we focus on for optimization. This generally involves doing math on paper. Then implementing it. If you turn loops in to matrix multiplications and use a good matrix library you get a great speed up.
I have a colleague who bench marked Python vs. Matlab for our workload. Python is faster
Is it also faster than C? From my limited experience, it seems that people sometimes spend a lot of time on concurrency when faster code would have been easier.
This generally involves doing math on paper. Then implementing it.
Ah, yes, math always wins. This reinforces your point #2.
So, is #2 that much of a win? Do scientific programs spend more time in "development" than "production"?
It can reach FORTRAN speeds with the right tools. With Numba (http://numba.pydata.org/), your pure Python code gets compiled down to optimized machine code at call time, if your arguments are Numpy arrays. With NumbaPro (https://store.continuum.io/cshop/numbapro), we automatically parallelize for multi-core CPUs, and we emit CUDA/PTX for GPUs, and automatically exploit the parallelism in your data and algorithm.
The reason "higher level languages" can be faster than lower-level ones is because the compiler has more information about data parallelism. Typically "low level languages" are lower in that their type primitives are smaller, and hence the algorithms around those have turned vectorizable arrays into opaque for loops over arbitrary loop variables.
I certainly agree with you that many people now reach for distributed and parallel while leaving a lot of single-core and single-node performance on the table, mostly by ignoring the realities of memory bandwidth on modern CPUs. However, that level of efficiency is well within the reach of the Scientific Python stack. (See this blog post for how we're building a persistence format that respects memory hierarchy: continuum.io/blog/blz-format)
And it's not just about static/dynamic language differences here: practically, JIT might even do better by specializing the algorithm for a particular dimensionality, whereas that's impractical in C++ since you don't know the dimensionality until runtime.
Now, sometimes you can reduce your algorithm to some large-scale eigenvalue decomposition or whatever, and then numpy or similar might provide reasonable performance. But it's not a very general solution because performance on small structures is terrible (and iterative simple updates are common in many algorithms). JITted code relying on some underlying native library (like numpy) could never extract reasonable performance from this type of code; it would be forced to make many, many function calls in the innermost loop.
It's not a fundamental issue, but I haven't seen a JIT do this particularly well, yet. All that inlining makes compiling slower, so to some extent the run-time nature of the JIT is an inherent limitation here.
re: concurrency: I have a script that boots hundreds of IPython workers on hundreds of cores. I then make a client object (in antoher IPython shell), and map my 1e8 parameter configurations on to the cores, all in under a minute. This is much faster than rewritng in C.
I even implemented a special case of the brain simulator we've developed in Python (http://thevirtualbrain.org/) in C w/ unaliased pointer arithmetic etc. It's 50% faster but took more than 50% longer to write; on the other hand the PyCUDA implementation is 80x faster, and didn't take 80x, maybe 10x. Also a win because PyCUDA takes care of the uglier details.
so #2 is a big win
At this point it's useful to know how long it takes to run, and how long to write. Is a run days long, months long, or years long? Or another way, is concurrency more expensive than a C re-programmer?
Also a win because PyCUDA takes care of the uglier details.
Is there not an analogous C++ library to take care of ugly details?
(I actually like python a lot, so there's a bit of devil's advocate going on. But, my longest running python programs take less than an hour.)
I believe Thrift (now shipped w/ CUDA SDK) makes things easier, but (since you know Python) nothing like NumPy exists in C++ and PyCUDA maps NumPy seamlessly into GPU computing, which is a big win.
Numba is completely open source. NumbaPro is not open source, but it is free for academic users.
No. In general, there isn't an analogous library at the more static-explicit languages (it doesn't matter much what library you choose). There are libs that people use when they have similar requisites, but they rarely are analogous.
However, I think the point the parent was trying to make is: Python is much slower than C and many other languages, however most of the time speed is unimportant. When it becomes important, there are many technologies to mitigate the problem in your "hot loops."
If speed is your primary concern don't use Python et. al. If it isn't your main concern go ahead it probably won't become an issue and if it does you probably will be able to get around it.
I was responding to @tptacek criticism of the parent not the deck. The deck is great and it mirrors the wisdom I have picked up from optimizing my own code over the years. I personally find it really frustrating not being able to easily pre-alloc lists in Python. I think that having better APIs would go a long way.
As the deck says:
"Line for line these languages are fast!"
"We need better no-copy/preallocate APIs"
"Take care in data structures"
l = [object()] * 100
Perhaps the difference is stack vs. heap? object[0].x = 1
print object[1].x
> 1
Edit: On second read, it looks like you're asking something other than what I thought you were asking. Yes, you could create a list of 100 items and then replace its elements, but that's not idiomatic.So my mind of course wandered in the direction of how to do that.
While I'd agree that for 99% of us we're not going to find python/ruby/php/javascript to be a bottleneck that can't be mitigated, that's no reason to say it's not worth trying to make them faster. If we can make changes to these languages that will make them more efficient, why not do it?
There is an implicit assumption there that most of the time in your program that could be saved is spent in a small number of hot spots. This will often be true, but unfortunately it is not necessarily so.
This is a particular problem in languages like Python, which are useful (among other things) for their support for rapid prototyping and their easily readable code. All of that is lost if you can’t perform local optimizations to reach an acceptable level of performance, leaving a ground-up rewrite in a faster language like C as the next most likely strategy.
The kinds of techniques mentioned in the linked slides could help to create a middle ground that would be very useful for performance-sensitive projects that currently find themselves between a rock and a hard place.
I'm not convinced by this "speed is unimportant".
Well, if you're writing shell scripts in Python/Ruby etc, OK, it might be. It might not even be important in web programming.
But for using any language as a generic programming language speed is very important.
The reason you cannot build full blown desktop apps like a browser or GUI libraries in Python? Lack of speed and memory control. And yes, you could offload the work to some extension. And that's a barrier.
Suddenly knowing Python is not enough. You got to also learn, e.g, C, and you have a segmented program structure, with some stuff here and some stuff there. Or you relegate Python to just the scripting layer for your program and do the real stuff in C/C++ (like Adobe Lightroom uses Lua).
I don't want to mitigate the problem in my "hot loops" with another language. I want to not have that problem in the first place. That would make me more productive.
One example: imagine NumPy in pure Python.
For one, it would be trivial to include in your project. Without building anything, it would work in all platforms.
Second, it would be far more accessible to people that don't know C/Fortran/et al to hack it.
Third, it would have been available for Python 3 or PyPy in a few months, not after several years.
Alright. Now, another way of achieving better speed is parallelism. But due to the bad support for it (GIL, lack of first class support) it's not easy to achieve this in CPython/MRI. Sure, you could use multiple processes but then you get all the issues of handling them and synchronising them with your own ad-hoc solution, and without first-class support from the language. Which is a barrier.
Yet another way to get more work done --for some kind of programs-- is evented code. So you have something like Node or Twisted. But Node doesn't have language support, so you get the "callback spaghetti" and Twister and co are external dependencies to the language, so they add another overhead.
Again, barriers.
People say "Speed doesn't matter" because they are trained by their language to only work on problems where speed doesn't matter. So it's more like a self-fulfilling prophecy.
Or course, if you constrain yourselves in "convenient" domains that your language supports fast enough, speed doesn't matter. But every step out of this and you are in need of clutches, from C extensions, to Cython, to Psyco, to Numpy, etc.
The real question is does it matter for a particular project.
If it is a desktop GUI. Does it matter if you write it in C++ and the time from button click to status update is 5usec or 1msec?
If you are receiving 10 messages per second, parsing out json and sending back a response or saving it to disk, does it always matter that it all happens in 10msec instead of 11msec. Maybe it does, I found it often doesn't.
With the right libraries, the hard parts of C probably turn out to be string processing with zero-copy string idioms, the requirement to lay out every data structure in fiddly detail, the requirement to track individual allocations, and the requirement to manage the memory lifecycle. What if performant Python only gives you an advantage on the last one of those?
- the syntax is less error-prone - ownership semantics are much clearer. You'll never segfault because you sent some memory into the wrong function - not as much detail is needed for memory layout, the JIT abstracts a lot of it away - there are high-level APIs handy - development and distribution are simpler with one less language - the barrier to optimising things is lower
The C syntax we're stuck with. But how big a deal is that syntax?
Segfaults are mitigated if you don't expose pointers, except to the extent that C programmers have to think about memory lifecycle (like I said, I think this is indisputably a win for high level languages). Look at NSMutableString for an example of a C-style idiom that removes whole classes of pointer operations.
I dispute that JITs abstract away details about storage; they may allow you to not think about those details for code that doesn't need to be performant, and they can help the language get out of the way when you need to care about the storage details, but the question I'm asking is limited to performant code. There is no question that nonperformant Python code is way easier to write than any kind of C code!
There are better APIs available in Python than are commonly available to C or even ObjC, but that's a solvable problem. Let's stipulate better APIs, to the limit of what the language would allow (in other words, it's totally fair to say that the design of C/C++ would prohibit certain kinds of easy APIs).
Development and deployment are easier in some cases for Python (for instance, building on OS X and deploying on Linux), but far easier for C in others (for instance, building code that will run in a kernel or as a plugin in the address space of another process).
I dispute that the barrier to optimization is lower in Python for obvious reasons: C programmers can optimize without working around the exposed wires and ductwork of the language runtime. C programmers generally have an easier time optimizing than Python programmers; that is probably the #1 reason any Python programmer ever writes C.
As a current Golang programmer I agree strongly with the commenter below that when you take this idea and apply it to a new language you wind up with something that looks a lot like Go, which does work great. But I'm not advocating Go here.
My personal favorite feature of Python is simply the syntatic sugar that allows me to write stuff like "for element in array" without having to remember that an index exists. These little things add up fast when you're trying to focus on the problem at hand!
The woes of pointers (segfaults and security vulnerabilities) cannot be addressed in a library without a performance penalty. If you want a nice error message instead of a segfault or random memory overwrite you will have to pass around type information at run time. You could however have a production version of the stdlib that did not pass around type information, but that would only solve the issue at development time: the security vulnerabilities in production would still be there.
There is also an argument to be made that many of the optimizations mentioned in the presentation can be done automatically by the compiler/JIT. For example Javascript JITs already optimize small hash tables used as objects, since every Javascript object is a hash table. Load forwarding followed by code motion can remove unnecessary intermediate allocations. And the square example should have been written as:
[i*i for in in xrange(n)]
This can allocate the result list of the right size at the start of the allocation.Meh, MEH. That's because you don't do anything involved with your Python code.
>I'm sure there are people who do wait. But that's why numpy, c extensions, all the pypy, psycho, and similar things exist.
That they HAVE to exist could also be considered a sad state of affairs though. With a faster language you would just use the language, not external extensions and tricks.
If you don't mind leaving Python's advantages on the table then use C in good health. Odds are that other people will be waiting on you to produce the C code, so let's hope you actually needed to do that.
https://gist.github.com/anonymous/5066486
gcc strange.c rk4.c; ./a.out
node strange.jsStraightforward numerical computations really isn't a good jit benchmark, because numerical computations are by far the easiest thing to JIT, and JITted perfs are going to be much closer to AOT than in the general case (unless the problem can be vectorized an the AOT compiler is vectorizing, I don't think JITs can usually vectorize)
$ cat squares.py
def squares_append(n):
sq = []
for i in xrange(n):
sq.append(i*i)
return sq
def squares_comprehension(n):
return [i*i for i in xrange(n)]
$ PYTHONPATH=. python -m timeit -s "from squares import squares_append" "squares_append(1000)"
10000 loops, best of 3: 148 usec per loop
$ PYTHONPATH=. python -m timeit -s "from squares import squares_comprehension" "squares_comprehension(1000)"
10000 loops, best of 3: 74.1 usec per loop
$ PYTHONPATH=. pypy -m timeit -s "from squares import squares_append" "squares_append(1000)"
10000 loops, best of 3: 46.9 usec per loop
$ PYTHONPATH=. pypy -m timeit -s "from squares import squares_comprehension" "squares_comprehension(1000)"
100000 loops, best of 3: 8.67 usec per loop
I'm curious to know how many allocations/copies a list comprehension saves in CPython/PyPy. However I wouldn't begin to know how to measure it. from numpy import arange
def squares_numpy(n):
a = arange(n)
return a * a
$ python -m timeit -s "from squares import squares_append" "squares_append(1000)"
10000 loops, best of 3: 130 usec per loop
$ python -m timeit -s "from squares import squares_comprehension" "squares_comprehension(1000)"
10000 loops, best of 3: 95.4 usec per loop
$ python -m timeit -s "from squares import squares_numpy" "squares_numpy(1000)"
100000 loops, best of 3: 5.31 usec per loopFeatures that come to mind include preallocated vectors (fixed-size or growable), non-consing versions of the standard list functions and the ability to bang on most any piece of data in place. There are fairly few situations in which a CL program can't come within a factor of 2 or 3 of the performance of C.
The Allegro CL 4.1 times of 3.9 seconds beat the f77 time of 4.8 [seconds];
Nice! setting on optimization for f77 brought its time down to 0.45 seconds.
Thus for this system, the [LISP] compiled code can have quite comparable speed to
that of the corresponding unoptimized Fortran in this case as well.
Oh really.On the other hand, given that C always had issues to beat even unoptimized Fortran, due to the optimization restrictions before C99, it is quite commendable that Lisp achieves such results.
The way the authors of that paper talk about unoptimized code in that paper gives the impression that they don't know what they're talking about. Your comments here begin to put you at risk of a similar appearance.
I don't have time to read all the comments now (thanks for all the interest though!). I just want to say I think when the video comes out it'll answer a lot of questions people are having.
I'd really like to use a lot more buffer()/memoryview() objects in Python. Unfortunately many APIs (e.g. sockets) won't work well with them (at least in Python 2.x. Not sure about 3.x).
So we ended up with tons of unnecessary allocation and copying all over the place. So sad.
Currently, if you want to really optimize a hot spot in, say, Python, your only real option is to write that part in C. Then you have all the additional complexity of gluing that into your Python program, along with portability concerns and a more complex build process. It would be so much easier if there were a way to sacrifice local simplicity and idiom for performance while still staying in the language.
And in the case of JS, I'm not sure you have much in the way of options at all for optimizing hot spots to reduce allocations. Maybe you could write it in C and compile it to JS via emscripten? I don't know if that would even help currently, but maybe if asm.js takes off. But once again, wouldn't you rather sacrifice a small amount of elegance for performance rather than switching languages?
So one way to improve performance is simply by having a good compiler. And GHC, at least, is a very good compiler.
Also, Haskell support for direct memory control is not that bad. In fact, in some ways, it's better than even Java--you can have your own unboxed data types which essentially act like structs, for example. This also means that you can have unboxed arrays of more than just primitive types.
Haskell also does some very clever things with both the heap and the stack, but I'm not familiar enough with its internals to comment. My understanding is that Haskell makes heap allocation much cheaper and has a GC optimized for handling lots of small allocations (as you would expect for a functional language).
Ultimately, the point is that the question is fairly nuanced and you won't be able to pin down a single language or implementation feature that uniquely determines performance.
map f (map g (map h someList))
is going to involve allocating exactly one list of the same size as someList, while a direct translation into Python map(f, map(g, map(h, someList)))
is going to involve the creation of several intermediate lists. map (f . g . h) someList
And in Python 3, map returns an iterator, not a list, so you aren't building the full list until you ask for it, and you never build intermediate lists in your example. You can do the same thing in Python 2 with the itertools.imap function. foldl g . scanl y . concatMap x . filter h . unfoldr k
Fuse that by hand.This is why we have optimizing compilers. They do what you could have done, only more often, and without mistakes.
So high-level optimizations like loop fusion do matter, but only for the 95% of the code which is not entirely performance critical. It's great for letting you write pretty (and therefore easier to write and more maintainable) code but not great for squeezing out all the possible performance in an inner loop.
Considering that many GHC optimizations are of this nature, it's really impressive that GHC is still good at speed in an absolute sense as well. So if you desperately need to wring out an extra bit of performance, you can still do it in Haskell instead of having to use C. Sure, the highly optimized Haskell will be relatively ugly, but it's still better than C and much easier to integrate with the rest of your codebase.
from itertools import imap
map(f, imap(g, imap(h, someList)))
I think Python 3's map built-in is a generator so you no longer have to use the itertools module.Unfortunately we don't have . or currying in Python so no pointfree python :(.
from itertools import ifilter
# ugly python function with a "Maybe dict" return type
def query(data, date):
"""Return the first dict where date is > x['date'] or None"""
is_greater = lambda x: date > x['date']
return next(
ifilter(
is_greater,
data
),
None
)The author didn't mention method dispatching. I think it's an issue for many languages. In Dart, they tried to optimize it by the specification by mostly eliminating the need to change methods at runtime. In Ruby I watched a video by one of the core Ruby developers and he said that in Ruby method dispatching can be very complicated requiring up to 20 steps to resolve them.
As important as getting the best performance out of programs is to get the programs created in the first place. That's why I'm against shying away from larger codebases. I'm in favor of OO programming exactly because I think getting things done comes first, even if that could complicate the implementation of the toolset. And OO is all about layers of abstractions that bring more performance costs with them.
That said, I absolutely abhor type annotations. They make code hideous and decrease the opportunities for experimentations. Instead of reading a + b = c algorithms, you may need to parse A a + B b = C c source code.
In Dart we have Optional Types. But the core developers are fond of type annotations, so most samples they post come with them. I take relief in being able to omit type annotations while experimenting, researching and ultimately prototyping. Although in a way I feel like a rebel in the community for this disregard. Thankfully there is this chance to share a community with them.
Reading the part that you don't like adding heuristics to help programs to go faster reminded of adding types to them even if they are mostly disregarded as in Dart.
Then again, not all "dynamic languages" are the same. Some are truly dynamic with eval and runtime method changes. Others, not so much. Sometimes the tradeoffs allow for other kinds of gains that could come into play like when deploying. So there is a lot more to it than just getting the algorithms correct.
int(s.split("-", 1)[1])
If the JIT knows that s is the builtin string type and the split() method has not been overridden [1], it can speed this up by using "pseudo-strings," where a pseudo-string is an index and length into another string. This would require only O(1) time and space.Garbage-collecting pseudo-strings would be an interesting exercise, but I'm sure it's a solvable problem [2] [3].
[1] If the preconditions for your optimization don't hold, you can always fall back to interpreting it. As noted by the speaker, this sort of logic is already a critical part of many JIT's including Pypy.
[2] The problem is actually GC'ing the parent. When the parent string is gc'ed, you have to compact the orphan strings to reclaim the remaining space; otherwise it'll be possible to write user code that uses a small finite amount of memory in CPython but has an unbounded memory leak in your compiler.
[3] You can avoid the trickiness in [2] if the parent string can be proven to outlive its children, which is the case in this example. You could probably optimize a lot of real-world code, and have an easier time implementing the compiler, if you only used pseudo-strings when they could be proven to be shorter-lived than the parent. As a bonus, this partial GC would build some infrastructure that could be recycled in a general implementation.
Dynamically-typed languages are often easier to program in, but require more copying (and memory allocation) as a result. Hash tables are heap-allocated and have to be garbage collected, but they're flexible - something you don't get with structs. Allocating and freeing memory has a cost, and that can add up quickly. Your primary line of optimization in most of these languages is "avoid the GC", which really boils down to "don't allocate more than you need to", which is sound advice in every language, scripting or otherwise.
Heap allocations are expensive even in non-GC languages.
You can get away from that with a sufficiently smart JIT turning a stable access to a known object into little more than a vtable dispatch. That's close to what V8 does with hidden classes on full cache hits (object map — its "hidden class" — + object "array" property both cached).
point.x
will generate the assembly: cmp [ebx,<hidden class offset>],<cached hidden class>
jne <inline cache miss>
mov eax,[ebx, <cached x offset>]
(where ebx is the previously loaded `point`)It may be possible to write a cffi backend for swig, but it's likely quite hard.
http://people.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf
In a copy collector the GC time is proportional to the amount of live memory -- garbage is free. In FP-style programs (lots of short-lived allocations) GC can be essentially free.
The other main source of slowdown is to do with boxing and type checks. Accessing all data in a naive implementation of a dynamically typed language involves at least one type check and typically one pointer indirection to unbox the data. This can kill performance relative to the unboxed and unchecked equivalent. Consider, e.g., floating point operations -- they run in 1 cycle. If you add type check (2-3 cycles perhaps) and pointer indirection (10 cycles if it's in the cache) you can see how massive slowdowns easily arise.
Modern JS VMs will remove most of this cost. I doubt Python and Ruby do.
Basically, I would say it really depends on the interactions between the program and the language implementation.
In the python case, pypy removes this cost whenever it can. The less you use dynamic features like duck typing, the faster your code gets.
I think the idea that GC is what slows down dynamic languages has to be the most prevalent misconception about language performance.
dynamic typing prevents type-based optimization
monkey patching prevents optimization
I think the most common complaint I hear about GC is not that it affects computational throughput, but that it affects _predictability_ of computational throughput. One maybe doesn't care in scientific computing, but game developers are always going on about how they can't use a GC language because a stall mid-frame will knock them over 16ms/frame or 33ms/frame, which for console certification is a project-killer.Perhaps I'm nitpicking, but with a function called `newlist_hint`, I struggle to see how anybody would adopt it. I had to go back to the slides maybe 3 times, and I still don't remember the name of this function... Those APIs must have the most obvious, logical and simple names.
Dictionary vs Object:
Lookups in both data structures is O(1), the difference being the hashing cost (and an additional memory lookup for heap) vs a single memory lookup on the stack (1 line of assembly).
Squares list:
> ... so every iteration through the list we have the potential need to size the list and copy all the data.
This is no different than stl::vector which has an amortized cost of O(1) for a push_back().
It's not going to be as fast as C, but I'd also argue for a generator version instead:
def squares(n):
return (i*i for i in xrange(n))
One of the main reasons people choose Python is for expressiveness and not manually managing memory, although pre-allocation does seem like a good idea.People start to create computer languages without carrying too much about the target processor opcodes (because in that time processor were just getting faster with time) and focus more on programmer convenience, and wild beasts like python and ruby were born..
C is fast because it was created with processor awareness in mind.. pretty simple...
these days kids are all about trying to create more and more crappy convenient sintax languages.. and they get worry when the languages dont scale? for what computer they design the language? from venus ?
nobody should be doing any serious software in python or ruby.. is such a waste of talent .. use it for education.. for fun.. or for the things they are best.. wich is not in the system/plumbing side of things
Javascript is insanely fast, with V8 and its ilk.
And I'm not talking about "toy benchmarks" either, I'm talking about envolved stuff written in plain JS (no C extensions), from the QT port to JS/Canvas, to the h264 encoder and such. Try doing those on Python and you'll see what you get. And of course all the toy benchmarks also agree.
Javascript with v8 is like a faster PyPy (with less performance deviation): 10 to 20 times faster than plain Python code.
Sure, you can extend Python with fast C code. But as the core languages are concerned, JS beats CPython hands down. (Oh, and you can also extend JS with fast C/C++ code if you need that. Node modules do it all the time).
def squares(n):
sq = []
for i in xrange(n):
sq.append(i*i)
return sq
A basically idiomatic version of the same in Python. No list
pre-allocation, so every iteration through the list we have the
potential to need to resize the list and copy all the data. That's
inefficient.
Is that true? I'd expect .append() to change a pointer or two, not "resize and copy" the list. Even an .insert() should just move pointers at the C-level... no need to "defrag" it. I guess the key word is potential.mixmastamyk's comment implies that (s)he believes that Python lists are, under the hood, linked lists. This is wrong. Python lists are ultimately backed by C arrays. This is why get() and set() are O(1), and insert() is O(n).
However, dynamically resizing an array to support append operations, if you're not stupid, takes amortized constant time. Individual operations may be O(n). Python implementers, happily, are not stupid.
However, insert() operations do require "defragmenting".
So, the primary question mixmastamyk asked about the cost of
def squares(n):
sq = []
for i in xrange(n):
sq.append(i*i)
return sq
is totally correct, but a lot of the sub-reasoning is wrong.I suppose using an array must improve performance in typical cases, while the resizing (a linked-list advantage) happens less often.
The only advantage I'm aware of for linked lists is insert and delete (but not append and pop), which are constant time in a linked list but O(n) in an array.
The thing is, though, that doing insert() on a linked-list usually requires a seek first. Which is O(n) on a linked-list (and may be O(n), O(logn), or O(1) on an array, depending on what you mean by "seek"). So in _practice_, usually inserts and deletes are O(n) in a linked-list as well. (But not always, because you could already have a pointer to the relevant thing, for example if you have multiple different pointed-based data structures with pointers into each other.)
Basically, as far as I can tell, linked-lists are almost useless, unless you're forced to write all your data structures from scratch and you have too little time to write yourself a proper library. The main exception is for really hairy shit where you have multiple linked-list views traversing the same data in different orders.
they're also such a flexible data structure that it is literally the ONLY data structure necessary to implement any of the LISPs.
Citation needed. I doubt this.
Certainly if I had only ten minutes to implement a stack or a queue, without access to anything more than stdlib.h (or equivalent), a linked list is easy to get right in a hurry, and only takes a few dozen lines. But the auto-resizing array is only a little harder, and has better performance for nearly every operation, as I explained in the previous post.
> they're also such a flexible data structure that it is literally the ONLY data structure necessary to implement any of the LISPs.
Of course. So what? That's not a reason to use them anywhere other than a school assignment that requires you to use them.
> Citation needed. I doubt this.
As do I. Since you're not going to reorder a stack or (in most cases) a queue, and since they contain fixed-size elements, what's the point of a linked list?
How can you take for granted the memory space past the end pointer is available?
But if you assume that "list" means "linked list", then you can just navigate to the correct part of the list, allocate enough space for one new cell, and stitch together a few pointers. Allocation and stitching is O(1). In general, navigating to part of the list is O(n), but if your list is a doubly-linked circular linked-list, or alternately if you keep a pointer to the end as a special case, then "navigate to the end of the list" becomes also O(1). I assume that all of this is what mixmastamyk was thinking Python was doing.
I was in fact taking it as almost a given that Python lists were backed by arrays under the hood.
http://www.reddit.com/r/programming/comments/19gv4c/why_pyth...
<quote>
While I agree with the first part ("excuses"), the "hard" things mentioned in the second part are a) not that hard and b) solved issues (just not in PyPy).
Hash tables: Both v8 and LuaJIT manage to specialize hash table lookups and bring them to similar performance as C structs (1). Interestingly, with very different approaches. So there's little reason NOT to use objects, dictionaries, tables, maps or whatever it's called in your favorite language.
(1) If you really, really care about the last 10% or direct interoperability with C, LuaJIT offers native C structs via its FFI. And PyPy has inherited the FFI design, so they should be able to get the same performance someday. I'm sure v8 has something to offer for that, too.
Allocations: LuaJIT has allocation sinking, which is able to eliminate the mentioned temporary allocations. Incidentally, the link shows how that's done for a x,y,z point class! And it works the same for ALL cases: arrays {1,2,3} (on top of a generic table), hash tables {x=1,y=2,z=3} or FFI C structs.
String handling: Same as above -- a buffer is just a temporary allocation and can be sunk, too. Provided the stores (copies) are eliminated first. The extracted parts can be forwarded to the integer conversion from the original string. Then all copies and references are dead and the allocation itself can be eliminated. LuaJIT will get all of that string handling extravaganza with the v2.1 branch -- parts of the new buffer handling are already in the git repo. I'm sure the v8 guys have something up their sleeves, too.
I/O read buffer: Same reasoning. The read creates a temporary buffer which is lazily interned to a string, ditto for the lstrip. The interning is sunk, the copies are sunk, the buffer is sunk (the innermost buffer is reused). This turns it into something very similar to the C code.
Pre-sizing aggregates: The size info can be backpropagated to the aggreagate creation from scalar evolution analysis. SCEV is already in LuaJIT (for ABC elimination). I ditched the experimental backprop algorithm for 2.0, since I had to get the release out. Will be resurrected in 2.1.
Missing APIs: All of the above examples show you don't really need to define new APIs to get the desired performance. Yes, there's a case for when you need low-level data structures -- and that's why higher-level languages should have a good FFI. I don't think you need to burden the language itself with these issues.
Heuristics: Well, that's what those compiler textbooks don't tell you: VMs and compilers are 90% heuristics. Better deal with it rather than fight it.
tl;dr: The reason why X is slow, is because X's implementation is slow, unoptimized or untuned. Language design just influences how hard it is to make up for it. There are no excuses.
</quote>
Also interesting is his research on allocation sinking:
Leads me to wonder - has anyone done a study of any large-scale program to check where the slow spots are? It's not that I don't trust the speaker, he makes excellent points and is obviously a great memeber of the community.
But it would be very interesting if he were able to say: "Using PyPy's secret 'hint' API, only in drop-dead obvious places, improved performance by a factor of 5".
atoi(strchr(s, '-') + 1)
What does this do? Finds the first instance of a -, and converts the remainder of a string to an int. 0 allocations, 0 copies. Doing this with 0 copies is pretty much impossible in Python, and probably in ruby and Javascript too. </quote>The copying could be avoided in non-idiomatic Python:
int(buffer(s, s.find("-") + 1)) +s.substr(s.indexOf('-') + 1)I've been interested in this talk since I saw it announced on Twitter, but prefer to watch/listen rather than go through these slides.
I, for one, am very grateful to speakers that make the extra effort required to share with a larger group what they have already shared (or are about to share) with a smaller group.
Remember: the author does not owe you anything.
As an example, just consider the enormous amount of effort that has gone into the JVM, and Java is still generally considered to be ~2x slower that C.
Some language features are easy to optimize away for some common cases (e.g. array bounds checks, so that your program throws an exception instead of segfaulting), but you can't optimize these if, for example, you're iterating over data you read from a file using indices you also read from that file.
Any good articles that summarize this info?
However, opening the video and seeking to a random point, I must say that the phrase "Ruby release policy: Ruby level compatibility" isn't doing any Japanese speaker a favour.
It's not like chadcf said "This talk is bullshit, that guy doesn't even speak English". chadcf said "I experienced this difficulty, I wish that the following thing existed, can anyone help me?" Maybe he could afford to have done
s/speak English/speak more fluent English/"Topaz is a high performance implementation of the Ruby programming language, written in Python on top of RPython (the toolchain that powers PyPy)."
4 years later and they still discuss it, heh.
Question: The author claims to be a compiler author. After some digging I haven't found any information on what compilers he has written or are part of writing. Could someone point me to the compiler(s) Alex is involved with? Thanks.
In what ways did that detract from his overall point?
He works on the JIT, among other things.