90 comments

[ 4.1 ms ] story [ 161 ms ] thread
At least back in the mini super era, Fortran was (far) easier for the compiler to optimize than C. Yeah, you could add all sorts of #pragma indicating that pointer/array are not overlapping in memory but that equivalence was a problem. But then, trying to do I/o in Fortran was annoying so lots of compute codes ended up with c front ends.
Still is.

Trying to outperform Fortran relies on C developers putting the right annotations on the right places and never get them misplaced, otherwise it will trigger UB.

In my opinion, Julia is much better for numeric performance than C or Fortran. Real macros mean that you don't have to use a janky preprocessor (or a separate programming language) to generate optimal code, and multiple dispatch means you don't need to give your functions awful names (looking at you cgbtrf) and instead can just name them for what they do.

(Also, Julia gives you easy automatic differentiation and gpuification)

The awful names are from a six-character limit somewhere.

I'm curious why you would want multiple dispatch. Seems to me like either longer names or single dispatch would be enough. In "cgbtrf", the first three letters just tell you what kind of matrix are the input... "cgb" being a complex-valued band matrix. That leaves you only three letters to say what the function does.

with multiple dispatch, this function would just be lu. Multiple dispatch means that all the functions that add things can just be called +, and all the ones that compute lu factorizations can be lu, and which specific version gets called is all handled by the type system. If I call lu on a complex banded matrix, it will dispatch to the method optimized for that without me having to care about what that is. This makes writing generic code much easier because you don't have to write a separate copy of your function for each new datatype you want to support.
So like Fortran generics.
Not really, Fortran is inching it's way towards generics, but doesn't actually have them in a usable form yet.
Parameterized derived types seem good enough for what Fortran is used for.
They are powerful enough to express functions that only depend on one argument, but how would you use them to encode the types of both arguments for matrix multiplication?
I don't remember if it exists for LAPACK, but there's a generic BLAS interface with ifort/MKL. It's just a pity it was never (quasi-)standardized. (The trivial example is Fortran intrinsics.)

I was happily using CLOS-like systems long before Julia, though. Does Julia actually beat SBCL, for instance, on such things?

Julia has been heavily inspired by Lisp (especially Dylan and scheme). In my experience, Julia is easier to use for numeric computing.
Julia currently has specific weaknesses that make it unsuitable for a large category of problems that a language like Fortran (or C) has handled seamlessly for decades.
Do you have any specific example? I am truly interested. One of the things I don't like so much about Julia is its community, always preaching Julia is a better language than X, Y, and Z all together. It would be nice to have some concrete examples.
The obvious one is any time you need a statically compiled binary. There's work on that, but for now at least, that's a definite shortcoming.
Can you elaborate further, with an example, how this is different from static polymorphism? I don't fully understand your argument where you say "it will dispatch to the method optimized for that" and then "you don't have to write a separate copy of your function for each new datatype". The two do not seem consistent with each other.
That sounds like single dispatch to me. Maybe I'm missing something.
Eventually, note that you should compare it against latest ISO Fortran standard per commercial compilers, not what gfortran is capable of.
> multiple dispatch means you don't need to give your functions awful names (looking at you cgbtrf) and instead can just name them for what they do.

Fortran has ad-hoc polymorphism, and 63 characters maximum length for names. These should be enough to keep the user happy in this use case.

(The programmer still needs to write separate code for each version of input types.)

Why is fortran so much more efficient (or easier to optimize)?
Because it was designed for mathematics from the ground up.

So the language has built-in optimization for linear algebra kind of math since decades, doesn't rely on pointers for basic data structures and type system prevents aliasing by default.

The language doesn't specify optimization, though it has features for optimizers to take advantage of, and the classic loop optimizations were introduced in Fortran H if I remember correctly. The storage association rules aren't in the type system; you can write code that violates them, and many people did, but perhaps fewer now.
See the section of Coders at Work where the author interviews Frances Allen.
Well, Fortran relies on you obeying the storage association rules that people regularly used to assure me didn't exist.
A bit odd of the author to make claims about "C-like languages" and then focus mostly on C. For example, Java does have loop labels. Further afield, Groovy has a very flexible switch statement which accepts literals, ranges, predicates, and types. In Groovy, you can also index into list literals with ranges and negative indexes.
Fortran used to get lots of hate from the C-like crowd. Maybe it still does.

This said, it is a modern, very high performance computing language. It is easy to, as the article notes, adapt the language to fit the algorithm, rather than doing the reverse which C/C++/etc. demand. The only other language that I am aware of like that is Julia.

Julia has a REPL built in, and you need to use PackageCompiler.jl to compile AOT code. Then again fortran has a nascent REPL[1].

[1] https://lfortran.org/

Honestly, I think fortran is way too far behind Julia at this point to be able to catch up. The work on LFortran is really ambitious and badly needed for the language, but it was needed 20 years ago. Once you factor in the fact that Fortran isn't expecting generics until the end of the decade, and has no real plans for solid GPU support, and I don't see it being enough to keep up with more modern languages.
The example 'exiting a nested loop' was weird. C has support to 'goto', C's version would look like the Fortran's code.
well, you'd label the end of the loop rather than the beginning, which is mildly messier, but yeah
What a weird post. There's so many cool things Fortran does better than C-like languages, but instead the post focuses on superficial syntax like labelled loops and dangling elses. The labelled loops thing particularly irks me because it's a so often repeated qualm about C yet it's performing the exact same function as a goto--so why not use a goto?
yeah I was expecting stuff like not allowing aliasing, which allows the compiler to optimize functions better
I guess some people are afraid to use a gun for fear that they might shoot their foot—or that someone else will. I think goto’s are useful.
There is a dogmatic contempt for goto:s. Like people who use them are stupid or something and obviously doing it wrong.

I think the underlying reason is that it is annoying to write tree walking interpreters and implement goto:s. (How do you even do that?). So the prominent CS folks at the time just dismissed goto:s instead by bullying user to submission.

The contempt is justified. `goto`s break the flow and make the code harder to read and reason about, and thus make it easier to introduce bugs. It's an instruction that's almost never _needed_ today, because there is always a better structure to use instead.

I only use it in Lua, to compensate for a lack of `continue` in loops.

    cybrpnk:~/src/linux$ grep -rniE 'goto\W+.*;' . | grep -E '\.[c|h]:' | wc -l
      182044

Sure.. I mean.. 'almost'
It’s worth noting:

1. One need for goto in C is for what is done with labelled loops in other languages. So one might expect uses there

2. Another use is for cleanup after errors which would be automatically handled by the compiler (via destructors) in most more modern non-gc languages. It looks like:

  int do_thing(args…){
    foo *x; bar *y; baz *z; int err;
    if((err = make_foo(&x, …)) < 0) goto exit3;
    if((err = make_bar(&y, …)) < 0) goto exit2;
    if((err = make_baz(&z, …)) < 0) goto exit1;
    frob(foo, bar, baz);
    err = 0; /* maybe use x,y,z and return instead of cleanup */
    free_baz(z);
    exit1: free_bar(y);
    exit2: free_foo(x);
    exit3: return err;
  }
So really all you’re proving is that C does not provide sufficiently useful structured programming tools to avoid goto. Java handles this fine for memory but not so nicely for objects that have more involved cleanup that shouldn’t be delayed (e.g. open files, removing from some parent object, etc). Indeed Java also doesn’t provide great tools for protecting cleanup code from stack unwindings, or at least they aren’t sufficiently ergonomic that you see them used everywhere.

It certainly isn’t the case that Linux is full of old-school do-whatever-you-like uses of goto.

Meh, that's just the kind of dealing in absolutes that gets on some peoples' nerves. I agree that in almost all situations gotos are probably not the best choice, but there are still situations where gotos are a reasonable option, arguably even the best that's available.
That's a very ahistorical take. The primary reason to move away from go to statements was around reasonability. Structured programming introduced semantically meaningful control structures which replaced the vast majority of the uses of go to statements. Go to statements, themselves, don't carry the same meaning with them (is it part of a loop, a conditional, a subroutine call, a return from a subroutine, moving to an error handler?), go to statements also permit spaghetti code which is somewhere between hard and impossible to express in most languages using structured programming control structures.
Ye sure I agree. Structured programming was a rational displacement of goto:s. I think it took Fortran like 20 years before it got proper loop constructs and you could essentially write to the instruction register with assigned goto, like Fortran was some Macro Assembler.

But the contempt goes further than "goto vs subroutine calls" or "goto vs the if-else construct".

With "underlying reason" I meant more like "underlying reason for the contempt of all use of goto". Even when goto makes the code less spaghetti and more readable.

Knuth did a good write up on the matter: https://pic.plover.com/knuth-GOTO.pdf

What you initially wrote, and what I was replying to:

> I think the underlying reason is that it is annoying to write tree walking interpreters and implement goto:s

I don't believe that there is any historical justification for this view. And your updated statement is even less historically sound. Everything written about go to statements (as a negative, something to partially or fully eliminate) seems to have been predicated on reasonability and comprehensibility of the code by people, not the ability to write tree walking interpreters. Which is a minor activity when reviewing the totality of all computer programming activities of the past 70 or so years.

I don't get why people are so afraid of goto. I looked into Dijkstra's original invective against it and it was an argument for structured programming. People may not be familiar with that term because it won so thoroughly that only assembly programmers work outside if it. It means constructs like loops, functions, if statements, etc.

In dijkstra's world, people just jumped to any code anywhere in the program, leading to insane spaghetti code that was impossible to read. In C++, goto can no longer jump to anywhere in the program, only within the function. This severely limits the potential damage of goto if the functions are otherwise well constructed.

goto can be used for high performance decision trees, loop exiting, and in C (less so in C++ due to RAII) for freeing memory at the end of a function. I used to fear goto, no longer. However, I would advocate using it only in those situations (possibly others) as in excess it will lead back to the same problems identified by Dijkstra.

Yes there's nothing wrong with goto if you need it. Of course, if you are just doing a loop, then a loop structure better communicates the intent and is safer. This is just abstraction and its a good thing. An example from functional programming is direct recursion versus maps/folds, again better to use these abstractions when possible, but direct recursion offers the most flexibility.
> People may not be familiar with that term because it won so thoroughly that only assembly programmers work outside if it.

I read an old edition of Numerical Recipes, and the first chapter or two was a paean to structured programming, which is when I realised that for, while etc hadn't sprung full-form from the heads of the first compiler developers.

There are several good uses for goto in C. Here's a few I can think of, off the top of my head:

1. Breaking out of nested loops. Sure, you can kludge this with a flag variable, but goto is far more readable.

2. Implementing resource cleanup with stack-style unwinding semantics (i.e. RAII-like). The alternative is usually a bunch of nested if statements, which makes things much harder to read.

3. Implementing state machines. goto is very good at expressing transitions in a graph-like state machine. It doesn't inflate code size as using inline functions would, and it doesn't incur register/stack setup costs that non-inline functions would. However, readability becomes somewhat difficult as the state machine gets more complex, so this is usually relegated to code generators like SMC, re2c, or Ragel.

4. Dispatch loops for threaded virtual bytecode. This is especially niche, and it relies on the labels-as-values gcc extension, but it is very important for implementing performant interpreters.

The average C programmer won't necessarily encounter all of these use cases in their career, but that doesn't make any individual one any less important.

For state machines nothing beats tail calls. Those are effectively a goto, but much more expressive and readable. Too bad those are not implemented in supposedly high-performance popular languages.
> Too bad those are not implemented in supposedly high-performance popular languages.

Perhaps a nitpick: you presumably mean tail-call optimisation, which isn't the same thing as a tail-call.

To my knowledge modern C compilers are generally able to perform the optimisation.

I believe Java has a hard time with tail-call optimisation as its exception-handling features require it to faithfully track the call stack in case an exception is thrown.

Yes, GCC can do tail-call optimization. What you can't do is rely up on in C. Lua has tail-call optimizations and because of that, you can rely upon it. There is a difference.
Right, some languages guarantee tail-call optimisation in the language spec itself. The C standard doesn't do this.
> Perhaps a nitpick: you presumably mean tail-call optimisation, which isn't the same thing as a tail-call.

Tail calls consuming unnecessary stack space is a common pessimisation.

Part of GP's point is that none of those are "real" gotos. C doesn't have them. C has a construct called "goto" which resembles goto the way that a vacuum cleaner resembles a black hole. A constrained, neutered, much safer shadow of the real thing.

If anything, the value of C's goto is evidence in favor of Dijkstra's argument more than against it. Real-world programs don't need real gotos, and the situations where you want them are perfectly well-served by constrained, limited replacements. Like function calls, for-loops, and the thingy that C calls a "goto."

For the sake of completeness: there's two main ways in which C's gotos are limited. The first is that they are function-scoped, the second is that they are declared labels rather than arbitrary program points. We nowadays consider that encapsulating data is a good thing. The idea of encapsulated control flow is so taken-for-granted that we don't even realize it could otherwise, but that's effectively what a true goto is.

(comment deleted)
I agree with you, but I thought about it a bit, and IMO you would probably only ever want to use goto for two or three cases: "repeating" something like going to the beginning of a loop, jumping to error handling, or jumping to a function exit+cleanup (everything else just leads to spaghetti code). I can see a lot of benefits for the first case if you are checking a bunch of conditions and have maybe a doubly nested loop, since its cleaner to e.g. jump back to the outer loop than to do a double break. For the two other conditions you can just return handleErr() or return myCleanup(), which I'd argue is cleaner.

Also, I'd argue that you should generally try to avoid doing doubly nested loops with lots of conditionals that you check to determine whether to break or continue. That seems like a code smell.

In most cases I'd agree with you, but in some domains, such as image processing algorithms (particularly for 3d images), those kinds of loops are somewhat common, performance is a first priority, and imo are sometimes more readable with all the conditions in one place even if overall the code is still very difficult.

The `return handleErr(val);` is a nicer way to do it in some cases. I'll keep that in mind though you may have to pass a lot of references to the function for it to work.

I don't remember if it was a rejoinder to Dijkstra, but Knuth wrote "Structured Programming with go to Statements".
Partially a rejoinder, he goes back and forth between structured programming and go to statements considering both sides over the course of the paper. But even in that his conclusion boils down to: go to statements work well if disciplined, but if you have a structured programming construct that actually conveys your intent (by its name, in particular) then that's going to be better in most circumstances.
Before OO programming was going to fix everything, and agile methodologies were going to solve all our problems, structured programming was the universal cure. Between the remaining religiosity about it and everyone's embarassement about their early coding efforts, goto is just anathema.
Java also has labeled loops. Such that I have used it before. Not nearly as easy to abuse as goto statements.
Java needed labelled loops because it deliberately did not include goto, so I don’t really see the argument. If you have goto then you don’t need labelled loops (this is a much less bad argument than ‘if you have goto you don’t need any other control flow’ as using labelled loops and using goto are similar) but it might be scary to have all the freedom of it.
My point was that there is later evidence in language implementation that labeled loops aren't that dangerous.
> it's performing the exact same function as a goto--so why not use a goto?

The 'break' keyword does the exact same function as a goto, why not use a goto? The 'continue' keyword does the exact same function as a goto, why not use a goto?

... Actually, let's play the game even further. The 'else' keyword does the exact same function as a goto. So do 'while' and 'for' and 'do'/'while'. The only things you actually need are 'if' and 'goto', everything else is merely syntactic sugar on top of that.

But it turns out that syntactic sugar does have value. The break and continue keywords are nothing more than gotos that are restricted to breaking out of the innermost loop or continuing to the next loop iteration, respectively, and so you can understand what they do without having to track down the label (or indeed, come up with a name for the label!). It's more mystifying to me that some languages don't extend the syntax to refer to an arbitrary loop in the current loop nest.

In fact, it turns out that labeled break and continue are sufficient to allow you form nearly every possible control-flow graph... and the kind that it doesn't is the one I don't want you to be able to form, speaking as a writer of compiler optimizations.

Not sure what your point is. C doesn't have labelled loops, so a goto signals intent better than the weird exitloop=1 thing in the article. This isn't to say C shouldn't have labelled loops; I think they're better than gotos, it's just that the C committee will likely never add them one of C's philosophies (goals?) is to limit syntactic sugar.
> The 'break' keyword does the exact same function as a goto, why not use a goto? The 'continue' keyword does the exact same function as a goto, why not use a goto?

A break from a named loop is one statement and a label. A goto is one statement and a label. You don’t lose structure by putting the label at the end of the loop instead of the beginning.

Unnamed break is incredibly common, so having a built-in shorthand is quite ergonomic; it also lets you avoid defining a label, which named break doesn’t do. The argument for continue is similar, but continue is less common and maybe doesn’t justify its keyword status. else, for, and do-while all take blocks, so they have the opportunity to encode structure in a way that a break from a named loop doesn’t.

On the contrary, these are, in my opinion, excellent examples, although not a comprehensive list, of where Fortran shines compared to other languages that do not have such advantages or new languages that desperately try to copy, mimic, and rebrand the decades-long existing features of Fortran under their names.
One of the first bits of FORTRAN mischief I learned (on a HP1000 A900) was because values are passed to subroutines by reference, the subroutine could alter an argument and the alteration would persist back to the parent(s). This could even be done to constants! Because the HP macro (microcode) architecture did not have immediate arguments, all loads are from initialized memory. Thus if you pass a constant (such as "1") to a subroutine that alters its value, all subsequent use of "1" in the program would no longer be 1.
The conventional, idiomatic style is to mark the `intent` of your function/subroutine arguments. Then the compiler can check that you don't attempt to mutate input variables, or read the output variables.

    pure subroutine mean_and_std(vec, mean, std)
        real, intent(in)  :: vec(:)
        real, intent(out) :: mean, std
        mean = sum(vec) / size(vec)
        std  = sqrt(sum((vec - mean)**2) / size(vec))
    end subroutine
Thanks! Apparently the "intent" attribute was added in FORTRAN 2003. This was about 20 years too late for the A900.

http://www.hpmuseum.net/display_item.php?hw=594

> Apparently the "intent" attribute was added in FORTRAN 2003.

It was added in Fortran 90.

Either way, it was too late for me. The A900 FORTRAN compiler did "Extended FORTRAN 77" and the "card" at the top of each source file was:

FTN7X

Fortran (and previously FORTRAN) passes by value-return, not by reference, even if Unix f77 and VAX Fortran implemented it that way.

If you use f77(1) in the form of f2c, which I don't have to hand, I changing the value of 1 won't work these days (at least with GCC). It will give a SEGV, because you can expect the C compiler to put constants in read-only storage.

Could you please explain the difference between passing by reference and "value-return"? My understanding is that one can pass by value or by reference. I do not think there is a third alternative. (There are variations on the two in different languages, but I believe there are still only two.) Passing by value creates a copy of the argument and passes the copy. The subroutine/function can modify the copy, but this has no effect on the original argument. Passing by reference is essentially passing a pointer to the object being passed, so if the subroutine/function modifies the object being pointed to, the original value is modified. The latter is what FORTRAN does.

MMUs with protected memory, CDS, and no-execute flags are all relatively new concepts that appeared after FORTRAN was created. It's nice that f2c/gcc fixes some of these problems.

Argument passing is, or was, defined in the "argument association" section of the standard, which obviously doesn't preclude implementing by reference. I don't know whether the same language was in the '66 standard. It isn't anything to do with protected memory as far as I know (which is early '60s); I don't know what CDS is. The point of f2c was just to show that what happens with the invalid code depends on the backend code generation with essentially the same frontend.

Regarding different sorts of argument passing, ALGOL 60 had call-by-value, -by-reference, and -by-name.

> <strong>loop1</strong>: do i = 1, 10

Those "strong" tags are, obviously, a mistake in the webpage html code, and not part of Fortran syntax.

Thanks for the clarification. I really thought Fortran wanted to borrow from XML there haha
"strong" loops - for when you want the CPU to really focus on this loop.
> In C-like languages exiting from a deeply nested loop isn’t exactly trivial (without the use of goto, so don’t even go there).

What's wrong with goto to do exactly what it's supposed to do?

You can of course wrap loop content into functions, and maybe the compiler will "out-optimize" that, but you will never get the performance of loops in a single function, nor you can conditionally jump from middle of one loop into another.

>> for numerical computation there is likely no faster language

What does the author mean by this? How can you get faster than any other compiled language like C where you can check the generated assembly? Does the Fortran compiler do anything special that others don't?

Fortran forbids aliasing. This gave Fortran compilers an edge before the introduction of restrict with C99.

Not sure if there still exist semantic differences affecting optimizability to this day - perhaps things related to automatic vectorization?

One big issue is that C doesn't have guard rails against overflows within an object. For example, if you have an array `int a[3][3];`, `a[0][4]` is a perfectly legal way to refer to `a[1][0]`, because it's all within the same object.
If I'm not mistaken, C has weaker guarantees over which pointers can point to what memory. It means that it has to dump the variables into memory more often to make sure that pointers dereferencing is correct.
(comment deleted)
With GCC (now the GNU Compiler Collection) for instance, it's the same compiler for C and Fortran, just different front ends. The statement isn't useful, anyway, as it doesn't say compiled Fortran will be faster. Given the complex type, and restrict in current C, the main feature of Fortran that might help is arrays.
You might consider that backwards; John Levine, comp.compilers moderator, referred to C as a Fortran-like language.
It looks like C-like language in this context is literally C. Even C++ has some of the features listed here, like array slicing. If you consider Rust "C-like", it has solutions for all the mentioned issues.
Stopped reading after the breaks.

You can break to labels. Even in Java. It’s like goto

A better design would be to just return from a function

C and C++ do not have labeled breaks - Java indeed has.

But of course you are right, that one could just use goto for C/C++.

It’s amazing how much effort has gone into making C compilers achieve the kind of performance that was routine for FORTRAN. The reason, ironically, is that FORTRAN didn’t suffer from the aliasing ambiguity that C does.