Does Julia Create a "1.5" Language Problem?

I also think Fortran looks simple and clean :slight_smile:

My impression coming to Julia was that it could offer performance in a “functional style” with minimal mutation, though I don’t know where I explicitly got that idea. Maybe the part where people said it’s supposed to look like math. I guess the surprise was that you have to write code more like Fortran and less like OCaml to get substantial performance gains.

1 Like

Vectorization in MATLAB and NumPy in Python are what make them usable for scientific computing so I would hardly consider them “optimizations” in the same sense, though they technically are. In Julia, my impression is that the surprise here has been that the non-optimized code is so slow, though I guess people have reported specific cases in the past - but not with the general proclamation that it has been a failed promise.

1 Like

I’m not at all sure, MATLAB’s up-to-date internal workings aren’t immediately available upon browsing. Earlier when I talked about MATLAB reusing allocations, I was paraphrasing the talk, which used the phrases “temporary vectors to cache computations” and “caching temporary variables.”

That last phrasing was telling because I don’t think there is a formal concept of “temporary variables” anywhere; all languages have local variables that live within a local scope, but he was likely talking about intermediate data in chained operations and used the term “variable” in the MATLAB sense, a name that owns data. This semantic allows C = A * B to modify C in-place because C owns the data. This is impossible in Julia and Python where variables are just names; A * B makes a new instance, and C is reassigned to it, renouncing its former instance. You can’t just optimize that into in-place mutation of the former instance because it might still be assigned to another variable D which should not change. Similarly, MATLAB must make a full copy when C’s data deviates from D’s.

Although I couldn’t find any literature corroborating cached computations in MATLAB, it’s possible that MATLAB’s JIT recognized that the same variables repeatedly make intermediate data in a loop and attempts to reuse the previous iteration’s matrix allocations. Note that this isn’t equivalent to reusing preallocated arrays in Julia; if any input vectors change length, then you’ll start hitting DimensionMismatch errors, so in some cases you must allocate. MATLAB doesn’t magically elide allocations and does have its own limits; here’s a documentation page on preallocation. Neither the talk nor MATLAB’s documentation make clear exactly what sort of memory management is happening besides the variable semantics.

3 Likes

Sure, I’ve also heard that about modern Fortran, but I’m not really familiar with it.

But, doesn’t it? I believe even parts of Julia Base are implemented in a functional style, using reduce, map etc.

I thought, though, that this thread was more about ‘Matlab style’ Implicitly vectorized code. In which case Julia still does well, but it is the core business of Matlab (and I guess numpy), implemented and optimized at considerable effort in a static language.

Still, Julia can reliably fuse broadcasting, and do it in-place, which is an advantage.

I just thought that referred to the use of unicode symbols😄

That’s not really fair. Vectorizing code is often a very demanding effort, it’s not just about simple surface-level syntax. You need to make sure every level of your code handles array data, make design decisions concerning preferred vector orientation (Nx1 vs 1xN), you have to slice, reshape, gather, calculate intricate index sequences, etc.etc. It can be a huge job, I’ve spent literally weeks vectorizing single algorithms in Matlab, after which they look unrecognizable, and highly complex. It’s an effort not to be undertaken lightly, and is very much a non-trivial optimization.

Reframing Matlab ot Python code to vectorized style very often involves contortionate transformations, when the original algorithm is ‘naturally’ scalar/serial. I’ve spent a large portion of my Matlab career converting simple scalar code into ugly, incomprehensible vectorized code. In Julia, on the other hand, you can vectorize scalar code by just slapping some dots on your top-level code.

4 Likes

One of the best reasons to learn Julia that I heard in the long time.

3 Likes

FWIW, most of what Modern Fortran is is Fortran past the 90’s. And Fortran is indeed a simple language. It is hard to write very inneficient code with it. I used it most of my career and I didn’t find it bad. When I heard Julia solved the 2 language problem, what I came for is to have a language in which using packages (LA, for example) was simpler, plotting and analysing results was simpler, and improving algorithms was more practical with a rapid interactive interface. The two language problem for me was not about prototyping in one language and improving the code in other, it was mostly about having the higher level tools in the same language I was writting the code I needed to be fast. Which includes, for instance, using the language itself as part of the interface for the users to use the packages I develop. The fact that Julia provides that and additionally makes it very easy to develop packages and the documentation is what makes it all appealing from a previous Fortran coder.

More specifically about the video: A Fortran code would look very similar to the optimized Julia code, because it will require you to preallocate all auxiliary arrays. You can have slightly simpler code if the arrays are small and can be statically declared in the routine, because the syntax is not different from that of a standard array (as it would be with static arrays in Julia). Except for the @views, how can matlab handle these buffers better? I missed that from the video, that 10x difference was related to what exactly? Views only? MKL vs BLAS? Or is there anything else in Matlab that makes handling auxiliary buffers better (than Julia and Fortran)?

14 Likes

Possibly implicit multi-threading.

1 Like

From your experience, would you recommend Julia to Fortran programmer? I’m physicists by training, so when I’m not a Fortran guy, I know a lot of them.

I do that probably more than I should :smile:

6 Likes

I don’t think the two language problem is that bad, maybe I’m even a fan, other than the fact that FFI is never fun.
Different tradeoffs and semantics for different use cases.

Julia libraries like Zygote, SimpleChains, ModelingToolkit, or Diffractor are unapproachable to most Julia users. If they were in a different language, that wouldn’t make much difference. Learning new syntax and semantics isn’t that hard.

I don’t want manual memory management as much as I’d like concepts of ownership and lifetimes. This can greatly improve the performance of allocating code, compared to what our GC is capable of.

Strict and pedantic gets in the way of scripting, of getting the real things done that add value to the world and society.
We need languages like Julia that don’t get in people’s way.

But when you want to develop a big complex system and maintain it, for those people who get things done to use? Suddenly, you want all the help you can get.
Analysis tools make refactoring and changing the code much easier. I’m a lot more comfortable making huge sweeping changes to my C++ projects than Julia (much to my detriment, honestly, because maybe I should be accepting more tech debt for the sake of moving forward – but it can be reasonably fun/relaxing…). You’re faster to spot everything in need of updating, or how different pieces connect together.
Lots of bad things outright don’t work, instead of working slowly – it helps you set a higher quality floor.
I don’t have much Rust experience, but would probably point to that as the ideal pedantic language, especially as hacks to get around it tend to be obvious (e.g. clone() or unsafe).

These are the sorts of things I want in Julia as well, and are broadly under the “static Julia” umbrella.
Type instabilities are pervasive in the Julia ecosystem, making much static analysis impossible, and even when they are static, LanguageServer.jl – while very useful!!! – is a long way behind clangd.

12 Likes

It’s mainly bad when you only know one of the languages :wink:

The biggest problem is probably knowing how to link the languages, and getiing started. I used Matlab exclusively for 15 years, struggled with performance, but never got over the two-language barrier. Now I’ve learnt quite a bit of C++, but I’ve still no idea how to use it in a two-language context.

1 Like

Yeah, FFI sucks. ccall + @eval in Julia is probably among the best, at least to call languages that support exposing a C-inteface.

IMO, this is the biggest motivation to stick with one language.

You’d also likely need code duplication whenever you want to share types across both, like if you define struct MyFoo in one and want to use it in the other, you either need some tool to export it automatically (not many of those exist, do they?), or need to maintain two separate implementations that are required to mirror each other.

2 Likes

In line with what others have said already, I find the language boundary introduced by FFI/removed by Julia to be a double-edged sword. Debugging cross-language errors is a pain (assuming you even get stacktraces!) and unfortunately something which comes up in the real world. On the other hand, having unified stacktraces can lead to a needle-in-haystack search for what the real cause is when errors bubble up from deep inside the internals of a library (vs just knowing that a particular public API function needs changing). In summary, it feels like removing the language boundary makes more possible while also making the learning curve steeper.

2 Likes

Imo, yes. I painfully recognized this fact and that’s why I slowly moved away from Julia and migrated to other languages.

  1. You can put features from dynamic languages and static languages into one language, with the risk of breaking your language and creating inconsistency. These features don’t compose well at all. For example, type safety is an integral property of the whole language. Making a seemingly innocent change to the system can add a lot of complexities and even destroy the system. Gradual type and optional type systems do exist, but they are more or less proven to be impractical (at least, on large scale codebases).

  2. Once you realize this, you may try to “control” these features, with PL techniques like interface, occurrence type, static subsets of the language (add your favorite PL paper here)… But there are too many weird corner cases and there is no uniform way to control them. On some cases this method may work, but for others it may not. And what’s worse it’s that people are greedy and they don’t want to make any tradeoff, so nothing can be done to essentially improve the situation.

  3. Another old trick is to give up static checking and move to partial checking or even dynamic checking. Again, like many good old PL topics, they have other fancy names : program analysis, incremental checking, tunable type checking, etc. But I realized that much more need to be done besides these technical issues, after I read Google’s Lessons from Building Static Analysis Tools at Google, which, as the title suggests, a rare user study on large scale code analysis in Google. Here are the five lessons quoted from the paper :

  • Finding bugs is easy.

    In Julia, it’s easy to sliently introduce type instability, even for experienced user (transpose, eigen, type promotion, etc). So with a rather simple 1000-line type checker (implemented by recursively calling code_typed), you can find a dozen of type instabilities in Julia’s type inferencer (yeah, I did this two years ago…).

  • Most developers will not go out of their way to use static analysis tools.

    … and developers may simply ignore warning and commit their codes, which makes it harder to apply static analysis tools, as codebases grow larger. Ideally, the tool should be intergrated into the workflow (IDE/ compiler/ code review), which implies that you may need to cooperate with programmers from other CS fields.

  • Developer happiness is key.

    Similar to the previous lesson.

  • Do not just find bugs, fix them.

    That’s why I emphasize in a previous post that a good mental model for type instability is important. So programmer can understand the error messages better and the tools can generate useful suggestions.

  • Crowdsource analysis development.

    This is more or less the combination of all previous lessons. Code analysis at large scale is completely different from at small scale. For example, whole program analysis never works on large scale codebase due to super-exponential time complexity. We should let users fix their codes by themselves instead of filing issues one by one or guiding them to old discourse post (I have done this several times, too boring…). For example, if it’s easy to forget that eigen(RealMatrix) returns a Union{RealMatrix, ComplexMatrix}, we can just add a rule in the type checker and directly produce a warning. Users can also contribute domain specific ruleset since PL experts generally lack relevant knowledge.

I realize that I have repeated the same mistakes mentioned in the paper. A bulletproof code analyzer is way too difficult to develop, not because bugs are hard to catch or people don’t read enough PL papers, but because there are much more aesthetic considerations than the PL issues. For example, no PL paper will teach you how to collect user data and use them to improve the code analyzer.

So either you find many domain experts to address this problem, or you simplify the problem into something intuitive you can control. Rust analyzer is an example, which is developed by a former JetBrain employee and a large portion of the architecture design inherits from his previous work. I have some PL friends who actively work on rust analyzer. The development experience is rather amazing.

That’s why I lost my last hope : Julia community is too niche to attract a diverse CS user group. Even I am a PL expert (and I am not), I can at most solve PL problems and the final product can still be disappointing, without support from experts in other CS fields.

7 Likes

What I got from this is that developer happiness is key which means you don’t want to impose on them to add rules or go through extra type checking steps. So the onus is on code analyzers which have a hard time with expressive typed languages, and Julia community is not likely to be able to build one.

collect user data and use them to improve the code analyzer

Is harvesting package repositories not sufficient to enumerate the range of use cases or that’s not the information you need for this kind of analysis?

It’s possible I’m biased from my use cases. I would only pick up MATLAB to write vectorized code and otherwise use Fortran (less often C) directly to program scalar computations. I don’t interop though. Usually if I run something in Fortran I want to save the output so I don’t have to run it again.

I’ll admit I thought using Unicode was just a novelty and would fade in the way of APL, but it’s very addictive once you start using it. But searching for Unicode strings or mistaking Greek and Latin characters in your own code, let alone others, is not a trivial problem.

1 Like

Maybe I had expected something like this - doesn’t gcc do the same? Or perhaps I’m thinking of something different.

Do you know why OCaml does so well in performance metrics without preallocation? As I understand it, while mutation is allowed, evaluation is non-lazy and most people would program it in a functional way (less instances of preallocation and mutation).

I had a similar experience to the presenter. From what I heard about Julia (youtube videos etc) I was expecting to write code that looks like the equations in my textbooks and runs as fast as C (and is differentiable and parallelizable by default and can run on GPU by default). I know now this was naive, but that was what I (probably by my own misinterpretation) believed. The reality does feel like a 1.5 language problem. All the points above are valid, this makes sense. You cannot (as I did) expect rapid prototyping and performant code to look the same. But the presenter’s point that we might be giving off the wrong impression and should think about managing expectations is valid imo. To that point, the “runs as fast as C” and “solves the two language problem” are repeated so often, without qualification, that I am not surprised newcomers would be taken aback by any Julia code that runs slower than Matlab/Python.

4 Likes

In my experience with Python and Java, the two language problem hits when you

  • try to debug across the language boundary
  • find your logging ends up in different places
1 Like

I think a new branding strategy could benefit popularity of Julia.

2 Likes