{ datagubbe }

datagubbe.se » small and efficient

Small and Efficient

Spring 2020

Shortly before the end of 2019 I stumbled across an excerpt from "Modern Operating Systems" by Andrew S. Tanenbaum, a well-known Computer Science professor and the original author of the MINIX operating system.

In it, he writes that "[MULTICS] was designed to support hundreds of users on a machine only slightly more powerful than an Intel 386 (...) This is not quite as crazy as it sounds, since in those days people knew how to write small, efficient programs, a skill that has subsequently been completely lost."

The quote has rattled around in my head since then. It's somewhat tongue-in-cheek, but the sentiment is clear. It wasn't the first time I'd heard something like it, and Tanenbaum isn't the first nor the last to make such a claim - I myself am surely guilty of saying something similar at some point in time. Nevertheless, it stuck with me because it's both factually incorrect and very insulting to a lot of young programmers.

In Tanenbaum's defense, I understand where he's coming from. It's an easy mistake to make, common among us grumpy old gits. Looking at modern software munching memory and disk space by the gigabyte, it's easy to reminisce about simpler times when 640 or even 64 kilobytes of RAM were enough for everyone.

It's also a very common misconception. The sentiment is just as applicable to trades other than programming and likely crops up everywhere with some regularity. Any craft that has developed over time, be it software development, cooking, tailoring, fishing or masonry, will have a group of elders scoffing at new-fangled shortcuts and technological advancements. Such modern folly is made out to be something that sullies the inherent soul of the workmanship and lessens its value. The fact that new skills might have to be learned to master this new technology is seldom taken into account when making such statements.

I'm certainly not advocating progress simply for the sake of progress. Doing so often leads to confusing the two very different concepts of change and improvement, a distinction sadly apparent to some only after it has been demonstrated empirically. It's also always good with a bit of historical knowledge, if nothing else because it's humbling to learn about the giants upon whose shoulders we stand.

The desire for faster computers with more memory, however, is a desire for real improvement. It means that not only can we do more of the same with our machines, but also new things that seemed impossible mere decades ago. This is important, because herein lies the core of my reasoning.

Elder Usability

Today, computers are ubiquitous. Not just as laptops or desktops in homes and offices, but as tablets, smartphones, wristwatches and entertainment centers. In fact, pretty much anything except food can be bought in some computerized form today: vacuum cleaners, light bulbs, mirrors, books, cars - even doorbells. We are at a point where we can have computers do our bidding by speaking to them in everyday phrases and be reasonably well understood.

At the time when MULTICS was conceptually conceived, a computer was, with few exceptions, a great big machine that easily filled a large room. One that had the performance of a 386 chip was truly top of the line, cutting edge stuff. In fact it was so advanced and expensive that MULTICS itself was accused of being a resource hog, which was one of the reasons it never became very popular.

The methods used to get an ancient mainframe like that to perform computations were, put simply, extremely crude. Complex program code had to be written in clunky languages like FORTRAN, stored on massive amounts of punched paper cards and mechanically read into the computer's small and expensive memory. If one was lucky and the program ran without bugs, all you could do was to wait. Even relatively simple tasks that a layperson has been able to solve in the blink of an eye using a spreadsheet program for the last few decades, could take minutes or even hours to carry out - after the computer had been prepped and the program loaded, of course.

That doesn't mean the layperson with the spreadsheet program is a skilled programmer, but it also doesn't mean there are no skilled programmers today. It's simply an example of where increased program complexity also increases the usefulness and usability of a computer. For a layperson to perform such a task without the knowledge of FORTRAN and card punches, such complexity is necessary. This, in turn, increases the overall footprint of software.

MULTICS and (perhaps to a greater extent) its more successful spin-off, UNIX, were certainly wonders of software engineering. They did truly provide multi-user, multi-tasking environments for a number of generic applications to run in and they did so on very limited hardware. At the same time, there was nothing about those systems that someone could get to grips with after goofing around with a mouse and GUI for a few hours. Everything was based on cryptic commands you had to learn by heart, controlled with even more cryptic parameters you also had to learn by heart.

Once learned, those commands could then be used to start a program. Not that you'd really know if it was even running, because most of the programs made no significant difference on the computer's output. Often they just produced a blank screen or an empty line on an teletype and were as cryptic to operate as the commands for starting them. Even a program for performing simple arithmetic could be confusing to a first time user, who would surely want to write "1 + 2" and press enter for a result, but may have had to learn about stacks and RPN and input "1 2 + p" to carry out a basic integer addition.

RPN, Reverse Polish Notation, was a great invention because it reduced memory usage at a time when memory was stupendously expensive - exactly the kind of "small and efficient" code Tanenbaum claims is a lost art. It is also an excellent example of why less small and less efficient programming might produce a more pleasant experience for the end user, who can then focus more on the actual problem at hand than on how to think like the computer used to solve it. Today we expect that even the cheapest of phones come with a program resembling a pocket calculator for performing such calculations, requiring little more prerequisite knowledge than a few grade school math lessons.

Pointing and Clicking

At the time of MULTICS, using computers meant dealing with crude and complex user interfaces, long execution times and results most often presented with simple text printed on paper. The code was small and efficient because the machines offered programmers no other choice, but that code - as brilliant as it was - resulted in a primitive, time consuming and often frustrating user experience. This was what users expected, even if it meant learning RPN for calculations and at least one complex, declarative programming syntax to format even the simplest of documents.

Today, we use graphical interfaces for most things. There are still people, like me, who enjoy the power and flexibility offered by a command line once you've learned how to operate it. For others, such as beginner users or the vast majority of people who simply consider their computer to be any other home appliance, the GUI is much more efficient. There's no need to think like a CPU, to memorize cumbersome commands and keep intricate data structures such as directory trees and concepts like logical operators in your head.

The GUI comes as at a cost: These easy to use interfaces add complexity to program code, requiring more memory and faster processors. Even early graphical interfaces required considerable power and, though instantly recognizable by a modern-day user, they lacked both speed, finesse and luxuries such as color. It has been a long, meandering journey to end up where we are today.

To type a ÔéČuro sign

Take something as ostensibly simple as text rendering. It was a big step up in usability when computers moved from DIP switch panels and blinkenlights to interfacing over teletypes: plain text commands could be entered and yielded plain text results. It was all 7-bit ASCII (or an equivalent) of course, not taking up much space and not requiring a lot of computational resources. The teletype dictated what characters most of the software written should be able to handle, keeping things small and efficient.

Moving forward a bit, teletypes were replaced by CRT terminals. In the beginning, they merely emulated their mechanical predecessors but software that utilized the fixed width and height of a screen and their faster refresh rate soon materialized. Full screen editors, for example, were a step up for the end users, giving them contextual information about the line they were currently editing by displaying the surrounding lines on screen at all times.

Of course, manufacturers wanted to sell their computers and terminals to markets other than English speaking ones, which made 7-bit character space a bit crowded. Adding an extra bit of information doubled the number of characters and, in effect, opened up the central European market in earnest. Things like umlauts, circumflexes and accents are taken for granted today but to handle them, we had to increase memory and bandwidth usage.

Asian languages such as Mandarin and Japanese, using complex signs for whole words, and a whole host of other alphabets such as Farsi and Tagalog, eventually led to the creation of Unicode and UTF, in which a single character now may take up many times the space of one in 7-bit ASCII. This adds a lot of complexity in tools written for handling simple text files. It also adds a lot of complexity in displaying such text. At the time when 7-bit ASCII ruled, a terminal had a fixed font embedded in hardware and there were no other choices. If you were lucky, it could display bold text in some fashion, but apart from that all characters always looked the same and had the same visual size.

Unicode was made possible by the spread of so called bitmapped displays, where a single position on the screen is not represented by a whole character, but by a small dot - a pixel. This increases freedom of character shape and positioning, but adds complexity. For example, to display simple text modes today, such as a terminal window on Linux or a PowerShell prompt on Windows, software must convert the rules of teletype-emulating terminals into something suited for bitmapped displays. This requires computational overhead, but allows the user to switch quickly between such an old-timey interface and a modern web browser without having to switch between different hardware display modes.

Early bitmapped displays were of course a huge step up from character-based ones, but they were still rather limited. The fonts available came with a preset number of sizes, each of them corresponding to a certain amount of pixels on screen. It was fast and easy to render such fonts on a pixel-based display, because their storage format was based on pixels. The problem was that using sizes other than the preset ones was either impossible or led to ugly scaling issues, and printing a pixel-based font on paper inevitably resulted in chunky, blocky output. It was maybe acceptable for high school homework, but hardly for the professional publishing market where smooth, crisp text rendering had been expected since the invention of movable type roughly a thousand years earlier.

This was solved by using fonts based not on pixels, but on mathematical representations of each glyph, describing end points and instructions for curves and lines of varying thickness to be drawn between them. Of course, they must still be mathematically translated into pixels on a bitmap display, which leads to an increase in memory and CPU usage.

Further refinements

To make text on computer screens look even more crisp and smooth, the most obvious way is to increase its pixel density. Today the consumer market offers displays with eight million pixels on them, between 60 and 120 times more than what was common on the first bitmap displays for home computers. To further improve screen readability and reduce eyestrain, various smoothing techniques are employed. One is to cleverly utilize the nature of modern flat screens to produce intermediate color values around the outline of a glyph, eliminating the otherwise sharp edge between foreground and background. This is called sub-pixel smoothing and we expect it to be present on pretty much any system we purchase today, from wristwatches to tablets and laptops. There are even coffee machines with this type of font smoothing on their little touch-screen control panels.

All of this is a massive improvement on text rendering, but of course it increases the complexity of writing program code for displaying text. This complexity might not be considered "small and efficient" compared to 7-bit ASCII on a teletype emulator, but the average computer user of today would hardly consider it unnecessary bloat and would likely complain loudly if it was suddenly removed.

Numerous such improvements stacked upon earlier improvements means that using computers today is, compared to the days of "small and efficient", a very pleasant experience. Years of thought and increased code complexity have resulted in something that's accessible to a lot more people than those who took an interest in the early, primitive machines running systems like MULTICS.

Good and Bad Programmers

Programming itself - in the sense of producing a usable piece of software - has become much easier, too. An abundance of work has already been done and pre-packaged into libraries, ready to import and use for those who want to solve a certain problem without having to write the same code over and over again - or, indeed, even know how to do it. Thus, less knowledge about the inner workings of a computer is needed to accomplish something. In some cases, this can of course lead to sub-optimal code, which itself is nothing new. There has always been good and bad programmers writing good and bad code. Even at the time of MULTICS, essays were written about how to write good code and rules were thought up to combat bad programming habits.

Today there are more programmers around than ever, which of course means there are a lot more bad ones - but also a lot more good ones - than before. Just like before, the good programmers know how to write small and efficient code. I would even argue that writing good code (at least in the sense of Tanenbaum's "small and efficient") today is harder than before, because early computers were so simple that developers could easily hold a map of the whole system in their heads, helping them for example with preserving memory usage.

The increasing complexity of computers and our expectations on them, combined with new delivery platforms for software, requires not only an understanding of computers at a fundamental level but also a lot of additional domain-specific knowledge. Writing good code for the Web means not only knowing about algorithms, big O notation and memory usage, but also about object-oriented and functional programming paradigms, SQL databases, advanced typesetting and layout concepts, problems related to asynchronous code execution and data loading, various networking and transport protocols, security issues pertaining to publicly available multi-user systems accessing sensitive data, and so on. The days of one developer spending weeks on polishing a small batch processor for text files down to a few lines of C are long gone - for a number of reasons - but that doesn't mean they don't still know how to do it.

There are many programmers around who can do all of this and more. People much younger than both MULTICS, from the late 1960s, and even Tanenbaum's own MINIX system from 1987, are writing everything from super small ray tracers in JavaScript, to music sequencers for the original Game Boy, to PIC processor assembly and even multi-tasking operating systems for the Commodore 64, a home computer from 1982. There are several recurring competitions around the world for writing the most beautiful non-interactive graphics and audio programs limited to as little as one kilobyte in .exe size.

Some of those people create such small programs for profit, others do it for fun. To claim that they have no knowledge in writing small and efficient code is not only a lie, but also deeply demeaning to their intelligence, skill and efforts. Perhaps a thoughtless sarcasm on Tanenbaum's account, it's still something that is repeated by a lot of people, many of whom should know better.

Actual Improvement

Were things better before? Have things gotten worse? My old Amiga 1200 - a wonder of smallness and efficiency - came with 2 megabytes of RAM and 120 megabytes of hard drive space, and always seemed to run out of both. I could run a graphics package and programming environment at the same time and almost do something worthwhile in them. If I wanted to connect to the Internet (and do some text-based stuff: graphical browsing was out of the question), I had to close those programs because The TCP/IP stack simply needed too much memory. It wasn't until I tripled the Amiga's memory (which, incidentally, also increased its speed) I could start truly enjoying the multi-tasking capabilities of the OS. Doing so cost more than half of the price of the computer itself.

Looking purely at the rise in absolute program sizes, things surely seem out of proportion. Luckily, the proportions have changed. My home computer, a cheap laptop, cost roughly as much as the Amiga did. Not adjusted for inflation, mind you - the same exact sum of money, except a quarter century later. Being a laptop, a high resolution, high quality screen was included in that price, something I had to add to the cost of the Amiga. This computer has a 256 gigabyte SSD disk, of which I use about 5% for a complete system install (including several browsers, programming languages, office-type software, and graphics and music packages) and my personal files. I have 8 gigabytes of memory, of which I typically never utilize more than 25% when watching high resolution video, browsing the web and editing code, all at the same time and without delay.

I'd say that's considerable improvement, no matter which way you look at it.

Wrapping Things Up

MINIX, Tanenbaum's own brainchild and an operating system meant for educational purposes, started its life as something distributable on a couple of floppy disks. The latest version still has modest hardware requirements by modern standards, but it takes up many hundred times more storage space and requires amounts of RAM equivalent to a decent-sized hard drive at its time of initial release. While its utilization of resources still might be considered impressive, it lacks a lot of the fundamental features we expect from our systems today. Want to use a cheap mouse with your computer, like the $10 models sold in stores today? In that case, I wouldn't recommend MINIX - it has no support for USB.

There is bad software around, for sure. There are several cases when accusations of "bloat" and "waste" are absolutely valid. There are bad programmers producing it, without a doubt. But a lot of what is easily mocked as "bloat" or "inefficiency" is in many cases just a natural result of increased complexity to meet the demands of higher expectations on usefulness and usability. It's true that we don't need sub-pixel smoothing to write an essay, but for those who spend hours staring at text on a screen every day, it's a great feature. For real world application programming, the added resource consumption is in most cases beneficial both to those who write the software and those who use it.

Returning to the "small and efficient" of yesteryear inevitably means removing things we now take for granted. I wonder which ones we should get rid of first?