Why are low-level languages fast and high-level languages slow?

New programmers quickly come across the debate of low-level versus high-level programming languages. Proponents of low-level languages claim that they benefit from fine-grained control of memory and minimal overheads. High-level language advocates argue that their code is free of messy implementation details and is more expressive.

Why is this? What it is about high-level languages that hinders application performance? And why do low-level languages lack many sophisticated features? To understand the difference, you need to understand instruction-sets and computing machines.

At its simplest, a computer is something with a state that can be altered by different commands. An abacus is a sort of computer: its state is the position of the beads, and the commands are the valid ways of moving them.

A program is a reusable series of these commands. Of course, each command must be one that the machine recognizes. These valid commands are called the instruction-set of the machine — if you have heard people talking about the difference between RISC and ARM, this is what they are referring to. Every machine has an instruction-set; it can be thought of as the native language for that machine.

We don’t have to write programs in a machine’s instruction-set directly. Using a compiler, code in one programming language can be translated into another. To execute a program on a machine, we just translate it into that machine’s native language and execute the translation.

In C, we have the concept of functions: named blocks of code that can be run from elsewhere in the program. However, if you look at the x86 instruction-set you will see that “functions” are nowhere to be found! This is because a C compiler can map the concept of functions in C to instructions for x86.

An analogy with natural languages is helpful here. In English, we might have the phrase “Hello!”; in French, the equivalent is “Bonjour!”. So if we had a compiler from English to French, it could replace every instance of “Hello” with “Bonjour”.

Anyone who has learned to speak a second language will know that concepts in one language do not always map to another. “Ciao” is an informal Italian word for “Hello” and “Goodbye”, depending on the context. If we were writing an Italian to English compiler, then we couldn’t naively replace every “Ciao” with “Hello” or “Goodbye”!

The same is true of programming languages, and this is where the distinction between high-level and low-level languages comes in. In a low-level language, every concept in the language — variables, functions, classes — can be mapped to equivalent instructions in the target machine’s language. In high-level languages, this is not possible.

So how does a real machine execute a high-level program? As is often the case with Computer Science, the solution is abstraction. Instead of running a high-level language directly on a machine, it is run on a virtual machine that is hosted by the target machine. A virtual machine is just a program that takes an instruction in one language and translates it on-the-fly to instructions understandable by its host machine.

Remember that in a low-level language every concept can be mapped to instructions for the target machine, so the whole translation can happen at ahead-of-time. Conversely, a high-level language does not have such a mapping, so this must happen dynamically. This is liberating for the programming language designer. Unlike a real machine, a virtual machine can have any instruction-set we would like — it might have classes, first-class functions or unlimited registers. It allows the designer to go beyond the fixed design chosen for the host machine.

Virtual machines give us the flexibility to create new concepts that surpass what can be expressed in a real machine’s instruction set, but this comes at a cost. With a virtual machine, performing an instruction is a two-stage process of translating code to the host machine’s language and then executing those instructions. This is why high-level languages are slower, but more expressive, than low-level languages.