Writing performant and highly readable cross-platform code is challenging.
In the world of programming languages, the more freedom that programming languages permit to the programmer, generally speaking, the more challenging it can be for the compiler to work with. When it comes to loose and flexible languages like Python, we've all head, and maybe even felt ourselves (if you've ever needed to do any compute intensive task with it) how grudgingly slow it can be. As an "untyped" language (using that phrase loosely here), it's hard for the computer to generate optimal machine code as it just can't make good assumptions about how the code is intended to run when compared to a statically typed language. If we take the following function call below for example:
function add (a, b) {
return a + b
}
We have a function, add, that takes two parameters, a and b, and then uses the plus operator on the two parameters and returns the result. If we think like a computer, then ultimately everything is just a bunch of numbers. Strings (text) are represented as a sequence of numbers in a text encoding like ASCII or UTF-8. The length of a string is either encoded in a separate region of memory typically immediately preceding the sequence of numbers that comprise the string, or otherwise some form of null termination is used. With null termination, when reading a string at an address in memory, you keep reading until you get a 0 (null terminator) character, and the length is computed on the fly—you only know it after you've iterated through memory and hit a null terminator. Now, as it relates to our add function, the information that we pass to add could be anything: it could be a number of some sorts (some integer or decimal), or it could be a number that holds an address that points to a region in memory.
When it comes to passing non-integer data to functions, things are a bit tricky, and to fully understand why so, we'll have to discuss in high level a bit how functions are implemented in computers at an elementary level. As you may know, compiled computer code is just a bunch of instructions for the CPU to execute. The program is able to jump around these instructions based on their address or location in order to create a living, executable program. When it comes to functions, the way they're implemented is by adding a "label" to the starting address of a sequence of instructions, that other code can jump to.
Walk around and look for rooms with their lights on
Walk to room with the light on
Find the light switch
Toggle the light switch
Move onto the next room
Walk around and look for rooms with their lights on
For a particular room number, n
Find the light switch
Leave the room
When strings or other objects are passed to functions in programming, you may notice that most of the time, objects and strings
Languages that are loose and flexible like Python tend to suffer from performance issues, namely because of the level of "dynamism" that is allowed. Notably, lack of type information means that the compiler cannot make strong assumptions about the code, and therefore optimizations are much more challenging for the compiler. There are trade-offs to be made in languages that are easier for the humans writing the program, and languages that are easier for the compiler to optimize and work with. As Python offers flexibility and ease of use, despite the performance issues, its use is ubiquitous in the industry and it remains at the top of the programming language popularity charts. Python developers though, haven't exactly sacrificed performance for ease of use. Instead, they've opted to use a hybrid approach, where they write the performance critical parts of their code in C, and then expose APIs that standard Python code can use to call into the performant C code. This is a common pattern in the industry, and these libs are known as "C extensions" for Python. While this does work to mitigate a lot of the performance issues, it requires a lot of boilerplate/glue code to be written. Additionally, it requires performance demanding developers to be proficient in *both* C, Python and the CPython engine that implements the Python language.
The world of computer science, spanning software and hardware engineering, is very broad. The specific demands and desires of software isn't universal, and heavily depends on the industry we're looking at. For example, software companies focusing on products for consumers may be more interested in rapidly iterating on their product, and therefore may be more interested in a language that is easy to use and allows for rapid prototyping. In industries like these, time is money and doing things quickly may be more important than doing things efficiently.
On the other hand, software companies focusing on embedded systems may be more interested in a language that is performant and allows for low-level control of the hardware. Where as a web or mobile developer may be focused on agility and minimizing engineering hours, embedded engineers may be more focused on correctness and being methodical. For example, software in transportation systems, like cars, must be correct and reliable. A bug in a web application may be annoying, but a bug in a car's software could be fatal.
For quite some time, we've relegated languages that feel "high level" in nature, with high amounts of syntactic sugar, multi-paradigm features, closures, terse syntax and non-explicit typing to the realm of "slow". Sitting at the top of layers and layers of abstractions like virtual machines, interpreters, sandboxes and fancy runtimes, remain perched languages like Java, C#, JavaScript and Python. Where as "low level" languages like C and C++, languages that give you direct memory access, we consider to be "fast".
This of course, is a common misconception that many new and even some experienced programmers have. The line between a "high" and "low" level languages is entirely arbitrary and a matter of personal perspective. Languages don't become fast or slow based on access to lower level features of the hardware like memory. Languages also aren't relegated to being either "compiled" or "interpreted". In fact, any programming language could be interpreted, compiled or a mix of both. C++ can functionally be an interpreted language much like Python could be a compiled one, as long as someone is willing to write a compiler or interpreter for it. There is no physical or mathematical restriction for this, so it begs the question: why aren't there compilers and interpreters for every language? The answer is as simple as it sounds, as no one has written one yet.
When JavaScript was first created, it was an interpreted scripting language designed for the web to make dynamic web pages on the client side. It features lots of quirks, misfeatures and oddities that make it a challenging language to work with. However, being the language of the web, and having a simple looking and terse syntax, it naturally became popular. As the web grew, so did JavaScript (there was no choice!). In this process, it became a language that was used for more than just making dynamic web pages. It became a language that was used to make web servers, mobile apps, desktop apps, games, and even embedded systems. Now, as an interpreted language, performance is evidently going to suffer as code has to be read character by character, and then executed after passing many layers of abstractions. Much like standard CPython, JavaScript was pretty slow. Accordingly with the rising importance of the web came the rising importance of JavaScript performance. It's not a great experience if your websites with JavaScript run slow, and so the industry responded by making JavaScript faster. Google Chrome's V8 engine marked a paradigm shift for JavaScript, as it was the first engine to rework traditional implementations of the language. This was done by introducing just-in-time (JIT) compilation, which allows the JavaScript engine (or any software) to compile JavaScript code (or any code) into machine code at runtime. This is a technique that is used by many other languages, notably including Java and C#.
Not all platforms allow the technology behind JIT compilation. As allowing JIT compilation is synonymous with the program being able to define certain regions of memory as executable, when combined with a sandbox escape bug, JIT compilation can potentially lead to system exploitation. Therefore, on some platforms like iOS, due to the ability to execute arbitrary unvetted machine code, JIT compilation is not allowed.