This is a little reductive. The speed comparison was between the highly parallel and optimized CPU version vs GPU. The GPU’s advertised FLOPS was only 4x the CPU setup’s. That the performance is so similar isn’t all that surprising and could come down to low level optimization details. There are likely some areas in Julia’s GPU stack that could be made better, but it’s certainly already pretty performant.
It’s also comparing against Fortran. Their Python comparison is 100x+ slower. That a high level language even comes close is impressive.
The choice of 1 based array indexing is due to Julia having it's roots in mathematics - which is a common choice for mathematics based programming languages (e.g. Fortran, Julia, MATLAB/GNU Octave, Mathematica, R and Wolfram). The reason being is that matrix notation starts at 1 and not 0.
From personal experience, I wouldn't let something as simple as starting index getting in the way of programming for a language as you'll soon start automatically switching to thinking in 1 based and 0 based indexing depending on the programming language.
Compiled high level languages have always been pretty fast in general, for decades, before 20 years ago it started to be trendy to use interpreted scripting languages to do their work.
Anyone used to Modula-2, Object Pascal, QuickBasic, Clipper, AMOS, Lisp, Scheme, Dylan, Smalltalk, SELF, Oberon, VB, Delphi, C++, SML, Miranda, Haskell, Prolog from 1980-1990's, isn't going to be surprised about speedups over Python.
100x relative to NumPy is still quite a lot, depending on how much of their code manages to stay in the C extension. If it was pure Python the delta would likely be much, much larger.
Julia is definitely built on the shoulders of giants. I don’t have experience with most of those languages, especially in high performance Lisp or Haskell. Calling C++ high level is definitely not in the same vein as Python or Julia. My understanding is that Haskell can run into all sorts of performance gotchas because lazy evaluation can be unpredictable and recursion may not always easily unroll into optimizable loops. I think the combination of features, especially borrowed from Lisp and C++, really come together as something very productive for writing fast, very high level code.
C++ is definitely high level, in the same vein as Python and Julia, the only thing missing is reflection (if we ignore C++/CLI, C++ Builder and custom libraries), which with some luck might still make the cut to C++26.
Just because some people insist in using it like C, doesn't make it less higher level in available features and tooling.
Regardless of how easy it is to optimize Haskell code, even stupid Haskell code beats an interpreted language like Python. And C bindings are not Python, regardless of how the Python community likes to think otherwise.
If anything, I look forward to Julia and others to put pressure in the community to take PyPy and other JIT attempts more seriously.
Or maybe it doesn't matter and in a couple of years everyone has moved into mojo, if it is successful.
I disagree that C++ is high level in the same way. The core of the language’s memory management relying at some level on bare pointers adds complexity that can never be removed. The syntax itself is not simple and rarely has the magic of 1 line of code expanding into an incredibly complex yet simply understandable computation. That functionality cannot even be retrofitted on because its metaprogramming system sucks.
PyPy will still never be as fast as C++ because the semantics of Python itself make it hard to compile into efficient code. Funny enough, Haskell has that similar problem that it’s difficult to compile well. That’s the actual magic of Julia: its semantics are designed so it can be compiled to fast code while still being ergonomic like Python. Mojo may end up being good, but in the examples I’ve seen the real “fast” part of the language doesn’t look all that much like Python anyway.
As mentioned, some people insist in using C++ as if it was C.
Smalltalk, Common Lisp, JavaScript, SELF, Dylan JIT implementations, for highly dynamic languages, beg to differ.
Some of those, anything in the process space can change at any time, either by using language features, or stopping into the debugger, changing the world as much as we like, hit continue or retry as if it was still the original code.
The Python dynamism as a problem is an excuse from a language culture that sees writing C extensions as being Python code, thus not willing to spend the resources to make it happen.
Thankfully even Ruby is leaving Python behind, joining the list of highly dynamic languages with a JIT in the baggage.
No, it’s not just that people write C++ like it’s C. Another example: there is no simple syntax for broadcasting a function over a collection. Anonymous functions have syntax that is all parentheses and brackets. They’re this way because adding them to the language itself would be breaking. You can’t easily improve the syntax of these things yourself for your subset of code because the meta programming systems C++ has just aren’t great compared to Lisp and Lispy languages like Julia. Further, even if you do add a bunch of cool stuff yourself like a GC or some other syntax magic, it rarely composes well with other libraries. Core to this is that inheritance never really panned out as hoped, and templates are cumbersome. C++ has come a long way, but all of that is due to the overall design of the language itself, not just how it is used.
Can you write something as fast as BLAS in JavaScript? As far as I know, the answer is no.
Python’s dynamism makes it so that a compiler often times can’t do anything to infer what some function calls will do. No investment will completely fix it without breaking changes to the language itself.
> I suggest learning about C++ ranges and CUDA algorithms.
I’m familiar.
> Parenthesis and brackets have nothing to do with how high level a language happens to be.
I would argue readability and expressiveness do, though. When someone is talking about high level languages in the context of Python, they don’t mean in terms of instructions, registers, and interrupts.
> BLAS in JavaScript would certainly be faster than Python, this is what is being discussed here.
No, it explicitly is not. You originally replied to my comment that it was impressive how Julia in this case was competitive with Fortran and much faster than Python.
You agree that no JavaScript JIT is going to make it as fast as C/C++/Fortran, right?
> Again the dynamism excuse.
Julia is a dynamic language, so I’m not sure what excuse you think I’m making. The reality is that the languages you cited generally are not competitive with C/C++/Fortran. The reality is that none of the languages you cited are even being considered for those applications. The reality is that languages that have had massive investment in a JIT like JavaScript are still relatively slow.
Julia seems to be different than the other dynamic languages you cited in this regard. Why do you think the relatively small investments in Julia have been able to achieve this while others, often with more resources, have failed? I think it probably has something to do with the language design.
Ok, so a not very representative tl;dr was provided so adding mine here.
1. Serial CPU performance of Julia-unoptimized was around 12 times that of Python/Numpy.
2. Serial CPU performance of Julia optimized gave another 10-12x improvement leading to total 140x improvement over Python/Numpy.
(good pointers on speeding up the Python code beyond Numpy listed but not tried or benchmarked)
3. Even though the GPU was rated for a listed performance of 9.7TFlops (A100) the code performed around the same as the optimized Serial CPU which has a listed performance of 2.5TFlops over 64 cores. Surprising result, there are some pointers to why that might be.
4. When comparing Julia-MPI with Fortran-MPI, at lower processor counts both match for total time, at middle processor counts (16-128) Fortran takes more time than Julia, at higher processor counts Julia becomes slower than Fortran. However the scaling down of Julia is driven more by a less efficient implementation of the communication time with the higher processor setup. Computation time scales well for both Julia and Fortran but the communication time takes over in the Julia case.
Some nice optimization tips are provided to speed up Julia code. No plans to use Julia yet but it is good for prototyping and Fortran is good enough for now.
Yes, definitely very impressive results. Especially considering how much of an improvement over the Python/Numpy code they could achieve. If you are writing something from scratch, very good reason to work in Julia.
Uhm, the poor performance of the Python/Numpy code indicates that there's something amiss; perhaps an apple/orange comparison. Don't get me wrong, Python is slow as molasses, but for this kind of applications, almost all time spent is (ought to be) spent in the Numpy code, which is mostly C/C++. Other systems might perform better, but I don't expect more than a factor of two (which itself would be huge in the HPC domain).
It looks like lots of quick calls to numpy. The python ffi is really slow and is eating all the gains. A large matmul works fine, but not thousands of small ones. Hpy numpy port might fix this.
Is the communication issue something intrinsic to Julia, or something in the way the author of the code implemented it? (I guess MPI must be expecting arrays stored in exactly the same format as Fortran has them, MPI grew up in Fortran-and-C land; does Julia have to do some extra marshaling or something?)
The authors seem to using the MPI package from Julia [1] so not something they did on their own. I didn't see any discussion on why MPI.jl is scales differently than Fortran-MPI. Something for the MPI.jl developers to investigate.
For sure another case for the "you doing it wrong" team at Nvidia.
But the decision to add Julia/CUDA.jl here was not the best idea on a comparison with really interesting insights about Fortran vs. Julia. The CUDA part of the code is nearly a one-to-one copy of the MPI code. Especially one sentence[1] (there are some similar, comparable blocks) explains why the code not performs very well.
For me the beginner's mistake is: on a CPU any 'if' will cost you a clock cycle (branch prediction not considered), on a GPU it costs you the sync. In best case the syncs will come back over many loops, more often some cores will go out of sync more and more, eventually all other cores will have to wait for the last. Otherwise memory accesses will come blocking and that would cost all cores thousands of cycles.
The key elements that I consistently see missing when comparing languages is the cost/effort of maintenance and how easy is to write fast/slow code.
The former boils down to, there's little point saving time in the writing of new code when new versions of the language hinder the use of old codes. Julia, being a very young language, simply does not have the legacy of backwards compatibility that Fortran has and as a result libraries written today are unlikely to work in 10 years time. Partly because of Julia's age and partly because backwards compatibility is not valued as much as it is in Fortran. The GNU Fortran compiler can still compile code written nearly 50 years ago (https://gcc.gnu.org/fortran/).
The second point is that benchmarks like this seem to focus on tuned implementations, which is really important, but another important aspect is how easy is it write fast code. With Fortran, it is very easy to write very fast code as a lot is abstracted away from the programmer so they can focus on FORmula TRANslation. My experience with Julia has not been the same and thus for people don't have the time to perform such optimisations, Fortran may still be the better choice.
I do look forward to watching the development of Julia but for now I'll stick with using Fortran as the high performance numerical language in my tech stack.
I'll finish with something I've read but I can't remember from where:
Over the years there have been many examples of "X with replace Fortran" and the only thing that ends up getting replaced is X.
28 comments
[ 3.1 ms ] story [ 70.4 ms ] thread- Julia nice for prototyping
- but zero speed up from using GPU
- sometimes faster sometimes slower on CPU
- no plans to use at the moment
It’s also comparing against Fortran. Their Python comparison is 100x+ slower. That a high level language even comes close is impressive.
For a comparison on array indexing in different programming languages see here: https://en.wikipedia.org/wiki/Comparison_of_programming_lang...
From personal experience, I wouldn't let something as simple as starting index getting in the way of programming for a language as you'll soon start automatically switching to thinking in 1 based and 0 based indexing depending on the programming language.
Compiled high level languages have always been pretty fast in general, for decades, before 20 years ago it started to be trendy to use interpreted scripting languages to do their work.
Anyone used to Modula-2, Object Pascal, QuickBasic, Clipper, AMOS, Lisp, Scheme, Dylan, Smalltalk, SELF, Oberon, VB, Delphi, C++, SML, Miranda, Haskell, Prolog from 1980-1990's, isn't going to be surprised about speedups over Python.
Julia is definitely built on the shoulders of giants. I don’t have experience with most of those languages, especially in high performance Lisp or Haskell. Calling C++ high level is definitely not in the same vein as Python or Julia. My understanding is that Haskell can run into all sorts of performance gotchas because lazy evaluation can be unpredictable and recursion may not always easily unroll into optimizable loops. I think the combination of features, especially borrowed from Lisp and C++, really come together as something very productive for writing fast, very high level code.
Just because some people insist in using it like C, doesn't make it less higher level in available features and tooling.
Regardless of how easy it is to optimize Haskell code, even stupid Haskell code beats an interpreted language like Python. And C bindings are not Python, regardless of how the Python community likes to think otherwise.
If anything, I look forward to Julia and others to put pressure in the community to take PyPy and other JIT attempts more seriously.
Or maybe it doesn't matter and in a couple of years everyone has moved into mojo, if it is successful.
PyPy will still never be as fast as C++ because the semantics of Python itself make it hard to compile into efficient code. Funny enough, Haskell has that similar problem that it’s difficult to compile well. That’s the actual magic of Julia: its semantics are designed so it can be compiled to fast code while still being ergonomic like Python. Mojo may end up being good, but in the examples I’ve seen the real “fast” part of the language doesn’t look all that much like Python anyway.
Smalltalk, Common Lisp, JavaScript, SELF, Dylan JIT implementations, for highly dynamic languages, beg to differ.
Some of those, anything in the process space can change at any time, either by using language features, or stopping into the debugger, changing the world as much as we like, hit continue or retry as if it was still the original code.
The Python dynamism as a problem is an excuse from a language culture that sees writing C extensions as being Python code, thus not willing to spend the resources to make it happen.
Thankfully even Ruby is leaving Python behind, joining the list of highly dynamic languages with a JIT in the baggage.
Can you write something as fast as BLAS in JavaScript? As far as I know, the answer is no.
Python’s dynamism makes it so that a compiler often times can’t do anything to infer what some function calls will do. No investment will completely fix it without breaking changes to the language itself.
Parenthesis and brackets have nothing to do with how high level a language happens to be.
BLAS in JavaScript would certainly be faster than Python, this is what is being discussed here.
Again the dynamism excuse.
You can change ANYTHING in Smalltalk, SELF, Common Lisp, and their JITs make up for it.
Might not be C++ fast by today standards, as those languages don't see much investment nowadays, they certainly beat Python no matter what.
I’m familiar.
> Parenthesis and brackets have nothing to do with how high level a language happens to be.
I would argue readability and expressiveness do, though. When someone is talking about high level languages in the context of Python, they don’t mean in terms of instructions, registers, and interrupts.
> BLAS in JavaScript would certainly be faster than Python, this is what is being discussed here.
No, it explicitly is not. You originally replied to my comment that it was impressive how Julia in this case was competitive with Fortran and much faster than Python.
You agree that no JavaScript JIT is going to make it as fast as C/C++/Fortran, right?
> Again the dynamism excuse.
Julia is a dynamic language, so I’m not sure what excuse you think I’m making. The reality is that the languages you cited generally are not competitive with C/C++/Fortran. The reality is that none of the languages you cited are even being considered for those applications. The reality is that languages that have had massive investment in a JIT like JavaScript are still relatively slow.
Julia seems to be different than the other dynamic languages you cited in this regard. Why do you think the relatively small investments in Julia have been able to achieve this while others, often with more resources, have failed? I think it probably has something to do with the language design.
I am not going to bother with Julia vs C++ evangelism.
By the way, there are 1980's papers about Common Lisp versus Fortran floating point performance, that you can certainly easily find out.
1. Serial CPU performance of Julia-unoptimized was around 12 times that of Python/Numpy.
2. Serial CPU performance of Julia optimized gave another 10-12x improvement leading to total 140x improvement over Python/Numpy.
(good pointers on speeding up the Python code beyond Numpy listed but not tried or benchmarked)
3. Even though the GPU was rated for a listed performance of 9.7TFlops (A100) the code performed around the same as the optimized Serial CPU which has a listed performance of 2.5TFlops over 64 cores. Surprising result, there are some pointers to why that might be.
4. When comparing Julia-MPI with Fortran-MPI, at lower processor counts both match for total time, at middle processor counts (16-128) Fortran takes more time than Julia, at higher processor counts Julia becomes slower than Fortran. However the scaling down of Julia is driven more by a less efficient implementation of the communication time with the higher processor setup. Computation time scales well for both Julia and Fortran but the communication time takes over in the Julia case.
Some nice optimization tips are provided to speed up Julia code. No plans to use Julia yet but it is good for prototyping and Fortran is good enough for now.
Worth having a closer look methinks.
[1] https://github.com/robertstrauss/MPAS_Ocean_Julia/blob/main/...
Damn, it would have been a nice benchmark if they did do a comparison against numba/cython.
But the decision to add Julia/CUDA.jl here was not the best idea on a comparison with really interesting insights about Fortran vs. Julia. The CUDA part of the code is nearly a one-to-one copy of the MPI code. Especially one sentence[1] (there are some similar, comparable blocks) explains why the code not performs very well.
[1] https://github.com/robertstrauss/MPAS_Ocean_Julia/blob/main/...
For me the beginner's mistake is: on a CPU any 'if' will cost you a clock cycle (branch prediction not considered), on a GPU it costs you the sync. In best case the syncs will come back over many loops, more often some cores will go out of sync more and more, eventually all other cores will have to wait for the last. Otherwise memory accesses will come blocking and that would cost all cores thousands of cycles.
The former boils down to, there's little point saving time in the writing of new code when new versions of the language hinder the use of old codes. Julia, being a very young language, simply does not have the legacy of backwards compatibility that Fortran has and as a result libraries written today are unlikely to work in 10 years time. Partly because of Julia's age and partly because backwards compatibility is not valued as much as it is in Fortran. The GNU Fortran compiler can still compile code written nearly 50 years ago (https://gcc.gnu.org/fortran/).
The second point is that benchmarks like this seem to focus on tuned implementations, which is really important, but another important aspect is how easy is it write fast code. With Fortran, it is very easy to write very fast code as a lot is abstracted away from the programmer so they can focus on FORmula TRANslation. My experience with Julia has not been the same and thus for people don't have the time to perform such optimisations, Fortran may still be the better choice.
I do look forward to watching the development of Julia but for now I'll stick with using Fortran as the high performance numerical language in my tech stack.
I'll finish with something I've read but I can't remember from where:
Over the years there have been many examples of "X with replace Fortran" and the only thing that ends up getting replaced is X.