“Runtime” and “Runtime Environment” are some of the most overloaded terms in software development. It’s confusing for everyone; this word means many different things in many different contexts. This post’s goal is to provide you with an intuition behind the many use-cases of “runtime.”
Word Overload
Let’s enumerate the common use-cases of “runtime.”
Runtime — The Lifecycle (Noun)
The first meaning of runtime is with regards to program lifecycle. This refers to the period of time in which a program is executing. A intuitive comparison is often made between compile time and runtime.
The IDE is spitting out a lot of new warnings during compile time.
This process is outputting a lot of logs during runtime.
Runtime — During The Lifecycle (Adjective)
In the same context of program lifecycle, runtime is also commonly used as an adjective. This is sometimes hyphenated as “run-time.” The O’Reilly Style Guide recommends using “runtime” for both nouns and adjectives.
This program seems to throw a lot of runtime errors.
What kind of runtime metrics do you want to capture for this application?
One gotcha is that while O’Reilly recommends “runtime” for both adjectives and nouns, they recommend “compile-time” as an adjective and “compile time” as a noun.
We should probably fix some of these compile-time warnings.
If you prefer to catch errors at compile time rather than at runtime, Typescript might be a good choice.
Run time — How Long Did It Take?
“Run time” as two words refers to the raw execution time of a program. This usage doesn’t appear as often as the others.
Q: Did you measure the run time of the data cleanup script last week?
A: Yea—it took about 3 hours.
To avoid runtime overload confusion, simply asking “How long did the script take to execute?” is more clear.
Runtime Environment
Armed with an intuition of runtime as a lifecycle, let’s discuss runtime environments. These two concepts are related, but subtlety different. Confusion is created because people abridge “runtime environment” to just “runtime.”
You have to be careful that your Javascript program doesn’t have any runtime errors when it’s running in the Node.js runtime [environment].
Q: Did you see the release notes for the new JRE?
A: Yea, there’s a lot of fancy stuff going on in the new Java Runtime [Environment].
In these examples, the standalone “runtime” refers to a “runtime environment.” In these situations, it’s better to be precise with your language.
The new Java Runtime Environment seems to be doing a better job with garbage collection.
Is there a way to control how many threads handle the I/O within the Node.js runtime environment?
Culinary Intuition
After developing a craving for Chinese Yu Xiang Eggplant, you decide to hop on Google to look for a recipe. After sifting through a few search results and scrolling past a few walls of ads, you find a clean step-by-step recipe that seems like a winner.
Unfortunately, a recipe on its own is useless. The recipe you found on Google isn’t actually going to get you your eggplant—you need a kitchen! The eggplant recipe’s runtime environment is your kitchen.
Your code is just code. Whatever code you write, in whatever language you choose, needs to eventually execute on a computer. Runtime environments enable this execution.
Everyone’s Runtime Environment — The Operating System
The universal runtime environment for any kind of programmatic execution is the operating system. The operating system is the only way you can get the CPU to execute your code. The OS is the silent hero that ensures your program gets some memory, gets scheduled fairly, and doesn’t disturb its neighbors. It doesn’t matter if you’re using C, Python, or Node.js—at the end of the day, the operating system is everyone’s runtime environment.
An Executable’s Runtime Environment
An executable’s runtime environment is the operating system.
Every operating system defines a binary file format for executable code. In Unix-Like operating systems, this is the ELF file format. In addition, operating systems ship with programs that are able to take these files and give them to the hardware to be executed. In Linux, one example is the execve() program. For all other operating systems, there will be a similar set of file formats and loader programs.
A C Program’s Runtime Environment
What does it take to execute a C program on your computer? This is very close to an executable’s runtime environment; all we need to do is to make sure our C source can turn into a proper executable.
This step is familiar to everyone. Compilers like GCC or LLVM take your C code and create a properly formatted ELF file, targeted for your processor’s architecture, waiting to be passed into execve().
A subtler part of C compilers is that they also provide a C runtime library that is automatically compiled into your program. The purpose of these libraries is to provide you—the programmer—with basic facilities to interact with the runtime environment. For a compiled C program as an ELF executable, this environment is the operating system. For example, the C runtime library provides useful functions like malloc() and free() that allow you to manage memory, automatically injects startup routines into your program to help prepare it for execution, and many, many other things.
In Linux, the most common implementation of the C Standard Library is glibc. In Windows, the library is referred to as the C Run-Time Libraries or CRT. These libraries will abide by the C Standard and will also provide additional specialized features for their respective operating systems.
For C developers, your program would be more portable if you decide to only use libraries and functions from libc. You can be more-or-less confident that OS vendors have implemented the C Standard Library well for their own OS.
If you’re writing a C program and you only ever want it to run on Linux, you may use Linux-specific libraries outside the standard that allow you to specifically leverage Linux. If you’re writing a C program and you rely on Microsoft-specifics inside the CRT, then you’ll need to update your code if you ever decide to compile your program with glibc.
The concept of the “C Runtime Environment” isn’t a real concept. A C program’s runtime environment is the universal runtime environment, the operating system. The important point to remember is that all code—from assembly to Javascript—needs some kind of environment in order execute, just like all recipes need some kind of kitchen to turn into food.
Higher-Level Runtime Environments
As programming languages evolved, people wanted an environment that could handle additional tasks that felt cumbersome for developers. Do you really enjoy using malloc() and free() to manage all your dynamic memory? Wouldn’t some kind of automatic reference counting and garbage collection be extremely convenient? Some people say yes and this is how fancier runtime environments like the JRE developed.
Another popular higher-level runtime environment is Node.js. Many developers refer to this as the “Node.js Runtime” or simply just “Node.” Node is a runtime environment for the Javascript language, similar to how the JRE is a runtime environment for the Java language. Node comes with fancy features like a callback queue, an event loop, and a thread pool. Just like the JRE, these bells and whistles exist to make our lives easier as developers.
With higher-level runtime environments, features can become blurred across the language and its environment. Within the Java ecosystem, the automatic garbage collection feature is not technically a feature of the Java language itself, but is actually a feature of the JRE. 95% of developers won’t bother making this distinction. The statement “Java has automatic memory management” can more precisely be stated as, “The Java Runtime Environment has automatic memory management.” As a warning, you’ll get some unpleasant glares if you ever decide to correct someone over these kind of semantics.
Runtime Environment Layers
Runtime environments have their own runtime environments. If you download the Node binary for Linux, you’ll find that it’s just another ELF executable waiting to be run by the OS. So we could say—Javascript’s runtime environment is Node, and Node’s runtime environment is the operating system. Or you could also say—Javascript’s runtime environment is a combination of Node and the operating system. Again, dive deep enough and everyone’s runtime environment ends up being the operating system.
Interpreting Languages
Code does not always have to be compiled to OS/architecture-specific binaries. A common pattern is to execute your programs via interpreters. For example, a Python interpreter can read your Python source and produce corresponding machine instructions for your computer to execute. This conveniently makes Python source very portable. However, the Python interpreter itself is a compiled executable, built for a specific OS/architecture, who’s runtime environment is the operating system.
Conclusion
“Runtime” is one of the most overloaded terms in software development. The two most common contexts are either a program’s execution lifecycle or the environment in which a program executes.
A recipe is only a recipe; source code is only source code. A recipe turns into food via a kitchen; source code moves electricity through metal via a runtime environment.
Runtime environments have their own layers of abstraction. Your Java source gets turned into bytecode and gets interpreted and executed within the JRE. The JRE itself executes via the OS. Everyone’s runtime environment ends up being the operating system.
So the next time your colleague says, “Hey, I was measuring the run time of our program and I noticed we’re getting a lot of runtime errors after we upgraded to the new Node.js runtime“—you’ll know exactly what they mean.
3 comments
This is GOLD. I was confused for almost a week and then I found this. Thank you David! You are a gem
Thank you! Very helpful with great specific examples.
Thank you! This explanation was very helpful!