Julia: Faster than Fortran, cleaner than Numpy
matecdev.com
matecdev.com
n = len(a)
M = np.exp(1j*k*(np.tile(a,(n,1))**2 + np.tile(a.reshape(n,1),(1,n))**2))
This has a common issue in functional programming and there's an easy trick to fix it. Change the big one liner by naming things: n = len(a)
A = np.tile(a,(n,1))
A_T = np.tile(a.reshape(n,1),(1,n))
M = np.exp(1j*k*(A**2 + A_T**2))
Much easier to read now, and it even exposes an error (the sqrt is missing!). Before we fix that, let's make the obvious simplification of using the .T for transpose: n = len(a)
A = np.tile(a,(n,1))
M = np.exp(1j*k*(A**2 + A.T**2))
Then let's use numpy's broadcasting to be more idiomatic: A = a[:, np.newaxis]
M = np.exp(1j*k*(A**2 + A.T**2))
Now let's fix the bug: A = a[:, np.newaxis]
M = np.exp(1j*k*np.sqrt(A**2 + A.T**2))
or even A = a[:, np.newaxis]
root_sum_squares = np.sqrt(A**2 + A.T**2)
M = np.exp(1j*k*root_sum_squares)
I think the last two of these are fairer comparisons.--------
And to pitch the hype train for jax: if you want to fuse the loops and/or get gpu/tpu for free, just swap np for jnp:
A = a[:, jnp.newaxis]
M = jnp.exp(1j*k*jnp.sqrt(A**2 + A.T**2))https://discourse.julialang.org/t/i-just-decided-to-migrate-...
and then even further here:
https://discourse.julialang.org/t/i-just-decided-to-migrate-...
I'd actually be very interested if there's anything other than handwritten assembly that's faster than the second one I posted.
______________
Regarding your more readable version of the Numpy syntax though, I think you have to admit it's still not as readable as
for i in 1:N
A[i,j] = exp((100+im)*im*sqrt(a[i]^2 + a[j]^2))
end
right?I'll also note that the author of the post didn't come from the Julia community. He was a heavy user of Python and Fortran, calling Fortran from Python to speed up performance hotspots. He wrote this blogpost as his first introduction to Julia.
So I think if it does cast numpy in a poor light, it was not done so intentionally to make it look bad.
Regarding the readability, I suppose it's a matter of taste at this point, but if I swapped `import numpy as np` for `from numpy import newaxis, exp, sqrt`, then IMO, the numpy is more readable:
A = a[newaxis]
M = exp(1j * k * sqrt(A**2 + A.T**2))
But then my tastes also run towards preferring the handwritten definitions to be in terms of vectors and transposes instead of elements and indices, at least until you get to many indexed tensors.Side note: any idea why the python and fortran are exp(i k... while the julia is exp((100 +i) i...)? Is it something I overlooked?
Looking forward to it! These microbenchmarks are very fun to explore.
> Side note: any idea why the python and fortran are exp(i k... while the julia is exp((100 +i) i...)? Is it something I overlooked?
Oh, that is a partially applied edit I guess. When the author posted on the julia forum, people pointed out that since the exponent was pure imaginary, it could be speeded up even more with cis(...) instead of exp(im * ...) but then the author claimed that exp((100 + im) * ...) was more representative of his actual workflow and I guess changed the julia version in his blogpost but not the Python or Fortran versions.
https://colab.research.google.com/drive/1ABrZJlm8pwB6_Sd6ayO...
On my macbook, using XLA's jit in python gave about a 12-15x speedup on CPU over OP's solution, which was pretty cool, but I'm too lazy to figure out how to install and benchmark Julia on my machine. Applying a 12-15x speedup would at least beat the Julia MT solution in OP, and you've got to admit `exp(CONST * sqrt(A**2 + A.T**2))` is a pretty clean way to do it.
Then I ran on whatever GPU colab decided to give me (a P100), and for just adding a decorator, it's a 1000x-1900x speedup (better as n goes up). Hence my current honeymoon period with jax. I love the speed vs readability tradeoff.
Assuming you're eager to try once you've found out:
You should be able to simply download and unpack a binary from: https://julialang.org/downloads/ I'd strongly recommend going with the current stable release (1.6.1).
To start the Julia REPL:
bin/julia
To install packages: using Pkg
Pkg.add("BenchmarkTools")
Pkg.add("LoopVectorization")
then you can copy/paste code from eigenspaces link to discourse to run Julia benchmarks.
E.g., you can copy/paste from https://discourse.julialang.org/t/i-just-decided-to-migrate-...
if you define using LoopVectorization
const var"@tvectorize" = var"@tturbo"
(because `@tvectorize` has since been renamed on the latest releases)Would be interesting to run your optimized code against the Julia optimized code that was linked to in this thread. Or run a Julia GPU benchmark as well.
As for loops versus vectorization - I personally am used to vectorized code as it has always been a requirement. Sometimes it makes sense to use (e.g. Matrix Algebra stuff).
On the other hand, I have also been in many situations where I could not vectorize my code. More code than I'm happy to admit includes list or dict comprehensions or even loops. Sure, not all performance critical. Nevertheless, the prospect of performance no matter what is pretty exciting.
Finally, threading and multiprocessing in python is a headache compared to Julia imo. Sometimes I just miss the good ol' "parfor" from Matlab.
All in all, Julia is a really appealing value proposition to people doing numeric computing.
Curiously, as much as I love the Julia language, I cannot stand multiple dispatch. I find it extremely confusing, ugly, and downright disturbing. Why would you ever want two different functions with the same name? And if this is good, why stop here? Why not two different variables with the same name but different types? Depending on the context where they are used, the language could pick one or the other.
How would you feel about having to write `sqrt_int32(x)`, `sqrt_int64(x)`, `sqrt_float64(x)`, `sqrt_rational_complex_float16(x)`, etc, etc, etc, etc, instead of just `sqrt`, and let dispatch or overloading take care of the different implementations?
Yeah. And I find OOP horrific.
> How would you feel about having to write `sqrt_int32(x)`, `sqrt_int64(x)`, `sqrt_float64(x)`,
What kind of savage wants the square root of an integer? I'd prefer if the language only had a single number type (maybe configurable at once by an external option) and a single sqrt function.
But if you talk about the general problem of naming functions differently according to their types, I feel that it's perfectly OK. A tiny price that I'm eager to pay for the large benefit of being able to identify which function is called just by looking at its name (and not at the---possibly yet undetermined---types of its arguments).
Your function may be written for numeric, and you use your favorite kind exclusively.
Now person B from this thread comes along, using both ints and floats. Well guess what, your package still works seamlessly. And where it doesn't, it must be due to a particularity that you yourself don't care about. In any case, the function can then be extended without namespace issues. In other languages, one would have to rewrite your package since you are using an obscure numeric type which others don't agree with.
Consider for example how people have used solvers for differential equations with entirely new datatypes, simply because the compatibility comes for free.
I think it's really neat!
I think it's great!
If you meant a numeric single type class, like haskell's Num, I think it's a great option. If you mean a single numeric type, like javascript, it unfortunately leads to a bunch of issues. Integers and floats really are both necessary very frequently. For example floats to represent something like speed, and ints to represent your bank balance. And you need different sizes of them (like float32 vs float64) are still necessary in a ton of applications, so you'll need them eventually if you want the language/numerics library to be truly general purpose.
It's also a huge convenience to be able to apply e.g. `+` to arrays as well as numbers and other polymorphism niceties.
It comes up moderately frequently (e.g., in prime sieves). It's usually defined as the largest integer X whose square X^2 is no greater than N. Search any sufficiently large codebase (postgres or something) and you'll probably find one or more functions called something like "isqrt".
My favorite implementation just takes Newton's method and blindly applies it to integers. It happens to converge for at least one starting value.
> But if you talk about the general problem of naming functions differently according to their types, I feel that it's perfectly OK.
You and I have nothing in common in this entire world.
Why on earth would you even want to touch Julia with 12 lightyear stick? The whole point of Julia is diametrically opposite to your preferences.
Being opposed to multiple dispatch, polymorphism and generic programming, while 'liking' Julia, is similar to loving bycicles, but abhorring wheels.
The multiple dispatch and oo features are some unfortunate warts, but I can live with those. Following your analogy, I love bikes and Julia is an electrically assisted bike. Sure, I would prefer if it was lighter and without the stupid motor, but it's still a bike. Not like numpy which is a horse carriage.
> You and I have nothing in common in this entire world.
for one, I 100% agree with your views on variable naming elsewhere on this thread ;)
But the performance relies on the aggressive specialization which depends on multiple dispatch. And the adaptation to numerical computing, the cleanness and beauty is all about multiple dispatch.
To stay with the bike analogy, multiple dispatch is definitely the wheels, not the motor.
> for one, I 100% agree with your views on variable naming elsewhere on this thread ;)
Waaah! (pulls hair.) And yet, so far apart on the function naming ;)
But seriously, though. Without the incredible polymorphism and genericity, there's really nothing at all left of Julia. Multiple dispatch isn't a feature bolted onto Julia. It is the core philosophy, and the central organizing principle.
This is not a necessity, but an implementation choice. "Specialization" is an unnecessary step when everything is explicit from the beginning. I'm not talking on philosophical grounds, but thinking on concrete examples of jit systems which are really fast but have nothing to do with multiple dispatch (for example luajit).
> Without the incredible polymorphism and genericity, there's really nothing at all left of Julia.
If "matlab with fast loops" is "nothing" to you, sure. As a user of Julia, this is the killer feature for me. The rest I see as unnecessary complexity and mumbo-jumbo. But there's nothing wrong that each user has different favorite parts of the language!
Just stay away from classes and other complex data structures, which you don't like anyway, and performance is very good indeed.
> This is not a necessity, but an implementation choice.
The performance of Julia relies on algorithmic specialization, and since you do not know what input types your code will receive, you cannot take advantage of clever specializations. I understand that you reject the concept of allowing user types that can be used by generic functions, but then you throw all the performance out the window. I want to do fast linear algebra, but your library says "no special static arrays!", "no special handling of diagonal matrices", and in general no exploitation of special structure in user types.
I want to calculate fast derivatives, but your library doesn't accept dual number types.
Your code will never be fast if it's only fast Float64, because it's when my special type elides all computation that the real speedups arrive.
> "Specialization" is an unnecessary step when everything is explicit from the beginning.
But you cannot know what it should be explicit about. I give your `sum` function a special Range vector, or a BitVector, or a OneHot vector. What should it do? It has to fall back to the same thing it does with all arrays: add-add-add-add-add. What is the norm of my UnitVector type? Your library has to ploddingly square, sum and sqrt. In the meantime, multiple dispatch just replaces the call with the number 1.0.
> If "matlab with fast loops" is "nothing" to you, sure.
As a daily user of Matlab for 25 years, several tens of thousands of hours of grudging use, I assure you it would be less than nothing. Matlab is a tragic, horrible mess of a language, and it's not even that slow! I rue every hour I've spent on it. If Julia were slow, and Matlab faster, I would still prefer Julia.
Also, Matlab has OOP and sigular dispatch.
> But there's nothing wrong that each user has different favorite parts of the language!
Well, as I am trying to say, multiple dispatch isn't a part of the language. It is the language. Everything revolves around that concept.
To really tune things you'd have to check the instruction timings among other things[1] for your processor and to be fair apply the equivalent setting for your compiler back end as well if it supports tuning for your processor and anything else applicable.
While it is likely there is a possibility for improvement, like most optimization work, it is usually for diminishing returns for your time and may be small in nature, but sometimes compilers, even those like llvm, do something strange, so you never know for sure unless you check.
[0] https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#In...
the work being done at JAX is great and Julia shares many of the goals (Julia would even be the pioneer in some cases). The best part is your library doesn't even need numpy/JAX as dependency. Yet your function that works on AbstractArray will work on arrays that live on GPU/TPU, for free. thanks to multi-dispatch, you don't need to write a ton of boiler plate code for interface and inherent some classes from JAX/numpy.
f =: 3 : 0
'k a' =: y
n =: #a
A =: (n, n) $ a
^ 0j1 * k * 0.5 ^~ (*: A) + (*: |: A)
)
I'm curious how the speed compares to Numpy, but I don't have a python environment installed. It ran in 6.6 seconds on my computer for n=10,000, but it used a ridiculous 10 gigabytes of memory. When you consider a 10,000x10,000 matrix has 100M elements, that you need two of them since you add two together and that a short has 4 bytes, you technically should only need 800 MB for the whole thing.As for more readable Python alternatives, indeed, the ‘np.tile’ isn’t strictly required, as Python will add a (1,n) matrix and a (n,1) matrix into an (n,n) matrix by default (I would be happier with an error message here, though).
There are many alternative ways to take advantage of this fact, for example what you have suggested
A = a[:, np.newaxis]
M = np.exp(1j*k*np.sqrt(A**2 + A.T**2))
Note that A is a (n,1) matrix here, which is not quite obvious at first sight. Equivalently, my new preferred alternative is resorting to two reshapes n = len(a)
M = np.exp(1j*k*sqrt(a.reshape(n,1)**2 + a.reshape(1,n)**2)
which is more explicit about the shapes of the arrays involved. I have updated the post to better reflect this discussion.In some more complex manual-vectorization cases I have encountered, the np.tile cannot be dropped, and the code looks a lot like my original posting.
Being able to resort to loops, and not even having to think about this manual-vectorization issues is a big plus, if you need to do this very often in your code.
Regards
I believe you've misunderstood the main point of my comment. I don't have too strong opinions about reshape vs newaxis.
The real point was about excessive inlining, which your update does not to fix. A fairer comparison regarding readability would be pulling out the matrix version of a into a named variable:
n = len(a)
A = a.reshape(n,1)
M = exp(1j * k * sqrt(A**2 + A.T**2))
I believe these are much much more readable.When you need loops, you need loops, of course, and python tends to suck here (though with jax's functional programming constructs the boundary is shifting). But the readability/cleanliness comparison in your post is still an unfair comparison.
But your comment just vividly shows, how much proficiency in writing clear code actually matters.
However it is a revolution for the HPC crowd. If your program is supposed to run for several days on multiple nodes, 10 seconds of compilation is a negligible price to pay for a much cleaner and shorter code than Fortran or C/C++.
I've been using Julia for a few months for physics related simulations and it's just so much better than Fortran to work with.
There's also PackageCompiler.jl https://github.com/JuliaLang/PackageCompiler.jl for AOT compilation, but that's heavy enough that I wouldn't bother with it for short scripts.
(and tbh, even leaving a REPL open all the time works perfectly fine, since include("myscript.jl") really does reload the script, unlike the import keyword in Python)
I think the way we talk about julia sometimes makes Python and Matlab programmers think they can just take some of their old code and copy paste it into julia, switch around some keywords and have everything be faster. It's important to emphasize to these people that it's a different language with its own idioms that need to be learned.
Writing proper and idiomatic code that gets the best out of each language takes even longer as it requires more knowledge and experience. Having spent the last ten years or so predominantly in Matlab, and the corporate environment moving increasingly to Python, and my own interest in Julia, I am getting a double dose of this.
Pragmatically I like Matlab best, in large part because I am so comfortable in my workflow and the large existing codebase, but also the IDE, debugger, etc. I most fascinated by Julia but find that exploiting its potential has its own learning curve, and wrangling with type stability has its own challenges. I am least enthused by Python, which I am learning mostly from necessity, but this may be colored by my extreme aversion to its use of indentation.
Still a work in progress, but I've written up a few of my lessons learned in the process [1] in case they're useful to anyone else. Properly figuring out type stability and dispatch-oriented programming took me way too long, but things started making a lot more sense after that.
I haven't used it a ton, but [2] also seems potentially useful for anyone else interested in making the same switch.
[1] https://github.com/brenhinkeller/JuliaAdviceForMatlabProgram...
I need a way to package and distribute scripts, so that they can run in a container for example. Running a server to execute scripts only works well for local workflows. PackageCompiler.jl has terrible ergonomics. I don’t think Julia is quite there for scripting yet, unless you’re already deeply invested into it for other reasons.
@numba.njit(parallel=True, fastmath=True)
def w(M, a):
n = len(a)
for i in numba.prange(n):
for j in range(n):
M[i,j] = np.exp(1j*k * np.sqrt(a[i]**2 + a[j]**2))
and timed it like this: %%timeit
n = len(a)
M = np.zeros((n,n), dtype=complex)
w(M, a)
On my 8-core system, this ends up more than 10x as fast as the numpy version he listed (which seems to lack the sqrt, though), which would place it close to the multithreaded Julia, even considering that ran it on a 4-core system. As an added bonus, it can also pretty much automatically translate to GPU as well.yet Numba basically makes your python code not python. It doesn't support so many things: pandas dataframe, or even as simple as a dict(), which means you often have to manually feed your numba function separate arguments.
To separate a complicated calculation into numba-infer-able parts and the not ones is not fun and sometimes just impossible.
Also, the jitclass things help somewhat. I use them as plain data containers, to work around the hideously long argument lists that otherwise would be required, but with no methods. jitclass breaks the GPU option, though.
Which is why I switched to dask, which even though slower integrates better with numpy.
The most expensive part of the fortran code is evaluating sqrt and exp.
For example on a 5600X the current Fortran code (with n=10 000) takes about 2.7 seconds. If I change the definition of M to remove the special functions (M(i,j) = a(i)+ a(j) then it takes .3 seconds. (Using gfortran with -Ofast)
When the special functions are the issue, using the Intel Fortran compiler and Intel MKL will make the code about 2x faster (this is what Matlab or Julia is probably using)
If you want to use a library to speed up the Julia code, checkout https://discourse.julialang.org/t/i-just-decided-to-migrate-... (but replace `@tvectorize` with `@tturbo`), which is code by eigenspace.
Just evaluating the special functions exp and sqrt is taking 90% of the time. You can evaluate these special functions in parallel, but that is about it.
On my machine the julia code on my PC is:
N = 2000
88.273 ms (8 allocations: 61.04 MiB)
49.445 ms (3 allocations: 61.05 MiB)
14.599 ms (2 allocations: 61.04 MiB)
If I compile the following code with pythran: #pythran export texp2(int)
def texp2(N):
a = np.linspace(0,2*np.pi,N)
return np.exp(1j*100*(a[:,np.newaxis]**2+a[np.newaxis,:]**2))
I get: timeit texp2(2000)
12.3 ms ± 27.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
So actually slightly faster than the optimized julia code.https://discourse.julialang.org/t/i-just-decided-to-migrate-...
The author made some julia performance mistakes in his benchmarks.
The code in this version:
https://discourse.julialang.org/t/i-just-decided-to-migrate-...
should be very tricky to beat, and I suspect that the only way is doing a lot of customized assembly.
What CPU are you using? I know my CPU, a Zen+ Ryzen 5 2600, is one of the worst modern CPUs for this sort of thing and there's much better scaling on Intel CPUs or the Zen 3 CPUs, but those things are probably also effecting the Pythran code as well.
Is Pythran any good at producing BLAS microkernels?
For 2000x2000, their time was 14ms, vs 6ms for the 2600.
exp(x) = exp(x.real())+ sincos(x.imag())
(for simplification sincos(x) returns cos(x) + jsin(x), but you get the point).The other code can't easily optimise for that (although it would be a nice optimisation). Interestingly (and I can't explain why, maybe someone knowing more about julia), if I change the code of that third method using tturbo to use the cis function (or exp) it becomes a factor of 20 slower and is actually the slowest of all the different algorithms)
exp(x) = exp(x.real()) * sincos(x.imag())
Anyway, it is the same algorithm as long as `x` is real.
The reason for manually inlining exp(::Complex) and manually decomposing the complex number into its real and imaginary parts is that `@tturbo` currently only supports real inputs.Given types that it does not support, it will silently run the original loop single-threaded and optimized simply with `@inbounds @fastmath`. I imagine in changing to cis or exp, you also switched to using complex numbers? If so, that would explain the dramatic slowdown.
If not, mind sharing your example, here or in an issue at LoopVectorization.jl?
It's planned that "some day" LoopVectorization will support complex numbers through the AbstractInterpreter interface, but this is probably still a ways off.
Yes I was using complex inputs when switching to to cis/exp. LoopVectorization not supporting complex does explain this.
As a side note, I generally dislike silent fall-backs, for optimisation libraries/modules. That's why I always do python=False when using numba (and I wish it was default). I'm using the library/module because I want speed up, the problem with the fall-backs is they are often slower than the default code (e.g. unwrapped loops in numba, and in this example), so I would like to know if it doesn't speed up the code, because rather not use it then. A failure would save me benchmarking the slow-down.
Hopefully after the LoopVectoruzation.jl rewrite we can make it understand complex numbers automatically. We could probably put in special-case complex support today, but we’d really like to support arbitrary structs of bits.
You can start `julia -t4` for 4 threads, for example. Or you can set the environmental variable `JULIA_NUM_THREADS=4`.
If you were using the same number of threads for both, mind letting me know which CPU you're using?
N = 2000
27.137 ms (24 allocations: 61.04 MiB)
49.496 ms (3 allocations: 61.05 MiB)
4.597 ms (2 allocations: 61.04 MiB)
The CPU is a ryzen 3600 btw.I have to say LoopVectorization.jl is quite an impressive piece of work. I have to try out some more julia at some point, unfortunately pretty much everything I do is using complex values.
https://gist.github.com/cycomanic/a0b16ab0c45b3d888a818d17a0...
Anyway, this is not a complaint about the language (which I like very much), just a dislike of the popular usage
In any code you write, you should be asking, "how will people check this?" If the answer is "by comparing it to something else," then it should resemble the original as much as possible. If the answer is "by thinking about it," then it should be formatted for self-contained comprehensibility. The distinct use cases lead to different best practices.
For the record, I'm not a blackboard addict, I never had a career in academia and I'm not even really a member of Julia community any more than any other person who wrote maybe a couple thousands of LoC in Julia. But I wish people would realize it's not 1970's anymore, and ASCII is culturally outdated. For that matter, I would love if I could write formulas in my code the same way I do on paper, like in that MathJax sample from the article. That's why math notation exists at all, imperfect as it may be (and I hate many things about established math notation, but it's the best we have). I just don't see how it's possible without abandoning plain-text, which I surely wouldn't like because we (currently) don't have tools that would make handling it as effortless, as editing text in Vim.
The fact is it's easier to type "mu" instead of "u" (like right now, I can't be bothered to look up "mu unicode", or open the special characters picker, or exhaustively brute force alt-gr key combos to find the mu).
Take it a step further, I wish physicists and mathematicians would stop naming their variables so poorly so that only the most seasoned of their field can follow along.
Sometimes, like v (velocity) it's quite obvious, at other times I find myself staring at scripts with insane variables like a,o,t_o, etc. And this is a habbit that seems to carry over to non-academic code as well. It's trivial to spot code written by a mathematician because it tends to be incredibly obfuscated.
Do you really need to name your constants K_(single letter)? Or can you just write "AIRDENSITY" once and let your editor autocomplete it in the future?
The reason I complain about these symbols is because they don't have clearly defined meaning. Maths/Physics had to pull out the Greek alphabet after it exhausted the Latin alphabet (and overloaded most letters with 2 or more meanings) and then promptly molested the Greek one in the same way. Just look at Omega. It's useless. It has been overloaded with so many possible meanings that it has none. Stop naming your variables "omega". Unlike Maths, programming languages support variable names of more than 1 character long.
Honestly I really hate seeing mu_a, delta_k and company all over the place. If we are gonna name our variables like this might as well give us the actual characters
your criticism of Perl is unfair. Base perl has supported unicode variable names since more than 20 years ago, way before all the other languages that you speak about.
For example these equations look similar,
x^2+4x+5=0
y"+4y'+5=0
Even though one is a polynomial equation and the other is a differential equation, the common visual pattern suggests that the techniques needed to solve them may be similar.
On the other hand in programming there's no need to do this, all the math has already been worked out on paper, so it's better to use clear, distinct, easy to type variable names.
How important is form when I'm programming mathematics?
Not important.
Who in their right mind does math on a computer, math is implemented on a computer, but it's done by hand.
Maybe you think this becuase your used to writing math on computers that make writing that math ugly and obtuse?
Besides, having the math you've written by hand closely match the syntax you use on the computer can greatly reduce the cognitive burden of switching between code and paper.
Making your code performant, and doing a thorough numerical analysis, usually requires a significant rearrangement of your formulae.
Why would I use the variable name "probability_distribution_on_m" instead of "rho_m", when I know all along what rho_m means in the contexte of my code (the symbol use throughout the paper). Usually, I comment at the beginning and specify what the variables mean and to what they correspond in the paper. And if somebody needs to read my code, that person will need to understand the paper first. More descriptive variable names won't make the person understand the paper better.
Of course, when I am writing some non-scientific software, I will use descriptive variable names, because there is no complicated formula, no paper that sets the context of the code, and it makes sense in general to have descriptive variables for logic elements for a clear code.
What I do think is bad form would be naming it ρ_m.
Edit:
There are like 5 replies to this mentioning different ways of doing it (including memorizing 3 digit unicode values) none of which seem more intuitive than writing "mu".
We're in the 21st century, all the tools we use should have reasonable Unicode support. The fact that we collectively keep on talking about typing non-ASCII characters as a real problem is a pretty depressing reflection of our shared computer infrastructure :(.
Write for the convenience of the reader, not of the writer!
Also, Emacs has an input method which supports "\mu", and this convention is also used in Racket for things like λ.
I agree that we need better system-wide tools for arbitrary Unicode input. Font support and confusable glyphs are also issues. But I think these are solvable problems, and they will be good problems to have solved.
\mu<tab>
You just type \mu and then hit the tab button and you get μ. I actually switch over to my Julia REPL all the time when I’m emailing someone and want to type a greek character.
So you can see how this becomes a bit of a barrier to entry.
For new users, if they're not used to writing unicode characters and don't have an editor with nice support, then they don't have to write them.
The julia community has a pretty strong convention of giving both unicode and ascii versions of almost all functions. Some packages don't have this convention, but I'm not aware of any big important ones that don't follow it.
I use vim, and have a completions plugin (part of julia-vim mode), so that I can write \mu<tab> for any unicode name, which makes it as easy to type as "mu", but easier for me to read (as it matches my textbook expectation of the formula).
And LaTeX codes are not always obvious. For example it's \triangleleft but \leftarrow
The solutions to those problems is to use better fonts, terminals, and editors. Using Unicode characters is fine but some people do have legitimate problems with them.
Typing certainly does add more friction between u and µ than a chalkboard would, though it is perhaps also notable that mathematicians seem to have felt that it's worth the effort, and have come up with a quite nice system for it in LaTeX -- and I would certainly not mind seeing more languages and editors add support for LaTeX completions the way the Julia ecosystem has.
Not all fonts are good fonts for an evey use use; heck, not all fonts make 0 and O or 1 and l and I clearly distinguishable.
> The solutions to those problems is to use better fonts, terminals, and editors
Exactly.
Some proper use casees IMHO are: 1. in "terminal" code: scripts, notebooks. 2. function internal variables 3. for making the code look like their counterpart of a paper.
idk what to think of 3. if you look at any paper, non of them only uses ASCII, which raises the question, if we're happy with reading papers (even CS ones) with symbols, why not in our code?
Maybe I'm behind on this, but needing to use the mouse to find symbols in a huge menu is very painful.
You can copy and paste too but again, huge break of flow.
Do some editors support discord-style emoji syntax, where typing :fo would bring up a menu of emojis that might match foo. Then hitting enter inserts the emoji over the :foo: representation. You can also not use the auto complete menu.
Ex. :pow2: might turn into ²
EDIT: In general it makes sense to set up your keyboard with a compose key and a “dead Greek” key, and set up your Compose table so the shortcuts make sense to you. Then you can use an expanded set of symbols everywhere, including comment boxes like this one. You can even put things like your email address in your Compose table.
Better yet, you can reverse look up how to type a thing:
help?> χ²ᵢ
"χ²ᵢ" can be typed by \chi<tab>\^2<tab>\_i<tab>I fully think this is a problem with archaic input - but that doesn't mean it would not be inconvenient when navigating through a code base :/
clear control
clear mod2
clear mod4
keycode 134 = dead_greek dead_greek dead_greek dead_greek
keycode 108 = Multi_key
remove Lock = Caps_Lock
keycode 66 = Control_L
add control = Control_L
But you have to figure out the keycodes that your keyboard is sending to make it work for your particular machine. The program xev is good for this.This mapping also makes the capslock key into another control key, which is good if you use Vim.
The Greek letters will be on keys that make sense, and the other Unicode characters are pretty rational too: to make é, type <mod>'e. You can make any key combination produce anything, even long strings, by putting the mappings in the Compose file. My Compose file is in /usr/share/X11/locale/en_US.UTF-8. I have snippets in there, such as my email address. This way the mappings work anywhere, not just in a particular editor.
https://unix.stackexchange.com/questions/292868/how-to-custo...
In particular: https://gitlab.com/interception/linux/tools
https://gitlab.com/interception/linux/plugins/caps2esc
(for remapping caps lock to escape - in the terminal and under Wayland).
This is optional, the language adapts to the capability of the user. He can write ∑ or sum for a function, µ or mu for an identifier.
https://docs.julialang.org/en/v1/manual/functions/
You get people riled up in this thread with your false assumption, next time simply ask questions.
Do you have any example? As far as I know this is strongly discouraged in the community.
On the other hand, if you're going to use \mathfrak{i} to index a simple loop you're doing it wrong.
So, if you are maintaining code where that is the idiom, use a font where that isn’t an issue.
You can also put Greek symbols in superscripts or subscripts and that works just fine.
Do you think the Symbolize (or the Notation package) are useful? I don't see any examples in the docs, so hard to tell.
I do wish that Julia would start up in an interpreted mode and compile in the background so that it would be fast enough when first opened and then attain maximum speed later on. (I think this is how JavaScript engines work?)
The problem with Julia tooling in general is that they feel 90% done. And tooling is, in my opinion, more important than the language itself.
There's much more recent work here: https://github.com/tshort/StaticCompiler.jl/pull/46 and apparently some more is still ongoing privately.
That said, yeah. I would not recommend Julia currently for people who truly believe they need AOT compilation, and that they need to trigger AOT compilation very often and with low friction. That definitely needs more work, but it's happening.
That said, a lot of people overestimate how much they actually need AOT compilation.
Julia has some fantastic tooling in other areas though. Especially the package management and interactive analysis tools.
Yes, modern V8 does this, as does the JVM. Common Lisp runtimes also often support this, though I think they usually leave it up to the programmer to choose when to compile a function, they don't always do it automatically. The .NET CLR also behaves like Julia - JIT compile on first execution of every function.
Are you hitting the startup time very often? If so, you might want to try some things to keep one julia session open and sending code to it like a daemon instead of constantly closing and opening sessions.
This package makes that workflow really easy: https://github.com/dmolina/DaemonMode.jl
Actually loading them when running scripts is O(s) even for some of the biggest library (Plotting, Differential equations etc.)
There is also technically an interpreter if you want to go that way [1], so in principle it might be possible to do the same trick javascript does, but someone would have to implement that.
f(x) = 2x + 1
The first time I call f(1)
it’ll compile a function specialization for f( ::Int), and the first time you call f on that integer it’ll be slow and all the subsequent calls will be equally fast.Next if you do
f(1.0 + 2im)
it’ll compile a new specialization for f(::Complex{Float64}) which will be slow the first time while it compiles and then fast on all the subsequent runs.The genius of Julia’s design is that the JIT compiler is designed around the semantics of multiple dispatch, and the multiple dispatch semantics are designed around having a JIT
It happens to be an example of a pathological vectorization case for GCC with an ancient issue open, sigh [1]. Much as it pains me to say so, apart from NAG's, I think, all the other compilers I know will vectorize such loops: ifort, xlf, flang, PGI, nvfortran (but the last three may be essentially the same here).
Also, last time I tried it, -floop-nest-optimize (marked "experimental") was quite broken, again with an issue open either in the gcc or redhat tracker. -O3 does unroll-and-jam, at least.
M_old = np.exp(1j*k*(np.tile(a,(N,1))**2 + np.tile(a.reshape(N,1),(1,N))**2))
with a_squared = a ** 2
M = np.exp(1j * k * (a_squared[:, None] + a_squared[None, :]))
This gets you to the same performance as single-threaded julia.https://github.com/mdmaas/julia-numpy-fortran-test/blob/main...
For fun I ran in Matlab with a 2.9 GHz i7-7820HQ and get about 1.83s for N=10,000 single threaded.
A = exp((k*1i)*sqrt(a.^2 + (a.^2)'))My biggest annoyance with Julia is however that they decided to follow matlab and do matrix operations by default and even worse use the '.' as the element wise operation modifier. From my own experience and from teaching many students, one of the primary source of issues is getting that wrong. I don't know how many times when debugging some weird matlab issue it was a matter of find the missing '.' they could have chosen any other symbol but instead the opted for the easiest one to overlook.
As far as the ‘.’ for elementwise operations, I don’t think it’s that hard to miss, but I guess my eyes are trained for it by now.
There’s also an @. macro to make an entire expression element wise though if needed, e.g.
@. h(f(x) + g(y))
is equivalent to h.(f.(x) .+ g.(y))
I think this broadcasting machinery is one of the closet parts of julia because you get to choose at the call site if you want the function to apply element-wise or to see the whole array, and it works on any container, with user customizable behaviour for user defined containers, and has all sorts of goodies like automatic loop fusion.https://github.com/mcabbott/Tullio.jl
https://github.com/Jutho/TensorOperations.jl
https://github.com/under-Peter/OMEinsum.jl
Tullio.jl is the most powerful and interesting to me of the above libraries, but they all have cool strengths.
the syntax for broadcasting a regular function is
f.(x)
but the syntax for broadcasting an infix function like +, is x .+ y
so the dot goes at the start. I think there were some convincing reasons it ended up this way, but I forget what those reasons are. It’s just muscle memory for me at this point. You can also treat any infix function as a regular prefix one by wrapping it in parens, e.g. (+).(x, y)I guess I'd say that then someone would get to own the syntax
[a, b, c]
it'd either be a vector of a non-mathematical array and whichever choice was made, someone would be unhappy. I think not too much was lost by having them be the same thing, but the ship has sailed anyways.I like the Matrix/vector operations by default for the reason that it becomes quite a bit simpler to understand the written code. Expressing things the way Numpy forces you to, means that you have to reason about the interface of your code to Numpy, as well as how Numpy will operate.
For matrix mult, I agree, writing out loops is tedious. This is why things like M = A * B, where M, A, B are all matrices, is far, far better than writing out the loop representation. I agree as well, messing up the index ordering is annoying (and I still do this 30+ years onward with Fortran, C, etc.)
Julia makes this stuff easy to trivial. It makes the interface to using this capability easy to trivial. It makes for good overall performance (I rarely run codes only once).
2. Have not-slow for-loops is invaluable precisely because sometimes your workload is not (either too hard, or impossible/bad in terms of RAM usage) suitable for vectorzie-styled code.
2. I agree that this is one of the nice things about Julia.
f1(::Vector,::Vector,::Vector) ``` You can write
``` function f2(a,b,c) tmp = a + b return tmp1 *c end
f2.(::Vector,::Vector,::Vector) ```
Fortran’s MT performance is more along the lines of what I would expect.
Julia is an incredibly flexible language that's all about enabling powerful code transformations, so it can be adapted to very different niches.
is this still the case
Slow startup makes it questionable for CLI tools, although that is supposedly improving a lot.
But what about high-concurrency "server-like" workloads? The language itself I think would be great for such an application, but I have no idea if the runtime itself would be good.
If you have specific things you are interested in, feel free to follow up.
What problems would they be trying to solve?
n = len(a) M = np.exp(1jk(np.tile(a,(n,1))*2 + np.tile(a.reshape(n,1),(1,n))*2))
is equivalent to
M = np.exp(1jk(a[:,None]*2 + a[None,:]*2))
which I find very easy to grasp.
How does one make that out in the formula?
Julia was designed for people who don't want to use a low level language for speed. It took a lot of great ideas from languages like Common Lisp, Fortran, etc. as well as a few good ideas of its own and packaged them together in a rather novel way.
The language is dynamic, but it's dynamism is limited in specific ways that makes it very easy for the compiler to optimize code.
The trifecta of complete type information, compilation to native code, and efficient data layout, is what makes the difference.
Notably, Julia is not any faster than most static languages, nearly all of whom also use the same trio.
Numba is very cool, but it's inherently limited.