4 ms·
Something about this constantly appearing trope bugs me. I began programming C and assembler on the VAX and the original PC. At that time, C was a reasonable a
by joe_the_user 7y ago
Something about this constantly appearing trope bugs me.
I began programming C and assembler on the VAX and the original PC. At that time, C was a reasonable approximation of the assembly code level. We didn't get into expanding C to assembly that much but the translation was reasonably clear.
As far as I know, what's changed that mid-80s world and now is that a number of levels below ordinary assembler have been added. These naturally are somewhat confusing but they aim to emulate the C/assembler model that existed way back then. These levels involve memory protection, task switching, caches and all things involved with having the current zillion-element Intel CPU behave approximately like the 16-register CPU of yore but much-much faster.
I get the "there's more on heaven and earth than your flat memory model, Horatio" (apologies to Shakespeare).
BUT, I still don't see any of that making these "Your Ceeee ain't low-level no more sucker" headlines enlightening. A clearer way to say it would "now the plumbing is much more complicated and even c programmers have to think about it".
Because... adding levels below C and conventional assembler still leaves C exactly as many levels below "high level" language as it was before and if there's a "true low level language" for today I'd like to hear about it. And the same sorts of programmers use C as when it was a low level language and the declaration doesn't even give any context, doesn't even bother to say "anymore" and yeah, I'm sick of it.
Edit: plus this particular actual article is primarily a rant about processor design with C just pulled into the fight as a stand-in for how people normally program and modern processors treat that.
- defanor 7y agoThe article blames C for the processor designs that emulate older (non-parallel) processors. I think it can be summarised as C having a relatively straightforward translation into hardware-friendly assembly in the past, but these days both major CPUs and compilers are working hard to preserve the same model, so that neither the assembly is hardware-friendly nor efficient/optimizing C compilers are straightforward.
- joe_the_user 7y agoBut "C has a bad effect" is a lot different from "C is not low level". Maybe C had a bad effect because it's low level and they should have been emulating the lisp model instead - for all I know. Edit: I seems like it's really the flat memory model with single processing that CPUs and compiler-designers have been trying to preserve and that's not C-specific. Indeed, I think higher level languages are effectively more wedded to that. I mention in another reply - Nvidia GPUs, complex memory model, programmed with Cuda, a C++ system. It really seems like C as such only enters here as something to beat-up on to make other points (whatever the validity of the other points).
- defanor 7y agoThere are different definitions of "low level language", but I think a charitable interpretation of the one in this article is that they view processors as providing a virtual machine, and count "levels" from what's more efficient for hardware (that is, the bits below that virtual machine). Though maybe a somewhat strange/confusing title was picked to attract attention.
- joe_the_user 7y agoOK, following that, in the case of the GPU, C++ code is translated into SPX "assembler" but SPX is very much a macro system and the programmer doesn't get access to the true low level. And I don't think we'll escape the situation that there will low-level emulation code that programmers can't and should not access. It's good to know but that doesn't change the levels programmers normally work with.
- gpderetta 7y ago>Maybe C had a bad effect because it's low level and they should have been emulating the lisp model instead - for all I know. Of course lisp machines were a thing. They went out of fashion when more conventional architectures could run lisp code faster than their dedicated machines.
- tybit 7y agoThe authors point was that it’s hard to separate discussion of modern CPU design from the constraints of C. Not from a technical perspective but from a pragmatic/commercial one. The take away for me was that while C is obviously a higher level abstraction than CPUs, it’s a mistake to think that C has been designed for that hardware, when nowadays it’s the other way around.
- joe_the_user 7y agoBut even if the hardware is designed for C instead of the other way around, getting away from C is going to be hard, indeed, if the hardware is designed for C, it kind of makes C the lowest level you can count on. I'm just saying that the authors group together a variety of claims under "C is not low level" but while the claims themselves might be reasonable, they don't support the base point.
- saagarjha 7y agoI found it insightful, because it goes on to discuss how C has had a huge effect on the programming model all the way from the processor to the compiler and it’s led to problems in performance and security. They suggest that it may be worthwhile designing or using a language that can better handle how processors are structured today.
- joe_the_user 7y ago...how processors are structured today But it admits that processors today are structured to look like processors of the mid-80s (even though they definitely aren't). Today's processors have lower levels than but those lower aren't intended to be accessed by ordinary programs using any language. Maybe that's a bad thing but we've migrated to a very different argument here. Maybe, they're saying we could have processors which wouldn't emulate the PDP-11. Sure, maybe we could. Like say, Nvdia GPUs programmed using the cuda system, which is based on ... C++, which also "isn't a low level language". I mean GPU definitely have emerged as alternate way to use the bizillion transistors of modern chips but broadly, I don't see this as invalidating C (or c++) as a next-step-up-from-assembly level of programming. I mean, the reality is C actually is used in all sorts of complicated memory spaces and chips, often mixed with assembler. IE, the "not low level" claim still is kind of a troll imo.
- pjmlp 7y agoWhile CUDA is designed to make C++ run optimally well (several CppCon talks about it), they are also designed to run any language with a PTX backend well, including C, Fortran, Java, Haskell and .NET.
- msla 7y ago> I began programming C and assembler on the VAX and the original PC. At that time, C was a reasonable approximation of the assembly code level. We didn't get into expanding C to assembly that much but the translation was reasonably clear. Right: On the VAX, there wasn't much else for a compiler to do other than the simple, straightforwards thing, and I'm including optimizations like common subexpression elimination, dead code pruning, and constant folding as straightforwards. Maybe loop unrolling and rejuggling arithmetic to make better use of a pipeline, if the compiler was that smart. > As far as I know, what's changed that mid-80s world and now is that a number of levels below ordinary assembler have been added. You make good points about caches and memory protection being invisible to C, but they're invisible to application programmers, too, most of the time, and the VAX had those things as well. Another thing that's changed is that chips have grown application-visible capabilities which C can't model. gcc transforms certain string operations into SIMD code, which vectorizes it and turns a loop into a few fast opcodes. You can't tell a C compiler to do that portably without relying on another standard. C didn't even get official, portable support for atomics until C11. You can dance with the compiler, and insert code sequences and functions and hope the optimizer gets the hint and does the magic, but that's contrary to the spirit of a language like C, which was a fairly thin layer over assembly back in the heyday of scalar machines. I don't know any modern language which fills that role for modern scalar/vector hybrid designs.
- kccqzy 7y agoSIMD design itself isn't constant between different processor families. Any purported standardized language for scalar/vector hybrid either has to rely on a smart optimizer or be utterly platform specific.
- msla 7y ago> SIMD design itself isn't constant between different processor families. Any purported standardized language for scalar/vector hybrid either has to rely on a smart optimizer or be utterly platform specific. That is indeed part of the problem. There might be enough lowest-common-denominator there to standardize, like there is with atomics, I don't know, but I'm not saying that C needs to add SIMD support. I'm saying that any low-level language needs to directly expose machine functionality, which includes some SIMD stuff on some classes of processor. Maybe there will be a shakeout, like how scalar processors largely shook out to being byte-addressable machines with flat address spaces and pointers one word size large, as opposed to word-addressable systems with two pointers to a machine word (the PDP-10 family) or segmented systems, like lots of systems plus the redoubtable IBM PC. C can definitely run on those "odd" systems, which weren't so odd when C was first being standardized, but char array access definitely gets more efficient when the machine can access a char in one opcode. (You could have a char the same size as an int. It's standards-conformant. But it doesn't help your compiler compile code intended for other systems.) C could standardize SIMD access once that happens. However, it would be nice to have a semi-portable high-level assembly which targets all 'sane' architectures and is close to the hardware.
- snaky 7y ago> if there's a "true low level language" for today I'd like to hear about it "Steel Bank Common Lisp: because sometimes C abstracts away too much" https://www.pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembly-code-breadboard/ https://www.pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembl... ATS - C plus everything you could get from modern types, including type-safe pointer arithmetics https://www.youtube.com/watch?v=zt0OQb1DBko https://www.youtube.com/watch?v=zt0OQb1DBko Compiling Haskell to hardware http://conal.net/blog/posts/haskell-to-hardware-via-cccs http://conal.net/blog/posts/haskell-to-hardware-via-cccs
- ori_b 7y agoI don't see how these get past the limitations of assembly (and C) language mentioned in the post above. None of those links seem to indicate anything about exposing the cache hierarchy or out of order execution to developers.
- Gibbon1 7y agoI think you touched close to an opinion of mine which is anything lower lever than c becomes very hard for ordinary people to work in. I remember DSP's in the 80 and 90's. Where to program them you needed to deeply understand how the machine worked. And guess what eventually manufacturers ported C over to them so that programmers could be productive when working on the non-performant parts of the code base. If anything modern processors are even worse under the hood. With the added problem that you can't feed one of them raw 'true' instructions fast enough to keep them from stalling.
- lmm 7y ago> Because... adding levels below C and conventional assembler still leaves C exactly as many levels below "high level" language as it was before and if there's a "true low level language" for today I'd like to hear about it. And the same sorts of programmers use C as when it was a low level language and the declaration doesn't even give any context, doesn't even bother to say "anymore" and yeah, I'm sick of it. Not really. For many purposes, C is not any more low-level than a supposedly "higher level" language. 20 years ago one could argue that it made sense to choose C over Java for high-performance code because C exposed the low-level performance characteristics that you cared about. More concretely, you could be confident that a small change to C code would not result in a program with radically different performance characteristics, in a way that you couldn't be for Java. Today that's not true: when writing high-performance C code you have to be very aware of, say, cache line aliasing, or whether a given piece of code is vectorisable, even though these things are completely invisible in your code and a seemingly insignificant change can make all the difference. So to a large extent writing high-performance C code today is the same kind of programming experience (heavily dependent on empirical profiling, actively counterintuitive in a lot of areas) as writing high-performance Java, and choosing to write a program with extreme performance requirements in C rather than Java because it's easier to control performance in C is likely to be the wrong tradeoff.
- biggestdecision 7y agoJava forces you to use profiling, at least with C you can see the exact instructions your compiler outputs. Missing the fancy vector instructions? Modify your code til you can guarantee it's vectorized. With Java you are at the mercy of the JVM to do the right thing at runtime.
- lmm 7y agoThe instructions no longer tell the whole story though. Maybe you can tell whether your code is vectorised, but you can't tell whether your data is coming out of main memory or L1 cache, and to a first approximation that's the only thing that matters for your program's performance.
- kjeetgill 7y ago
- philwelch 7y ago> Because... adding levels below C and conventional assembler still leaves C exactly as many levels below "high level" language as it was before and if there's a "true low level language" for today I'd like to hear about it. Me too, actually. I know you meant that rhetorically, but what if you designed an instruction set that better matched modern processor designs and then built a low-level compiled language on top of it? My hunch is that modern processor designs are so complicated that you’d have to do similar amounts of abstraction to make the language usable, but I’m not sure.
- benibela 7y agoThe optimizers are much stronger nowadays. They rewrite the program, so that the resulting assembly might have nothing to do with the code you wrote. Especially if undefined behavior is invalid. Decades ago you did not need to care about undefined behavior. You write a + b, and you know the compiler emits an ADD instruction for +, and that ADD of x86 does not distinguish between signed and unsigned instructions, and you get the same result for signed and unsigned numbers, regardless of overflow. But nowadays the optimizer comes, says, wait, signed overflow is undefined, I will optimize the entire function away.
- mpweiher 7y ago> still leaves C exactly as many levels below "high level" language as it was before And the levels below assembly are inaccessible. Assembly/machine code is the lowest level you that's accessible.