C is not a low level language (any more)
Pages: 1 2
GavinWraith (26) 1563 posts |
Fascinating article on why C can no longer be thought of as a low-level language. Developments in CPUs and GPUs should force us to rethink many hoary assumptions about programming. |
Rick Murray (539) 13806 posts |
Using that definition, one might consider BASIC to be lower level than C. Anybody who disagrees, please write a Wimp program. ;-) I’m not sure that one can lay blame on C. For instance the misconception of the flat memory model. This is typically assumed, and those of us of a certain age will remember the horribleness that was the segmented memory models (plural) of the x86. But, when you think about it, flat memory has never really been true. Any version of Windows worth mentioning used virtual memory so the actual allocations are an illusion, and even in the world of RISC OS, our memory mapping is a lot simpler but one thing is for certain, every application is not based at &8000. It’s only there when it needs to be.
This would probably hold true for any number of contemporary languages. One needs only read a bit of StackOverflow to see that. There may be recognised qualifications required in order to call oneself a software engineer, but there’s nothing required in order to call oneself a programmer (hell, I learned from the Beeb user guide, AcornUser, and hacking stuff to see what broke – nobody ever taught me C, or assembler, I read stuff, examined stuff, took stuff apart…all of which probably makes me the very worst sort of programmer – but then I started with BASIC so I’m already damned…). Yes, C compilers jump though hoops to make a fairly uncomplicated language generate fast code, but then again don’t you think modern frameworks with tens of megabytes of runtime support libraries aren’t jumping through their own hoops? The problem is nothing to do with the use of any particular language, even if C’s typically linear behaviour (akin to BASIC) is not exactly best suited for multicore processors. The thing is, language doesn’t matter. Why? Because a modern processor isn’t going to be running one C (or otherwise) program, it’ll be running hundreds. And switching around them hundreds or thousands of times per second. This is an operating system issue, not a language issue. PS – why’s this an Announcement? PPS – I never thought of C as “low level” or “readable assembler”. It was just closer to the machine architecture than languages that try to hide function calls, that don’t give you a choice of variable types (signed/unsigned), or worse the “whatever it needs to be” type of variable. |
nemo (145) 2529 posts |
I can’t disagree with any of that. -O anything means what you wrote is not what gets run, and the continued preservation of frankly ridiculous undefined behaviour (signed overflow, uninitialised values, cross-type casting) means what you think you wrote means is not necessarily what some compilers will give you. |
nemo (145) 2529 posts |
No, it is. The C language programmer’s model fits the silicon less and less well each generation. Some other languages fit much better. |
John Sandgrounder (1650) 574 posts |
Not as difficult as it used to be. In most cases I would argue that it is no longer necessary (with today’s processor speeds) to write windows one rectangle at a time. |
Rick Murray (539) 13806 posts |
That is a problem, certainly. Given the number of buffer overruns we’ve seen do nasty things in the past, the continued inability to optionally invoke bounds checking is also ridiculous.
I would like to believe that a language translates something readable into a usefully compact form that may be executed by the system; with less and less dependence upon what the actual silicon is doing. Because these days we cannot dare to assume such things, what with multicore processors, fake multicore processors (i Atom), dual issue, out of order execution, and of course, the fact that in most cases you’re one of many, not exclusive.
Today, perhaps. They’ll get old and outdated too. How many “modern” languages can you name? How many result in actual executable code (as opposed to interpreted script or bytecode)? How many do you think will still be around in five years? That’s not to say that C is the best, but it has huge support and longevity (even C++ failed to kill it off). I can’t, offhand, think of any other language that has the scope and support that C offers. It’s available for everything from microcontrollers to today’s processing monsters – and that alone is why it being “close to silicon” is a bit nonsense, there’s practically nothing in common between a Intel Core i9-7980XE (go eyeball the specs) and…a PIC. Yet, with some obvious allowances for hardware differences, it is possible to compile C code for each, and pretty much everything in between. I still maintain that C is not especially at fault here. The processors had one job to do, to execute code. Whether single thread C, or assembler written by a monkey, it was just expected to follow instructions. In the craze for squeezing of more and more speed, some corners were cut. Now that’s turning out to have some unwanted side effects.
Depends whether you do it yourself or use somebody’s pre-written library. The latter option is, obviously, the logical choice; but even so there’s some nasty mucking around with memory blocks to make it all work. Not at opcode level, at BASIC level. You just don’t see it when using a library…
Indeed. My Pi2 runs an easy hundred times faster than my original A3000 (faster yet due to cache and architectural enhancements), the Ti is something like twice that. So why should we be drawing windows a rectangle at a time? We have enough processing power that we ought to be able to manage circles and parallelograms too. :-) More seriously, processing speed aside, we have enough memory that I do wonder if it would be useful to draw into a sprite (instead of the window) and have the Wimp handle most of the redraws for itself? Perhaps blitting a part of a sprite would be quicker than expecting the application to work out what needs to be redrawn from an arbitrary rectangle created as a result of a menu or some other window appearing in front of the window that we have drawn into. |
John Sandgrounder (1650) 574 posts |
Or a compromise. use somebody’s existing code as a starting point. |
Tristan M. (2946) 1039 posts |
A compositing window manager would make a nice option. Implementation would probably make some nice additions to z ordering support. What languages are more suitable? I’m curious. Maybe Go, or the relative safety of Rust? |
Jeffrey Lee (213) 6048 posts |
This has been discussed before, and may have even existed at one point. https://www.riscosopen.org/forum/forums/2/topics/2541
Depends on what you’re after, as they both have their pros and cons when compared to other languages, including C. E.g. they both offer very little control over memory allocation strategy (or failure modes when allocation fails). Fine for application code where you can mostly assume you have gigabytes of uniform RAM and gigabytes of pagefile, not so great for OS kernels or hardware drivers. Also despite Rust aiming to be as safe as possible, they still haven’t solved this stupid and dangerous bug. |
Rick Murray (539) 13806 posts |
Steve said, at the time:
This is not necessarily a bad plan, because it seems to me that the fundamental behaviour of the Wimp itself is going to have to change. For a start, consider the idea of the Recorded Message. You send the message, and you receive back either a reply or your message back (essentially an ACK or a NAK). Well, the moment the Wimp gains the ability to disperse tasks among cores (as it surely should to make best use of the hardware) is the moment that one ceases to be able to assume that each task will be run in rotation. It is quite possible that the Wimp will maintain a list of pending tasks, and the next one to be ready will be assigned to the next free core. However, and here’s the important part, we cannot assume in one polling cycle that each interested task has in fact been executed. Imagine if one of the tasks was ChangeFSI (known for stalling the machine for a long time while it works its magic). If tht was sat hogging one core, the rest of the Wimp would cycle thousands of times in the meantime. Questions like that (and all of the other wishlist items) raise the question of whether it is better to patch the existing code, or to design something new with the features available from the outset? Of course, as always, we’re right back to the issue of developer time and resources…
There’s a certain irony in the LLVM backend being written in C++. ;-)
Wow. I like the discussion regarding “accuracy” and whether to saturate. A big float is not going to fit into a little integer, so it doesn’t really matter what is stored. Wrong is wrong. I can understand saturating, but it seems like their implementation added a huge overhead (even in values within range). Perhaps one should just AND mask it? With this in mind, I tried a program to see what RISC OS’ C does: Here’s some code:
Running it: 954408050.000000, 43690, 7282, 21845 The value 7282 is 954408050 ANDed with &FFFF. The code? A bit jumbled up, but essentially:
Does it flag an overflow? No, it does not. But then, I as the programmer, have asked the compiler to take a potentially large value (20 bits of fraction, 11 bits of exponent) and stuff it into an integer value capable of storing only values from 0 to 65535. Technically the behaviour is “implementation defined”, but it’s been a while since I’ve seen a compiler that didn’t just throw away the data too large to fit… |
Rick Murray (539) 13806 posts |
Of course, one can arrive at code better suited to the Pi’s ARMv7 processor by using the
This is, clearly, when the compiler decides to troll the programmer. |
Andrew Conroy (370) 725 posts |
Am I the only one who keeps reading that as “A composting window manager…”? |
nemo (145) 2529 posts |
Let’s not have that discussion again. Just like Windows, the entire Wimp desktop protocol must be single threaded. Otherwise if you have two running programs that can load text files then when you double-click one you have no idea which program will load it… and if you double-click again it might open in the other too. I’ll say it again and hopefully for the last time: Task messaging/polling must be single threaded. |
GavinWraith (26) 1563 posts |
An important requirement of any programming language is that it should enable the programmer to have an adequate mental picture of its operational semantics. What adequate means obviously depends on what the programmer is trying to achieve, and to what extent any given language satisfies this is up for debate. But if our hardware is now so complex that only a handful of people understand it, then we are in trouble. The classic SICP book makes great play of the notion that programming languages are not just for instructing machines; they are also for describing algorithms to humans. Functional programming has been around since LISP, but after Backus’s Can programming be liberated from the von Neumann style? in 1978, their advantages for multiprogramming have been recognised. There seems to have been a wide gap between academia and commerce about this (with some honorable exceptions). Maybe the prevalence of mult-core processors will force a greater rapprochement? |
nemo (145) 2529 posts |
These two statements are orthogonal – the problem with C is that its semantics are weirdly compromised by… well I was going to say early hardware, but it was actually executive decision by Dennis Ritchie. As long as the people who wrote your compiler understand the hardware, the only need for the author to understand it too is a degree of optimisation or ‘mechanical sympathy’. But when the compiler (for an appropriate language) is free to decide whether to use arrays of structures or groups of arrays (or a combination as appropriate) then the degree of mechanical sympathy required by the author is massively reduced. By mechanical sympathy I mean awareness of things like cache lines, cache sizes and behaviour, structure packing etc This is a good read on undefined behaviour. |
Steve Pampling (1551) 8155 posts |
I’m being dim aren’t I? I thought that was down to the definition of the appropriate variable:
|
Jeffrey Lee (213) 6048 posts |
I believe there’s a Wimp message which gets sent around, prior to the system falling back to the environment variables. So if you have multiple running apps which are capable of responding to that message (e.g. Edit and StrongED), and a multi-threaded Wimp decided to deliver the messages in a non-stable order, you could easily get different behaviour each time you tried opening a file. |
Steve Pampling (1551) 8155 posts |
Observed behaviour is that where a text editing app has startup config to set the variable the last one to load, and therefore point things their way, loads the file. |
Rick Murray (539) 13806 posts |
How does RISC OS do it? Offer the file to every interested app (from the beginning) until one (usually the first loaded) accepts it? Why does this require messaging to be single threaded?
“The system maintains a single system message queue and one thread-specific message queue for each GUI thread.” That said, Windows works differently to that. Like with RISC OS, files are “associated” with an application. I’m running Notepad, I double click a text file, Notepad++ loads it, as that’s what’s been told to deal with text files. On RISC OS, it’s more interesting. Load Zap. Load StrongEd. Load a text file (it’ll open in Zap). Quit Zap and StrongEd. Double click on a text file again. What loads it? [hint, it isn’t Zap] Indeed, on my system in Apps I have Edit, StrongEd, and Zap. Zap is the default by virtue of being the last one “seen” by the filer and hence the last one to mess with the RunType values.
I’m guessing that this is due to the age of C and the fact that such things didn’t exist back then?
That’s easy enough to mostly fix. There will, by necessity, be a global list of tasks, even if they’re running at different speeds on different cores. Simply offer the file to each in turn as their poll turn arrives, skipping over blocked/busy tasks. |
nemo (145) 2529 posts |
Steve partially remembered:
Some programs that ask others to edit a “file” use a very similar protocol by Jason Williams called the External Edit Protocol, which I think Rick continued to Not Get It
The protocols upon which the familiar RISC OS Desktop experience are built require a single-threaded message polling system. If one had filetype registration, it would be possible to reduce this to subsets for particular messages, but the principle stands. There are three classes of Wimp messages:
The negotiations (of which DataLoad/DataOpen is but one example of many) must be delivered in a static order as Jeffrey has pointed out. Notifications could be randomised, but the Wimp would have to keep a record of every busy task still to see a particular message before the sender could continue. Direct messages require strict serialisation between the parties… which may not be limited to two. Adding knowledge of the type of each kind of message to the WindowManager, and the complex handling necessary for the various types, all to achieve the perceived advantage of allowing Wimp programs to spend time ‘busy’ in their main thread, achieves nothing. To quote the old joke, “If I was going to go there, I wouldn’t start from here ”. |
Rick Murray (539) 13806 posts |
Rick actually did get it, given the scenario you posted pretty much matches what I summed up with my one line…
So how then might this expand to a future scenario where the Wimp can spread tasks around cores in order to make best use of the available hardware? A scenario where different tasks will be running on different cores concurrently, and quite likely at different speeds. Or does the current system pretty much preclude this? |
John Sandgrounder (1650) 574 posts |
Southport Sailing Club is using RISCOS to score the Junior 12 Hour Race which is held on the last Saturday in June each year. Each boat carries a small GPS Tracker which reports its position to a RISCOS Server every 30 seconds (and more frequently at the end of each lap). The GPS server then reports each completed lap to the race scoring program (also RISCOS). Results are calculated live and updates sent to the Race Scoring website (Raspbian Apache) every minute. The GPS server and race scoring are written in BBC Basic. The results of the 2017 race and the corresponding GPS Tracks for each boat on every lap can be seen by following the 12 Hour Race – Race Results Site link on the Southport Sailing Club website. The scoring for all but the first 3 of the linked previous 30 races was also done with same RISCOS program. GPS Tracking was introduced in 2017 (after a few years of experimentation). All of the software and website is on Raspberry Pi. |
nemo (145) 2529 posts |
The short answer is: You can’t spread tasks around different cores, but you can spread parts of tasks around different cores. There are many analogies – Web Workers in JavaScript, Pool & Map in Python, Channels in Rust – but we already have a suitable RISC OS model, at least at the low level – The Tube (or Hydra I suppose). Writing multithreaded code (using pthreads for example) is hard (in general), because it is very easy to do the wrong thing – it’s just a length of rope, after all. The multithreaded infrastructure I have created was based on message handling and entity lifetime management in order to insulate programmers from the low-level stuff. Exactly the same idea as the language models I just listed. They all, in effect, allow ‘work’ to be declared in an abstracted form, which the OS/Scheduler can then choose how best to implement and distribute – whether serially on a single core, or concurrently on a proportion of the available cores, or through a thread-pool system on hyperthreaded cores. Those ‘workers’ (to use the JavaScript terminology) have much greater restrictions on their use of ‘the’ OS… and this is why I cite The Tube as the obvious parochial model. The task is the ‘host’, the worker(s) are the ‘Tube(s)’. Communication is down a ‘channel’ (to use the Rust terminology) – this is precisely what “The Tube” refers to – and is buffered so that asynchronous worker messages are queued for the ‘host’ (the foreground task). Anyway, the details aren’t important, but the scale of the parallelism is – it should be ‘bits of work’ that are distributed, not ‘Tasks’. |
Steve Pampling (1551) 8155 posts |
So if the WIMP is a task of the basic OS then you can spread bits around? |
Jeffrey Lee (213) 6048 posts |
The short answer is: You can’t spread tasks around different cores, but you can spread parts of tasks around different cores. “You can’t spread tasks around different cores” sounds like you’re saying that only one task should be running at once, which sounds a bit barmy to me. I’m assuming this was just a poor choice of words and the second statement is the more accurate one? |
Pages: 1 2