Grigory Petrov from Evrone has a couple of fascinating talks on YouTube about programming languages. He had learned a lot about programming languages design and how compilers work under the microscope and he has found answers to questions about programming languages performance.
In order to describe how programming languages work we must begin with the CPU on a machine. Modern CPU has plenty amount of cores for its performance. Modern CPU architecture is challenging to learn and describe. In simple terms, we can say that the code execution speed is the amount of machine code instructions that one CPU core can exec by one moment.
CPU reads each instruction from memory. Read operation is always slow operation. Therefore each modern CPU hash multi-level cache (L1, L2, Ln cache) and processor registers. They help not to read data from memory.
In simple terms, code speed performance is equal to how effective our code (machine code) works with memory. Can we store data in CPU cache or do we have to read data from memory?
What programming languages as C, C++, Rust, Objective-C and Golang have in common? When a programmer writes code he always thinks about memory: we must specify the data type of each variable; we have to allocate memory in heap; always think about pointers, blocks; etc.
As an advantage, the source code will be compiled to machine code and it’s executed so fast. Nevertheless, everyone knows that write code in C, C++, Rust is challenging. It happens because the syntax is tricky and you have to always care about memory.
If a programmer/developer doesn’t want to worry about the memory he can delegate the routine to a compiler. A compiler is a tool that transforms your source code into machine code. it tries to do it effectively. Source code converted to machine code will be easily handled by the CPU and data will be stored in cache and registers
The third way is to delegate routine to runtime, virtual machine (VM). It means that the language will not compile source code to machine code, it will compile the source code to bytecode and execute the bytecode in VM. Programming languages as Python, Ruby, PHP follow the way. These languages have awesome high-level and sweet syntax and the ability to write an extension easy and fast. But the price of using a virtual machine is speed - code performance is slow.
We can imagine each computer language as a character in a video role-playing game (RPG). In a typical RPG when we create a character we have a limited amount of points and few skills to put points into (strength, defence, intelligence, agility etc). Each language has three skills to put points into:
- Speed - speed of execution
- Syntax - enjoyable and elegant high-level syntax
- Extensibility - memory compatibility in order to easy write and use third-party code or an extension.
When a person, group of people start to design a new programming language they can choose only two skills as the base. A third one will always be hard to achieve. For example, C, C++, Rust, Objective-C, Go are fast and have extensibility, but they have low-level inelegant syntax.
Ruby, Python and PHP have chosen the third way. They have awesome sweet syntax, the code is easy to write. When you are writing the code you think only about business logic and don’t worry about memory. Also, it’s easy to use third-party code or write an extension as NumPy, SciPy in Python; or Nokogiri, Ruby-openCV, Redcarpet in Ruby. The price is performance these languages are slower than previous languages.
Whenever we say or hear that N language is slow remember that it was a meaningful decision in language design. You can ask a question: “How to became N language fast? Is it possible?”. Yes, it is possible but not easy to achieve.
Otherwise slow languages are flexible. It’s easy to write an extension in C++ and make some type of operation faster. For example NumPy and SciPy in Python. When we do ML in Python or use Ruby in order to write business login we use advantages of Python/Ruby in a high-level language and write the code fast. Also, we can delegate slow operation to swift extensions in third-party libraries in order to work with data, do math operations, parse text, etc.