[HN Gopher] What Fortran does better than C-like languages
       ___________________________________________________________________
        
       What Fortran does better than C-like languages
        
       Author : Bostonian
       Score  : 59 points
       Date   : 2022-02-12 16:54 UTC (6 hours ago)
        
 (HTM) web link (craftofcoding.wordpress.com)
 (TXT) w3m dump (craftofcoding.wordpress.com)
        
       | jleyank wrote:
       | 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.
        
         | pjmlp wrote:
         | 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.
        
           | gnufx wrote:
           | Well, Fortran relies on you obeying the storage association
           | rules that people regularly used to assure me didn't exist.
        
           | cudder wrote:
           | Why is fortran so much more efficient (or easier to
           | optimize)?
        
             | pjmlp wrote:
             | 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.
        
               | gnufx wrote:
               | 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.
        
           | adgjlsfhk1 wrote:
           | 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)
        
             | sampo wrote:
             | > 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.)
        
             | pjmlp wrote:
             | Eventually, note that you should compare it against latest
             | ISO Fortran standard per commercial compilers, not what
             | gfortran is capable of.
        
             | klodolph wrote:
             | 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.
        
               | adgjlsfhk1 wrote:
               | 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.
        
               | pjmlp wrote:
               | So like Fortran generics.
        
               | adgjlsfhk1 wrote:
               | Not really, Fortran is inching it's way towards generics,
               | but doesn't actually have them in a usable form yet.
        
               | pjmlp wrote:
               | Parameterized derived types seem good enough for what
               | Fortran is used for.
        
               | adgjlsfhk1 wrote:
               | 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?
        
               | gnufx wrote:
               | 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?
        
       | anonymousiam wrote:
       | 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.
        
         | sampo wrote:
         | 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
        
           | anonymousiam wrote:
           | 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
        
             | sampo wrote:
             | > Apparently the "intent" attribute was added in FORTRAN
             | 2003.
             | 
             | It was added in Fortran 90.
        
         | gnufx wrote:
         | 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.
        
       | sampo wrote:
       | > <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.
        
         | kangalioo wrote:
         | Thanks for the clarification. I really thought Fortran wanted
         | to borrow from XML there haha
        
       | gnufx wrote:
       | You might consider that backwards; John Levine, comp.compilers
       | moderator, referred to C as a Fortran-like language.
        
       | legerdemain wrote:
       | 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.
        
       | hpcjoe wrote:
       | 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/
        
         | adgjlsfhk1 wrote:
         | 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.
        
       | eterevsky wrote:
       | 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.
        
       | jbverschoor wrote:
       | 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
        
         | sischoel wrote:
         | 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++.
        
       | Aisen8010 wrote:
       | The example 'exiting a nested loop' was weird. C has support to
       | 'goto', C's version would look like the Fortran's code.
        
         | asddubs wrote:
         | well, you'd label the end of the loop rather than the
         | beginning, which is mildly messier, but yeah
        
       | nmilo wrote:
       | 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?
        
         | jcranmer wrote:
         | > 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.
        
         | taeric wrote:
         | Java also has labeled loops. Such that I have used it before.
         | Not nearly as easy to abuse as goto statements.
        
           | dan-robertson wrote:
           | 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.
        
             | taeric wrote:
             | My point was that there is later evidence in language
             | implementation that labeled loops aren't that dangerous.
        
         | tehjoker wrote:
         | 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.
        
           | grumpyprole wrote:
           | 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.
        
           | disgruntledphd2 wrote:
           | > 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.
        
           | opportune wrote:
           | 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.
        
           | gnufx wrote:
           | I don't remember if it was a rejoinder to Dijkstra, but Knuth
           | wrote "Structured Programming with go to Statements".
        
             | Jtsummers wrote:
             | 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.
        
           | 10000truths wrote:
           | 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.
        
             | fpoling wrote:
             | 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.
        
               | MaxBarraclough wrote:
               | > 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.
        
               | spc476 wrote:
               | 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.
        
             | lmkg wrote:
             | 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.
        
         | asddubs wrote:
         | yeah I was expecting stuff like not allowing aliasing, which
         | allows the compiler to optimize functions better
        
         | nusaru wrote:
         | 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.
        
           | rightbyte wrote:
           | 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.
        
             | scambier wrote:
             | 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.
        
               | _0ffh wrote:
               | 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.
        
               | cyberpunk wrote:
               | cybrpnk:~/src/linux$ grep -rniE 'goto\W+.*;' . | grep -E
               | '\.[c|h]:' | wc -l           182044
               | 
               | Sure.. I mean.. 'almost'
        
               | dan-robertson wrote:
               | 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.
        
             | Jtsummers wrote:
             | 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.
        
               | rightbyte wrote:
               | 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
        
               | Jtsummers wrote:
               | 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.
        
       | baybal2 wrote:
       | > 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.
        
       | lentil_soup wrote:
       | >> 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?
        
         | cygx wrote:
         | 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?
        
           | jcranmer wrote:
           | 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.
        
         | eterevsky wrote:
         | 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.
        
         | [deleted]
        
         | gnufx wrote:
         | 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.
        
       ___________________________________________________________________
       (page generated 2022-02-12 23:01 UTC)