Addressing skill intersection issue and realizing Julia full potential

Julia has a big issue. Julia is described as a language that is fast, easy, and can do this and that. This comes with a big asterisk. Let’s not dodge criticisms and face it head-on.

Julia is easy, except when I decided to learn Julia…
Oh, I had to learn multiple dispatch and abandon the old OOP paradigm, including how constructors work. I had to learn about type stability, and how it affects the code performance (function compile-time dispatch vs runtime dispatch), as well as function barriers to have a mental model of how Julia works. I had to learn to use code-warntype, which was a macro, which means I was also dragged into figuring out what a macro is!

But that pales in comparison to writing a good package in Julia, and what constitutes a good package. Now, Julia is a garbage collected language, so knowing GC/etc are essential to fully optimizing code. Guess what? Let’s make an example. To make a source-to-source AD for common uses, you need to know linear algebra (because that’s a common use case), LLVM, loop optimization, perhaps SIMD, cache, maybe GPU, etc. Also, if you want to understand use cases, maybe add scientific simulation and so on! What you end up with is a ridiculous list of requirements. And this applies when you want to make your package composable with X-Y-Z, especially if you’re the person maintaining the specialized methods. To use Julia to its full potential, it’s not quite different, even if we imagine all things are perfectly composable, which such perfection doesn’t exist, to make X by composing A with B and then with C, you need to understand A,B, and C, which can be more than understanding X alone. Even the simple “Julia advantage” can easily be a disadvantage. To get the best performance, if you need good algorithms and good implementation, guess what? The algorithm may have been developed by a theoretical computer scientist and may only have a horribly-performing reference implementation, if at all, and then you need someone who understands hardware. How are you supposed to get someone who knows both the algorithms and the performance optimization?

Julia is made by demanding users, and now it demands back. How are we supposed to get the people with the right intersection of skills to compose Julia libraries to full potential? I’m not sure if I can be optimistic about it.

2 Likes

It is a (well known) point that this simplified marketing isn’t/wasn’t doing Julia only good. Julia can be easy. Julia can be fast. Sometimes both, sometimes not.

Who forced you to learn these things? You can write perfectly working Julia code without knowing anything about type stability etc. You decide how much you care about performance (optimization). To me, this is a key property of Julia: you can gradually tune between “I just want easy code that works” and “I want to optimize the heck out of this function”. (Be aware though that the latter can be addictive.)

Once again, you don’t have to support (or compose with) everything that’s out there. And also you don’t have to write a source to source AD if that’s not what your into. Yes, it’s hard. Yes, you have to know a lot of things to make it work. So what? There is no free lunch (in any language).

Are you claiming that this is specific to Julia?

17 Likes

I agree with @carstenbauer You are thinking like a computer scientist. I would say think like an engineer. Engineers choose tools like Matlab (etc etc) and dont go into looking at the source code (you cannot do that anyway)

As I often say on discussions here “good enough for jazz”
If a tool gives you good results for your simulation and is fast enough to give you results in the time your business needs then why optimise it further?

6 Likes

This is an interesting topic - thanks for bringing it up.

I think we need to be careful to separate two kinds of “skill requirements”:

  1. Skills that are inherently needed to write some kinds of high-quality code, which has nothing to do with Julia. Examples of this include:
    • If you want to write an AD library, you need to know about autodifferentiation
    • If you want to write an ultra-fast SIMDd library, you need to know about low-level computing including SIMD, details of memory management etc.

Until we have AI-backed compilers that can do the programming for us, these requirements are just a fact of life. Worth griping about, for sure, but more in an existential “woe is me” kind. It has nothing to do with Julia, really. That’s just how the universe works: You want to write expert software, you gotta be an expert.
On the other hand, we have

  1. Skills that are specifically required for Julia, and which we believe COULD be made obsolete by better design of Julia and its ecosystem. These include:
    • How unreasonably hard it is to detect and amend code cache invalidations
    • Why it is so difficult to understand all the obscure details about what Julia considers stable API - don’t import undocumented names, don’t rely on RNG number or float comparisons, don’t rely on inference for results, don’t rely on exception types or check the exception type in try/catch statements, don’t take “vector” to mean Vector in documentation, don’t rely on the explicit return type unless documented, don’t rely on specific fields existing in structs, don’t use using MyDependency in package code, etc. etc. etc.
    • How it’s almost impossible to write code where you have any reasonable guarantees that it will remain type stable across Julia releases
    • How hard it is to track down and eliminate all allocations from Julia code

… and more. I think these are much more fruitful to discuss, because we can actually do something about them.

Also, I totally agree that this latter list is problematic: It’s way, way to hard to write high-quality Julia code, because the language is built in a way that encourages cowboy hacking, not high quality, robust and low-maintenance code.

Nonetheless, I still encourage very specific griping. For example, what can be done about making it easy for non-experts to write Julia code that doesn’t use ANY internals, or rely on things that are subject to change? I think this is a really interesting question, and I believe this subject requires core language support.

14 Likes

I wonder whether or not what you know helps you when learning Julia. For example.

  • Is Julia easier to learn if it is your first programming language?
  • Is Julia easier to learn if you know R and not Python, or vice versa?
  • Is Julia easier to learn than Python if you have a background in Mathematics and not Computer Science?

While I can understand that getting good answers to these questions can be very difficult, it might help degrees whether or not to include Julia in their curricula.

1 Like

I think that’s exactly the promise of Julia, you can compose and it just works? [There’s at least one exception, if you use OffsetArrays incorrecly, but then don’t do that, or if enable bounds-checking, and you will know, I think in all cases.]

A.
Let’s first take “realizing Julia’s full potential” to mean full speed, i.e. full runtime performance, and even tiny binaries.

One way would be to code in C style, type all your code, functions and structs with concrete types (it’s not hard to avoid non-concrete, abstract. types).

This will result in no (possible) type instabilities, and then I believe thus no invalidations either.

This will result in the same speed as C, will not be idiomatic Julia code(?), though it has one loophole.

C doesn’t have GC, meaning its code is more complex, while sometimes (not always) faster). You can code in Julia without relying on the GC, use Libc.malloc and free, and then the languages are very comparable, and you could compile to tiny fast binaries with StaticCompiler.jl. It seems unfair to me to complain about Julia for speed or being hard to support e.g. GPUs, that seems easier than in C or C++.

You don’t need to know anything about LLVM (it’s an implementation detail, and is actually skipped after compilation for those small binaries).

To get full speed CPUs are just very complex (in any language), the compiler takes care of a lot for you, e.g. loop unrolling, and SIMD, though it can be manually tuned.

Potentially Julia should be taught this way? Some argue C or assembly should be taught first to learn the low-level details that really help you as a programmer. I think it’s a mistake to teach that way first, but you could do that even with Julia.

There is one other loophole here, C has globals and they are fast, but only if const in Julia:

B.
What Julia is trying to do is cater to the Python and MATLAB crowd, work as other dynamic languages.

But Julia is less dynamic for speed-reasons, than e.g. Python, e.g. has potential of overflow of integer types. Those are defaults, Julia can be made as dynamic, and prevent overflows, or use BigInts as new defaults. Could also be less strict regarding arrays, similar to Matlab, it’s just thought to be bad engineering.

It’s instructive to think about what needs to happen to get rid of invalidations. It’s entirely possible to do without them, as in A. It’s just that in Python where you don’t have them you don’t try as hard to compile and be generic. In C you can’t be generic (or could not, it has since added some features for, I think rarely used, Go has also added generic feature, less powerful than Julia’s).

Why do invalidations happen at all? Julia is recompiling for some reason (and while it’s an annoyance, it just works, though with annoyingly long “startup”); a type changed I think (but why?). Even with the changes now needing recompilation, the recompilation is in theory avoidable, it’s just because of a performance-obsessed compiler, it could opt into less inlining and no recompilation. When you program in Python you use fast C code with, and Python doesn’t have this problem since the C code is precompiled. But the limitation, is that then the binary code for that C code is static, it basically can’t be recompiled (Julia could limit itself in the same way, and even still support generic code). Since the precompiled code from C is static it also means, it can’t be generic, that code, i.e. it only works for some types, usually machine floats and machine integers, and strings, no alternative floats or integers. It also breaks the illusion that Python will not overflow your integers, Python doesn’t, but when used with NumPy, NumPy does it for it.

For Julia to stay generic (a good thing) it needs NOT have compiler at runtime. It just needs to inline less. It means not quite as fast code at runtime. Some features in Julia, like eval, require either a compiler or an interpreter (Julia has both).

The compiler knows exactly where and when it’s (heap) allocating (or e.g. using a non-const global). It could show you with a WARNING when you define the function (if it knows, but it CAN’T know for all types, e.g. those not yet defined). I’m not sure we should be too concerned. Heap (and stack) allocations are very natural in a program. It’s just in some extreme cases we want to avoid or eliminate completely. They are however often a symptom of other problems, that go away if you address the allocations. Very possibly one code-path allocates, and another doesn’t. Which is not a problem, the compiler sees the whole picture. What’s more problematic is that you can call code that allocates, and also if your code is generic, then some code path may only allocate for some type, e.g. none of the default types, or what you have tested with. There’s probably no way around this except go with A. and no generic.

That’s not a limitation, it’s just syntax. Use const c = Ref(1) and consider @code_native (()->c[])(). This is almost verbatim the same assembly as you would get in C with a non-constant global long.

1 Like

I find this less specific than needed. Which internals? I never use any, for instance. Do you keep issues open for the internals you cannot avoid? (If they are really necessary they should be exposed, or at least an issue should be kept open until a solution is found).

1 Like

Making an AD framework is a nontrivial undertaking, so yes, you have to know and understand a quite few concepts for it, including (:drum: roll for suspense…) calculus. But this is not specific to Julia.

To make a program fast, you can invest in understanding how compilers and modern computers work, including, LLVM, SIMD, cache, etc. But this is not specific to Julia either.

You would face the same issues in any modern language, including C++.

You pick it up by practice. It is not a big deal, especially compared to acquiring some level of expertise in a field. Internalizing the performance tips is maybe comparable to 1/3–1/2 of typical one semester course, depending your previous exposure to computing.

Pro tip: time management. Instead of opening highly speculative topics like this one, this one, or this one, just program Julia. It will pay off.

12 Likes

Lol… I did one library which kinda became an awkward mess. I did a bunch of benchmarking with Julia vs C++ and Julia managed to win at one benchmark thanks to the loopvectorization library (and perhaps because I was not good at fully optimizing C++ code). I also dipped my toe into JuMP lately. That being said, admittedly one’s brain is inclined to do easy tasks.

Yeah, I guess it’s unavoidable. However, the big problem is that we Julians figured out that X, Y, and Z are usually already done, so we had to leverage the strength at the intersection of requirements for Julia to have some chance. Maybe that’s inherent? Maybe that strategy had a price.

I’m a self-contradictory person. I naively tried making games with Pygame then moved to Julia to take more performance without sacrificing ease so that one day hopefully I would be able to make my dream game, which, if not for some miracles, won’t come true. I chose Julia as my language of choice, hoping one day to build the game of my dreams from the first principle. So, I became someone cheering Julia :sob:

Sorry for the long rant ahead.

To illustrate how hard the vision that led me to Julia was, think of the Avengers fighting Thanos in the movie, it had lots of movements and magic options, in contrast to just left, right, up, down, forward, and backward in the game. This requires fine-grained control, which meant traditional controls wouldn’t do, and it probably meant I had to allow macros/keystrokes/etc because games would be too hard to play otherwise with so many movement options, but then, with that, aimbots could come alongside endless glitches and fixing a game glitch in a highly simulated game without removing intended features and/or ruining performance might be impossible, not to mention I already knew even if I miraculously formed a team, we would not have the resource to fix glitches or balance. So, I decided to go full emergence gameplay, with no balancing, most glitches becoming features, and aimbots allowed, this requires me to throw off pretty much the entirety of game design knowledge. But alas, my bet on game design alone was not enough and I ran out of game design tricks and had to turn to technologies. So, now I’m living in the Julia community hoping the stars align and somehow my wish would come true. And stories as to why I had such a far-fetched dream? Because I didn’t quite buy many popular games when I was younger, I played games in my head imagining how good the games would be, and alas, I fantasized so much and woke up to shocking disappointments when the games turned out to be… well… the games they currently are. Overwatch/Minecraft/etc were good, but not enough. So, I guess my speculations are totally just me cheering for the stars to align, and I bet for Julia to be the one that brings me there, Julia is a language for the greedy after all, so maybe if I wished hard enough and everything aligns, it will come true, knowing full well how unlikely it is.

All of my early Julia attempts were horrible in retrospect. Don’t worry, you can either forget or refactor them later.

It is like the first instruments from a trainee luthier: a waste of fine wood from one perspective, but a necessary learning phase from another.

12 Likes

There’s the old parable about the pottery teacher who decides to do an experiment. At the start of the course, they tell one half of the class to spend all their time on making one perfect ceramic pot. The other half are to make as many pots as possible–they won’t be graded on quality, but purely on the number they can crank out.

At the end of the course, it turns out that the students who were supposed to be doing sloppy work in high volume are actually turning out better pots than the ones who were supposed to be focused on One Perfect Result.

8 Likes

After spending half a day chasing down an bug in my code (that was in a branch not caught by unit test, because it involves two functions, and they have to be called with a specific parameter combination, coverage was ostensibly 100%), I feel like the student whose pot made the kiln explode, burned down the neighboring village, and made the king declare pottery illegal.

4 Likes

Unfortunately Julia --code-coverage has only line-level granularity. Tools like coverage.py support branch-level coverage measurement.

That wasn’t the issue. Conceptually, the code looked like this:

function make_A(flag_A, inputs)
    if flag_A
        do_A1(inputs)
    else
        do_A2(inputs)
    end
end

function make_B(flag_B, inputs)
    if flag_B
        do_B1(inputs)
    else
        do_B2(inputs)
    end
end

combine_ab(make_A(flag_A, inputs), make_B(flag_B, inputs))

Unit tests covered flag_A == flag_B == true and flag_A == flag_B == false, hence 100% coverage. But the bug was in flag_A == false && flag_B == true. User error :stuck_out_tongue_winking_eye:

2 Likes