Is it worth switching from Python to Nim for performance?

Nim is a mix of Python syntax and C performance







A few weeks ago, I was browsing GitHub and came across an interesting repository: the project was written entirely in the Nim language . I hadn’t come across him before, and this time I decided to figure out what kind of animal it was.



At first I thought that I was behind the times, that this is one of the common programming languages ​​that many, unlike me, actively use. And then I decided to study it.



Here are my conclusions:



  • This language is actually popular among a narrow circle of people.
  • Perhaps it should be so.


So, I'll tell you a little about my experience with Nim, briefly talk about the features of programming in it, and also try to compare it with Python and C. Looking ahead, I note that this language seems very promising to me.



Code in the studio!



As an example, I decided to write something more complex in Nim than hello, world:







It seems like nothing superfluous, right? It seems so simple that you can easily figure out what it does, even if you've never heard of Nim before. (The program will output: "num: 5 i: 5")



So, let's take a look at what seems familiar to us from somewhere.



Variable declaration



This is painfully familiar to JavaScript developers. While some languages ​​use var and some use let, JS and Nim allow you to use both when declaring variables. However, it is important to note that they work differently in Nim than in JS. But more on that later.



Blocks



To denote a new block in Nim, we use a colon followed by an indented line. Everything is like in Python.



Keywords



Both loops and the if statement look like they are a piece of Python code. In fact, everything from line 5 onwards is Python code (assuming we have the echo function defined).



So yes, many Python keywords and operators can also be used in Nim: not, is, and, or, and so on.



That is, so far we do not see anything special in Nim: the worst version of Python (in terms of

syntax), taking into account the fact that you need to use let or var to declare variables.



We could stop there, but there is a big "but": Nim is a statically typed language that works almost as fast as the C language.



Well, now another conversation. Let's check it out.



Performance test







Before we dive into the syntax of Nim (especially the statically typed part that we haven't seen so far), let's try to evaluate its performance. To do this, I wrote a naive implementation for calculating the nth Fibonacci number in Nim, Python and C.



To keep things fair, I standardized the implementation based on the Leetcode solution (Option 1) and tried to stick to it as strictly as possible in all three languages.



You can of course remind me of LRU Cache . But for now, my task is to use a standard approach, and not try to optimize calculations. So I chose a naive implementation.


Here are the results for calculating the 40th Fibonacci number:







Yes, strictly speaking, the experiment cannot be called clean, but this correlates with the results of other enthusiasts who did more serious tests [1] [2] [3] .



All the code I wrote for this article is available on GitHub, including instructions on how to run this experiment.



So why is Nim so much faster than Python?



Well, I would say there are two main reasons:



  1. Nim is a compiled language and Python is an interpreted language (more on that here ). This means that more work is done when a Python program is run because the program must be interpreted before it can run. This usually makes the language slower.
  2. Nim is statically typed. Although there was no type declaration in the example I showed earlier, we will see later that it is indeed a statically typed language. In the case of Python, which is dynamically typed, there is a lot more work to be done by the interpreter to define and handle the types appropriately. It also reduces performance.


The speed of work grows - the coding speed decreases



Here's what the Python Docs says about interpreted languages:

« / , , ».


This is a good generalization of the tradeoff between Python and C, for example. Anything you can do in Python you can do in C, but your C program will run many times faster.



But you will spend a lot more time writing and debugging your C code, it will be unwieldy and less readable. And that's why C is no longer in demand and Python is popular. In other words, Python is much simpler (comparatively, of course).



So, if Python is at one end of the spectrum and C is at the other, then Nim is trying to get somewhere in the middle. It is much faster than Python, but not as difficult to program as C.



Let's take a look at our implementation of calculating Fibonacci numbers.



FROM:



#include <stdio.h>
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    } 
    return fibonacci(n-1) + fibonacci(n-2);
}

int main(void) {
    printf("%i", fibonacci(40));
}


Python:



def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(40))


Nim:



proc fibonacci(n: int): int = 
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

echo(fibonacci(40))


Although Nim uses the "=" sign in its procedure (function) syntax, in general it is much easier to write code than in C.



Maybe this is really a worthy trade-off? A bit harder to write than Python, but it works ten times faster. I could live with it.



Nim syntax



import strformat

#    https://nim-lang.org/

type
  Person = object
    name: string
    age: Natural #      

let people = [
  Person(name: "John", age: 45),
  Person(name: "Kate", age: 30)
]

for person in people:

  echo(fmt"{person.name} is {person.age} years old")


I'll just point out the key features.



Variables



We use var, let or const to declare variables.



var and const work the same way as in JavaScript, but let is a different story.



JavaScript let differs from var in terms of scope, and Nim let denotes a variable whose value cannot change after initialization. It looks to me like Swift.



But isn't that the same as a constant? - you ask.



No. In Nim, the difference between const and let is as follows:

For const, the compiler must be able to determine the value at compile time, while for let it can be determined at run time.


Example from the documentation:



const input = readLine(stdin) # Error: constant expression expected
let input = readLine(stdin)   #  


Alternatively, variables can be declared and initialized like this:



var
   a = 1
   b = 2
   c = 3
   x, y = 10 #   x  y   10


Functions



Functions in Nim are called procedures:



proc procedureName(parameterName: parameterType):returnType =
   return returnVar


Given that the language is similar to Python in many ways, the procedures seem a little odd when you first see them.



Using "=" instead of "{" or ":" is clearly confusing. Everything is a little better with writing the procedure in one line:



proc hello(s: string) = echo s


You can also get the result of the function:



proc toString(x: int): string =
   result =
       if x < 0: “negative”
       elif x > 0: “positive”
       else: “zero”


It feels like you still need to somehow return result, but in this case, result is not a variable - it's a keyword. So the above code snippet will be correct from Nim's point of view.



You can also overload procedures:




proc toString(x: int): string =   
    result =     
        if x < 0: "negative"     
        elif x > 0: "positive"     
        else: "zero"  
proc toString(x: bool): string =   
    result =     
        if x: "yep"     
        else: "nope"
echo toString(true) #  "yep"
echo toString(5) #  "positive"


Conditions and cycles



It has a lot to do with Python.



# if true:

# while true:

# for num in nums:


To iterate over a list, for example, instead of range (), you can use countup (start, finish) , or countdown (start, finish) . You can do it even easier and use for i in start..finish



User input and output



let input = readLine(stdin)
echo input


Compared to Python, readLine (stdin) is equivalent to input () and echo is equivalent to print.



echo can be used with or without parentheses.



My goal is to give you a basic understanding of Nim, not retell all of its documentation. So I end up with the syntax and move on to other features of the language.



Extra features



Object Oriented Programming



Nim is not an object-oriented language, but it has minimal support for working with objects . Of course, he is far from Python classes.



Macros



Nim supports macros and metaprogramming, and the developers seem to be putting a lot of emphasis on this. This is the subject of its own section of the three-lesson series.



Small example:



import macros  macro myMacro(arg: static[int]): untyped =  
   echo arg

myMacro(1 + 2 * 3)


Basic data types



string, char, bool, int, uint  float.


You can also use these types:



int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64


In addition, strings in Nim are mutable types, unlike Python.



Comments



Unlike Python, Nim uses the "#" character in combination with "[" and "]" for multiline comments.



# a comment#[
a
multi
line
comment
]#


Compiling JavaScript



Nim can translate its code to JavaScript. Not sure if many people come by to use this. But there is such an example of the Snake browser game written in Nim.



Iterators



Nim iterators are more like Python generators:



iterator countup(a, b: int): int =
   var res = a
   while res <= b:
       yield res
       inc(res)


Case Sensitivity and Underscore

Nim is case sensitive only for the first character.



That is, he distinguishes HelloWorld and helloWorld, but not helloWorld, helloworld and hello_world. Therefore, the following procedure will work without problems, for example:



proc my_func(s: string) =
   echo myFunc("hello")


How popular is Nim?







Nim has nearly 10,000 stars on GitHub. This is a clear plus. Nevertheless, I tried to estimate the popularity of the language from other sources, and, of course, it is not that high.



For example, Nim was not even mentioned in the 2020 Stack Overflow Survey . I couldn't find Nim developer jobs on LinkedIn (even with Worldwide geography) and a search for [nim-lang] on Stack Overflow only returned 349 questions (compare to ~ 1,500,000 for Python or 270,000 for Swift)



. Thus, it would be fair to assume that most developers have not used it and many have never heard of the Nim language.



Replacing Python?



To be honest, I think Nim is a pretty cool language. To write this article, I studied the required minimum, but that was enough. Although I haven't gone too deep into it, I plan on using Nim in the future. Personally, I'm a big fan of Python, but I also like statically typed languages. So for me, the performance improvement in some cases more than makes up for a little syntactic redundancy.



While the basic syntax is very similar to Python, it is more complex. Therefore, most Python fans will likely not be interested in it.



Also, don't forget about the Go language. I am sure that many of you have thought about this as you read, and rightly so. Despite the fact that the syntax of Nim is closer to the syntax of Python, in terms of performance it competes precisely with languages ​​like "simplified C ++".

I once tested Go performance. In particular, for Fibonacci (40), it worked as fast as C.


But still: can Nim compete with Python? I doubt it very much. We are seeing a trend towards increasing computer performance and simplifying programming. And, as I noted, even if Nim offers a good syntax / performance trade-off, I don't think it's enough to beat pure and versatile Python.

I spoke with one of the Nim Core developers. He thinks Nim is more suitable for those migrating from C ++ than for pythonists.


Can Nim compete with Go? Possibly (if Google "allows"). The Nim language is as powerful as Go. Moreover, Nim has better support for C / C ++ features, including macros and overloading.



But more about that sometime next time.






Advertising



Epic servers are affordable virtual servers with processors from AMD, CPU core frequency up to 3.4 GHz. The maximum configuration will allow you to come off to the full - 128 CPU cores, 512 GB RAM, 4000 GB NVMe. Hurry up to order!






All Articles