3 ms·
It really is remarkable that Python's numerical computing libraries have such poor performance. When doing chains of elementwise operations on large arrays (suc
by bobbylarrybobby 7y ago
It really is remarkable that Python's numerical computing libraries have such poor performance. When doing chains of elementwise operations on large arrays (such as toy example `elementwise cos(sqrt(sin(pow(array, 2))))`), Julia appears to outperform Python by a factor 2! Numpy cannot avoid computing each intermediate array, which means it has to allocate a ton of wasteful memory. Meanwhile Julia does the smart thing and coalesces all operations into one and applies that single operation elementwise, allocating only a single new array.
Pandas does not also defer computations, which means computing Boolean functions that include the same data multiple times must make multiple passes over said data. Absurd.
- deleted 7y ago[deleted]
- quotemstr 7y ago> Julia appears to outperform Python by a factor 2 Now I have some more reading to do. Thanks.
- timClicks 7y agoJulia is very, very good. I say this as a person who has invested about a decade in the Python data science ecosystem. Numpy will probably always be copy-heavy, whereas Julia's JIT can optimize as aggressively as the impressive type system allows.
- nmca 7y agoJAX supports this; as do other performance-targeting numerical libraries. There are hoops of course.
- a_t48 7y agonumpy appears to have optional arguments for the storage location of outputs: https://docs.scipy.org/doc/numpy/reference/generated/numpy.cos.html https://docs.scipy.org/doc/numpy/reference/generated/numpy.c... - Could you elaborate a little further? The syntax might not be as nice as another language or framework, but it's not "unavoidable". Disclaimer: have never used numpy, have used python fairly extensively.
- bobbylarrybobby 7y agoYou are right about the `out` argument, I'd forgotten about that. But even avoiding the wasteful memory allocations, numpy still is about 60% slower than Julia, as it makes multiple passes over the input data. (If there's a way to get numpy to just make a single pass over the data and remain performant, I'd love to know.)
- a_t48 7y agoThe first thing I'd reach for is to refactor into a list comprehension. Looks like this is the proper way to do it: for x in np.nditer(a, op_flags = ['readwrite']): x[...] = cos(sqrt(sin(pow(x, 2)))) That's some knarly syntax though. I've never seen that ellipsis operator? Edit: just read about `Ellipsis`. I'm a fan, even if it's sort of nonstandard across libraries. Those readwrite flags are a travesty though, but at least you can paper over it with a helper function. Something like: def np_apply(a, f): for x in np.nditer(a, op_flags = ['readwrite']): x[...] = f(x) np_apply(x, lambda x: return cos(sqrt(sin(pow(x, 2)))))) or def np_apply(a, *argv): for x in np.nditer(a, op_flags = ['readwrite']): for f in argv: x = f(x) x[...] = x np_apply(lambda x: return pow(x, 2), sin, sqrt, cos) Edit3: There's a way to turn this into "pythonic" list comprehension code, but it would probably only make it look prettier rather than more performant.
- quotemstr 7y agoYes, you can use the out arguments, but doing so 1) complicates your code, and 2) isn't necessarily a win. You have to do the same number of memory write bus cycles whether you write to a specified output array or to some new array.
- a_t48 7y agoI agree with point (1), wholeheartedly - by default the syntax is ugly unless you wrap it in a utility. (2) isn't true as far as I can tell - you can keep reusing the same output buffer, or double buffer if the output can't be the same as the input. Either way see https://news.ycombinator.com/item?id=22786958 https://news.ycombinator.com/item?id=22786958 where I figured out the syntax to do it in place, which shouldn't result in any allocations at all and should solve both your concerns.