The conventional wisdom shared by many of today's software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise-and-pound-foolish programmers, who can't debug or maintain their "optimized" programs. In established engineering disciplines a 12% improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering.
Donald Knuth. Structured Programming with go to Statements. (It's the same paper that the oft-misquoted "premature optimization" quote comes from.) http://www.cs.sjsu.edu/~mak/CS185C/KnuthStructuredProgrammin...
That's right, but "improvement" doesn't necessarily mean performance. Adding a GC would yield more than 12% reduction in vulnerabilities and bug count, which are also "improvements".
Also when compared to Rust-style lifetimes? (Did Redox OS have any CVEs yet?)
Every decade, hardware performance skyrockets upward, but developers adopt libraries/frameworks/languages/low standards/habits which give rise to woefully inefficient software. It's so atrocious that it seems to roughly counterbalance the hardware advances. Heavyweight single-page web applications seem to be the endgame of this trend, for now.
See Wirth's law: What Intel giveth, Microsoft taketh away.
That said, the 80-20 rule is a real thing. It's possible that you'll get better real performance by writing 80% of your code in Python and the performance-critical 20% in carefully optimised C, than by spending the same amount of time writing it all in Java.
The authors would probably not call it "unoptimized". From the last paragraph in Section 8.8:
"It took effort to make Biscuit achieve this level of performance. Most of the work was in understanding why Linux was more efficient than Biscuit, and then implementing similar optimizations in Biscuit. These optimizations had little to do with the choice of language, but were for the most part standard kernel optimizations (e.g., avoiding lock contention, avoiding TLB flushes, using better data structures, adding caches)."
I agree that it's impressive, and better than I would have guessed.
Firstly, I would like to know if that's an average of 5-15% slowdown, and if that means some operations are multiple slower and some are close to equivalent. Average means nothing if your workload triggers the pathological cases.
Secondly, you might be giving up classes of security vulnerabilities and gaining classes of kernel DOS bugs. I don't write kernels, but my understanding (mostly from comments of people here that do), is that kernels rarely allocate or de-allocate after book, as they immediately reserve the memory needed for their limits and then work within those limits.
Believe me, I'm not a proponent of C. I'm critical whenever it comes up, as I think it's caused a huge amount of harm. That said, of the places where C has a well deserved reputation of being the right tool, I think kernel development is one of them. That isn't to say other languages don't have something to offer, but this is one area where you actually seem to need all the crazy unfettered access that C gives you.
So, I'm not sure C is the right tool today given anything it can do can be done in safer languages plus assembly or sometimes even invoking/extracting-to C. In the latter case, the C part becomes incidental to where history went with who wrote or optimized what in what language. The person developing uses as little of C as necessary in that case. Software can also be incrementally ported to something safer, more maintainable, and so on.
> People are also writing low-level, efficient software in Ada/SPARK, Rust, Oberon (Astrobe Oberon), Pascal/BASIC (MikroElektronika), Haskell-based DSL's (eg Ivory Language), and ATS.
I fully think other languages can be used to quite good effect. I was specifically thinking of Rust and Ada/SPARK, and for the latter one coming to my mind so quickly you can only blame yourself. ;)
> So, I'm not sure C is the right tool today given anything it can do can be done in safer languages plus assembly or sometimes even invoking/extracting-to C.
Yeah, I'm not so much advocating for C as a good choice, as much as excusing it as a somewhat adequate choice given the specific circumstances. A rigorous set of usage guidelines, which I think are likely easier to implement and stick too given the type of project, mean that C is only slightly more dangerous, but also slightly easier to work in given the needs, than some other options. Even this small advantage is quickly being eroded by advances in other languages though, so I doubt it will persist (even if only in my mind) much longer.
This would turn into a massive research project on GC and language runtimes where the commodity server market already is and especially where it is going considering a dual socket AMD Rome will have 256 threads and a single socket POWER10 would have the same. To consider that project in terms of time scale and cost, one can look at the history of JVMs. Go's type system might make some things slightly easier but there is no implicit scalability codex they've unlocked beyond state of the art JVMs that have been evolving for 15 or more years.
This is why Rust brings something novel to the table for systems programming. With the borrow checker, memory safety can be added in many cases without runtime overhead and follow on contention.
An extended though, we need to be moving _toward_ sympathy with the on chip network and implicit message passing nature of cache coherency policy. This is mostly a library/ecosystem shift rather than language.
That said, nobody’s disputing that a “lot” of people care about performance. It’s worth taking a look at the last slide of the presentation, which states clearly that if you care about performance, you should use C:
Performance is paramount ⇒ use C (up to 15%)
Minimize memory use ⇒ use C (↓ mem. budget, ↑ GC cost)
Safety is paramount ⇒ use HLL (40 CVEs stopped)
Performance merely important ⇒ use HLL (pay 15%, memory)Now, could one of these academic kernels have sheer engineering effort put into optimizing that 5-15% regression away? Probably.
We constantly pay this cost for other security-related issues, but when it comes to a systemic, one-time penalty that almost entirely eliminates a class of bug we freeze up.
Obviously it's a lot of engineering effort to transition but we don't think twice about other security issues
In general, these types of decisions aren't uncommon.
So saying "just don't use GC" as a answer to improving throughput shows ignorance.
So what are the real tradeoffs?
GC is less work. Is easier to get right. And lets you handle complex self-referential data structures more easily.
Reference counting lets you handle real-time constraints more easily. Is simpler to understand. And results in faster freeing of potentially scarce external resources.
The basis on which the system works is easy to understand.
It could also mean they use Rust, memory pools, some kind of checker that catches those errors, or separation logic. Probably that order due to popularity.
As a counterpoint to "don't use GC," there's also low-latency or real-time implementations of RC/GC to consider as well. A lot of developers using RC/GC languages don't know they exist. Maybe we need a killer app that uses it to spread awareness far and wide. More open implementations of the concept, too.
These exist but tend to have poor throughput, frequently set a maximum amount of memory you're allowed to use, and typically don't perform well when compared to commonly used garbage collectors.
We would be further down this path if bigger companies really cared to push down this path no matter what, but politics and short term profits always win in the end.
There is therefore a trade off and obviously the lower level the code the less suited high level languages are.
Thanks to Internet and IoT it seems that we might be finally at the turning point where OS security starts to be taken seriously, so lets see how long that resistance keeps to uphold.
It was refreshing to see that a large set of Linux Security Summit 2018 talks were all about how to fix C related issues on the Linux kernel.
The Checked C project is working on such a bounds-checked dialect of C: https://www.microsoft.com/en-us/research/project/checked-c/
Since nothing in the C language prohibits bounds-checking for arrays, we can retrofit this to existing insecure languages rather than forcing all programmers to rewrite their software in some other language, which realistically won't happen for a decade or longer.
I don't know if things would be better now with more optimizing compilers, but I remember being somewhat surprised by this at the time, since you'd expect most of the overhead from passing fat pointers around to get optimized away.
There are many other examples of people trying to band aid and improve C's security history.
The biggest problem is that the developers that are supposed to actually adopt those practices don't really care, because performance trumps security.
Those that actually care end up moving to C++, or some other language, so we end up with this selection bias.
Thankfully they are being forced to change, as being 100% connected to the Internet does wonders to security.
Secure by default with option to turn those features off for performance, has been the way other systems programming languages have been designed since 1961.
That C culture is now being forced to change, because fixing memory corruption CVEs in devices that are permanently under security exploits attacks does not scale.
If you don't want to wait on the standards committee, the SaferCPlusPlus library provides memory-safe substitutes for commonly used unsafe C/C++ elements (including pointers). These memory-safe elements require a C++ compiler, but because they are largely compatible with their native counterparts, you can write programs that can switch between using memory-safe (C++) elements and (unsafe) straight C elements with a compile-time directive.
That is, you don't have to choose between safety and performance when authoring the code. You can make the choice at build-time. So you could, for example, provide both "memory-safe" and "non-memory-safe" builds of your software. There was even a (now long neglected) tool[1] to automatically replace arrays/buffers in C source code with (optionally) memory-safe substitutes. (Not just bounds-checked, but safe from use-after-free.)
> I know I'd trade 10% perf for the elimination of whole classes of security vulnerabilities.
Full memory safety may sometimes cost a bit more than that [2]. But a nice thing about this approach is that you can choose to use (faster) unsafe code in critical inner loops, and memory-safe code everywhere else.
[1] Shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...
[2] https://github.com/duneroadrunner/SaferCPlusPlus-BenchmarksG...
In theory, it's possible to write bug-free code in C or assembly. In practice, it just doesn't happen. The rule of "worse is better" ensures that the rare projects that put in the necessary effort to manually eliminate the bugs that HLLs catch automatically will get steamrolled by projects that don't (witness the adoption rates of Linux versus OpenBSD).
That said: I think that tool developers have only scratched the surface of the possibilities for compile time checks. I'm always amazed how little the industry has learned from projects like Ada. There's an opportunity in there somewhere to have that cake and eat it too.
Ironic how its descendant didn't managed to replicate that feature.
Also Burroughs B5000, developed in 1961, already took security as major OS feature.
The problem is that UNIX designers never took security as serious as other OS designers were doing on their OS implementations.
Had UNIX been a commercial OS sold at the same price levels as VMS, OS/360 and others, instead of given to universities with a symbolic price due to Bell Labs commercial restrictions and history would have been quite different.
Lots of asterisks and caveats apply I'm sure, but it bolsters the argument that writing kernels and device drivers in a high-level safe language wouldn't impose the 2x or worse perf penalty detractors like to claim. I know I'd trade 10% perf for the elimination of whole classes of security vulnerabilities.
(For the same reason, I want the standards committee to produce a bounds-checked dialect of C where I can choose to pay some perf cost to get real dynamic and bounds-checked arrays)