[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)