It’s All About Interfaces

The Frank system — see also this recent post — is intended to reduce the amount of code needed to create a usable desktop software stack by about 1000x. I’m pretty sure that this goal is sufficient but not necessary. In other words, if we can reduce software size to 0.1% of its current size then that’s great and a lot of current software problems will be far less severe. However, the 1000x reduction is probably unnecessarily difficult — there may be easier ways to achieve similar benefits.

To support my claim that “1000x reduction in software size” is too strong of a goal, consider this question: Would a reasonable person object to running Frank on a modern Intel processor containing more than two billion transistors? In other words, does the gigantic complexity of one of these chips somehow detract from Frank’s simplicity? Of course not. The x86-64 ISA forms a fairly leak-proof abstraction boundary between Frank and all those transistors.[1. Is the ISA exported by a modern Intel chip really leak-proof? Pretty much so as far as the functional semantics goes. However, the abstraction is incredibly leaky with respect to performance — but this only matters for real-time systems and performance-sensitive codes — neither of which is in Frank’s target domain.] I believe the same argument can be made about other interfaces. Let’s say that we have a compiler or an operating system that we trust, perhaps because it has been proved correct. In this case, does it really matter how much code implements the proved-correct interface? I don’t think so.

What we need, then, are more interfaces that are:

durable

non-leaky

beautiful (or, at least, extremely workable)

A durable interface lasts. For example, the UNIX system call interface is reasonably durable — it is often the case that a statically linked UNIX executable will run on many years’ worth of systems, whereas a dynamically linked program rots fairly quickly. x86 has proved to be highly durable.

A non-leaky interface reveals little or nothing about the interface’s implementation. Trivially, a non-leaky interface requires some sort of language-based or OS-based memory isolation — otherwise peeks and pokes (and their unintentional equivalents) cause leaks. Non-leaky interfaces require a lot of error-checking code to ensure that each side adheres to its part of the contract. Performance leaks, alas, are nearly impossible to avoid, unless we give up on implementing optimizations. Bugs are perhaps the ultimate abstraction leak, forcing evil workarounds in code using the interface. Formal verification and automatically-inserted contract checks constitute partial solutions.

Beautiful interfaces do not expose too much or too little functionality, nor are they more complex than they need to be. I’m not sure that I’ve seen a lot of specific examples of beautiful interfaces lately, but at a slightly more abstract level I would consider regular expressions, database transactions, stream sockets, SMT queries, RISC instruction sets, functional programming languages, and hierarchical filesystems to all have a certain amount of beauty. A lot of work and a lot of iteration is required to create a beautiful interface — so this conflicts somewhat with durability.

The worthwhile research problem, then, is to create interfaces having these properties in order that the code living in between interface layers can be developed, understood, debugged, and maintained in a truly modular fashion. To some extent this is a pipe dream since the “non-leaky” requirement requires both correctness and performance opacity: both extremely hard problems. Another problem with this idea — from the research point of view — is that a grant proposal “to create durable, non-leaky, beautiful interfaces” is completely untenable, nor is it even clear that most of this kind of work belongs in academia. On the other hand, it seems clear that we don’t want to just admit defeat either. If we disregard the people who are purely chasing performance and those who are purely chasing correctness, a substantial sub-theme in computer systems research can be found where people are chasing beautiful interfaces. There are probably a lot of good examples I could give here, but Matthew Flatt (example) and Eddie Kohler (example) come to mind.

In summary, I’ve tried to argue that creating a really small software stack, as in Frank, is a good goal, but not necessarily the very best one.

15 thoughts on “It’s All About Interfaces”

Would you consider the UNIX shell as an example of an “interface” ? it’s a particularly good example of “something” – a clean set of operators that work well together, a rather high degree of insulation from the low level, and so on.

I should also add that from a theory/algorithms perspective, a good interface is very valuable, because it gives us the level of abstraction we might need to reason about a system without worrying that our abstraction ignores important performance issues that would make it useless as a predictor.

In fact, what we are facing in the big-data/many-machines world is the lack of good interfaces: we have many of them to play with, but each seems to hide important aspects of the underlying system that can be used to subvert performance predictions.

Summary: “To print floating point numbers Go proudly uses a ‘simple’ algorithm at least 15x slower than glibc.” So if you need to print a lot of fp numbers you should probably use another language.

The implementation simplicity of a language’s fp-decimal converter seems irrelevant. Its correctness is relevant, its performance is relevant, and its interface simplicity is relevant. But given two correct implementations of the same interface, one simple and slow and one complex and fast, the simple, slow converter is strictly worse (except for code size). While it is often assumed that simplicity strongly correlates with correctness, these properties are somewhat orthogonal. I think I’d trust glibc’s implementation over Go’s for a couple more years.

Of course implementers should pick their battles; maybe no one will ever need fast fp conversion in Go. It is fun to reinvent wheels! And by reinventing wheels you can find new ideas. But often implementation simplicity just shunts a problem onto your users.

“Clean slate” approaches—in networks, operating systems, languages—assume that complexity is the result of some original sin, or design mistake, that can be rooted out. For Viewpoints Research Institute the original sin was not using Smalltalk. For exokernels it was restricting application behavior. For RISC, wordy ISAs. For many new network architectures, perhaps it was the TCP robustness principle? But complexity also comes from interacting with complex requirements in the real world. And the relative proportions of sin-complexity and requirement-complexity vary by sin and sinner.

Eddie, maybe in addition to “performance people” and “correctness people” I should have listed “people who will grind the same axe for 35 years.” I noticed that Tanenbaum’s “Linux is obsolete” post was almost exactly 20 years ago — no doubt he still thinks so. No doubt it still (irrelevantly) is obsolete.

I’m not a wheel reinventer, like many OS and PL people are. Sometimes I feel like this is a flaw, sometimes not. Are you? Maybe just sometimes?

Suresh, are you mainly referring to pipes and redirection? These are great abstractions but I’m not sure they’ve aged well (though of course most of us use them many times per day). Plan9 seemed like a nice refresh of those ideas, too bad it didn’t take.

The x86-64 ISA is certainly a fairly leak-proof boundary — but I suspect it’s not exactly cheap to make it so. If the average software API had the same level of architectural agonising about design decisions and enormous quantity of testing and validation applied to its implementations I’m sure it would be much less leaky too.

I’d also argue that the requirement that we deal in the software layer with the kind of concurrency bugs you only get in multicore systems is a significant leak through the abstraction layer of fundamental limitations at the chip design/transistor level.

It only matters how much code implements the proved-correct interface if a change in the requirements forces a change to the interface or its implementation. A large amount of code makes change prohibitively expensive. It is not clear that we have yet discovered The One True Way to design a set of OS system calls, for example—consider the hoop-through-jumping required to get asynchronous I/O on a Unix-style system to see what I mean—and so in a lot of ways putting a lot of effort into designing and implementing durable interfaces is premature. Frank-like systems seem likely to be more agile than larger codebases, permitting cheap exploration of the design space.

Shorter me: I question the value of durability when we don’t even know what we really want yet.

John, I’m occasionally a wheel reinventor. Click comes with its own version of (a subset of) the C++ STL. 😐 (When it was written, real STL implementations depended too much on features unavailable in the kernel.) But it always makes me feel kind of guilty!

@Eddie Kohler ” For RISC, wordy ISAs”
Remember that during some time, the number of transistors used to decode CISC was a real issue and slowed down significantly the CPU: x86s were much slower than RISCs, even if x86s used more transistors..
SW compatibility with Windows proved more important than raw performance and nobody cares about the number of transistors in the decoder anymore (except for their power usage), still you cannot say that RISC was only about “wordy ISA”, it was about better performances.

Sometimes things get simpler when we look at the stack of layers as a whole. Each the layers was built up incrementally upon the layers below, and they usually co-evolve from simpler forms, with more rapid evolution happening in higher layers. But after a sufficient end-state of the higher layers (those closest to the application) has been reached, it sometimes yields surprising simplicity gains to collapse adjacent layers together and redesign them together. Lower layers are necessarily more general-purpose than the layers above, and sometimes that generality is unwarranted, and the vestigial parts can be removed.

I think as computer scientists and architectures we should be courageous enough to throw out all of our lower level software and rebuild it from scratch at least once a decade, reusing the knowledge from the previous iteration. If the abstractions from the previous iteration are worthwhile they will reemerge. If they aren’t, they will die.

/axe-grind
And I think each iteration should also strive for a better language in which to be expressed. 40 years of C/C++ is about 25 too long, in my opinion.
/axe-grind-off

Great post. Seems like there are two kinds of durability for interfaces though. On the one hand, there are interfaces like ASCII that replaced a bunch of other things, were clearly superior and simpler, and made the world a better place for a long time.

Then there are interfaces like x86 ISA and Win32 and GCC that are durable, but out of necessity—too much on top needs to keep running—and so heroic engineering is done to keep them viable. These things are ripe for replacement, but the trick is figuring out when, and getting sufficient mass/compatibility to make it stick. A good recent example is LLVM which has taken on the GCC interface.

I feel you are misinterpreting the VPRI initiative a bit. Indeed, their stated goal is to reduce the total size of code in a system. But what’s interesting is what they’re doing in practice: designing small, hopefully clean and powerful abstractions to capture the different problem domains of personal computing. So the approach they have to size reduction is exactly the approach you advertise for system robustness, and in practice I expect both initial motivations to provide related results.

I read this post with great interest, software interface design being a passion of mine. I completely agree that getting the interfaces right is the key to building large-scale software systems.

The current situation is not very good. I only realized just how bad some interfaces can be after I left academia and started working in the software industry. I haven’t seen truly bad interfaces, at least not interfaces used by hundreds of thousands of programmers, until I worked with some “enterprise systems”.

Large scale systems are built by necessity using interfaces at a much higher level of abstraction than the operating system. If these interfaces were built at least with as much care as those of the OS, this, in itself, would be a huge improvement. While I’m sure new knowledge will continue to emerge, I’m convinced that we can achieve big improvements by simply spreading and applying the knowledge we already have.