Чи варто переходити з Python на Nim заради продуктивності?

Чи варто переходити з Python на Nim заради продуктивності?
13 хв. читання

Nim — це поєднання синтаксису Python і продуктивності C

Кілька тижнів тому я бродив по GitHub і натрапив на цікавий репозиторій: проєкт був повністю написаний на мові Nim. До цього я з ним не стикався, і в цей раз вирішив розібратися, що це за звір.

Спочатку я подумав, що відстав від життя, що це одна з поширених мов програмування, якою багато, на відміну від мене, активно користуються. І тоді я вирішив вивчити її.

Ось які висновки я зробив:

  • Ця мова насправді популярна серед вузького кола осіб.
  • Можливо, так і повинно бути.

Отже, розповім трохи про мій досвід роботи з Нім, коротко розповім про особливості програмування на ній, а також спробую порівняти її з Python і C. Забігаючи наперед, відзначу, що ця мова мені здається дуже перспективною.

Покажіть мені код!

В якості прикладу я вирішив написати на Nim щось складніше, ніж hello, world:

Чи варто переходити з Python на Nim заради продуктивності?
Начебто нічого зайвого, правда? Він здається настільки простим, що ви без зусиль зможете зрозуміти, що він робить, навіть якщо ви ніколи раніше не чули про Nim. (Програма виведе: «num: 5 i: 5»)

Отже, нумо розберімо, що звідси здається нам знайомим.

Оголошення змінних

Це до болю знайоме розробникам JavaScript. У той час як деякі мови використовують var, а деякі використовують let, JS і Nim дозволяють використовувати при оголошенні змінних і те, і інше. Однак важливо відзначити, що в Nim вони працюють не так, як в DOS. Але про це пізніше.

Блоки

Щоб позначити новий блок в Nim, ми використовуємо двокрапку, за якою йде слідом рядок з відступом. Все, як у Python.

Ключові слова

Обидва цикли, а також оператор if виглядають так, як ніби це фрагмент коду на Python. Фактично, все починаючи з рядка 5 і далі є кодом на Python (за умови, що у нас визначена функція echo).

Тому Так, багато ключових слів і операторів з Python також можна використовувати в Nim: not, not, and, or і так далі.

Тобто, поки ми не бачимо в Nim нічого особливого: найгірша версія Python (з точки зору синтаксису), з урахуванням того, що для оголошення змінних потрібно використовувати let або var.

На цьому можна було б зупинитися, «але є одне але»: Nim — статично типізована мова, яка працює майже так само швидко, як мова C.

Ну, тепер інша розмова. Тепер перевірмо це.

Тест продуктивності

Перш ніж заглибитися в синтаксис Nim (особливо в статично типізовану частину, яку ми досі не бачили), спробуймо оцінити його продуктивність. Для цього я написав наївну реалізацію для обчислення n-ого числа Фібоначчі Nim, Python і C.

Щоб все було по-чесному, я стандартизував реалізацію на основі рішення Leetcode (Варіант 1) і постарався якомога суворіше дотримуватися її на всіх трьох мовах.

Ви, звичайно, можете нагадати мені про LRU Cache. Але зараз моє завдання — використовувати стандартний підхід, а не намагатися оптимізувати обчислення. Тому я вибрав наївну реалізацію.

Ось результати для обчислення 40-го числа Фібоначчі:
Чи варто переходити з Python на Nim заради продуктивності?Так, строго кажучи, не можна назвати експеримент чистим, але це корелює з результатами інших ентузіастів, які робили серйозніші тести [1][2][3].

Весь код, який я писав для цієї статті, доступний на GitHub, включаючи інструкцію про те, як провести цей експеримент.

Так чому ж Nim працює набагато швидше, ніж Python?

Ну, я б сказав, що є дві основні причини:

  1. Nim — компільована мова, а Python — інтерпретована (докладніше про це тут). Це означає, що при запуску програми на Python виконується більше роботи, оскільки програму необхідно інтерпретувати перед тим, як вона зможе виконуватися. Зазвичай через це мова працює повільніше.
  2. Nim статично типізована. Хоча в прикладі, який я показав раніше, не було жодного оголошення типу, пізніше ми побачимо, що це дійсно статично типізована мова. У випадку з Python, який динамічно типізований, інтерпретатору потрібно виконати набагато більше роботи, щоб визначити та відповідним чином обробити типи. Це також знижує продуктивність.

Швидкість роботи зростає — швидкість написання коду падає

Ось що в Python Docs йдеться про інтерпретовані мови:

«Інтерпретовані мови зазвичай мають коротший цикл розробки/налагодження, ніж компілюємі, хоча їх програми зазвичай працюють повільніше».

Це хороше узагальнення компромісу між Python і C, наприклад. Все, що ви можете зробити на Python, ви можете зробити й на C, проте ваша програма буде працювати у багато разів швидше.

Але ви будете витрачати значно більше часу на написання і налагодження свого коду на C, він буде громіздким і менш читабельним. І саме тому C вже не має такого попиту, а Python популярний. Іншими словами, Python набагато простіший (відносно, звичайно).

Отже, якщо Python знаходиться на одному кінці спектра, а C — на іншому, то Nim намагається встати десь посередині. Він працює набагато швидше, ніж Python, але не такий складний для програмування, як C.

Подивімося на нашу реалізацію обчислення чисел Фібоначчі на C.

#include 
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))

Хоча у Nim в синтаксисі процедури (функції) використовується знак «=», в цілому писати код набагато простіше, ніж на C.

Може бути, це дійсно гідний компроміс? Трохи складніше писати, ніж на Python, але працює в десятки разів швидше. Я міг би з цим змиритися.

Синтаксис Nim

import strformat

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" is  years old")

Просто зазначу на ключові особливості.

Змінні

Для оголошення змінних використовуємо var, let або const. var і const працюють так само, як в JavaScript, а з let - інша історія. let в JavaScript відрізняється від var з точки зору області видимості, а let в Nim позначає змінну, значення якої не може змінитися після ініціалізації. Мені здається, це схоже на Swift. 

Але хіба це не те ж саме, що і константа? - запитаєте ви. Ні. У Nim відмінність між const і let полягає в наступному:

Для const компілятор повинен мати можливість визначати значення під час компіляції, тоді як для let воно може бути визначено під час виконання.

Приклад з документації:

const input = readLine(stdin) # Error: constant expression expected
let input = readLine(stdin)   # все правильно

Крім того, змінні можна оголошувати та форматувати так:

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

Функції

Функції в Nim називаються процедурами:

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

З огляду на те, що мова багато в чому схожа на Python, процедури здаються трохи дивними, коли ви вперше їх бачите.  Використовувати «=» замість «{» або «:» явно збиває з пантелику. Все виглядає трохи краще із записом процедури в один рядок:

proc hello(s: string) = echo s

Ви також можете отримувати результат виконання функції:

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

Таке відчуття, що все одно треба якось повернути result, але в цьому випадку result не є змінною - це ключове слово. Так що, наведений вище фрагмент коду буде правильним з точки зору Nim.

Ви також можете перевантажувати процедури:

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"

Умови та цикли

Тут багато спільного з Python.

# if true:
# while true:
# for num in nums:

Для перебору списку, наприклад, замість range() можна використовувати countup(start, finish), або countdown(start, finish). Можна зробити ще простіше і використовувати for i in start..finish

Введення та вивід інформації користувача

let input = readLine(stdin)
echo input

Якщо порівнювати з Python, то readLine(stdin) еквівалентно input(), а echo еквівалентно print. echo можна використовувати як з дужками, так і без них.

Моє завдання - зробити так, щоб у вас склалося базове уявлення про Nim, а не переказувати всю його документацію. Тому я закінчую з синтаксисом і переходжу до інших особливостей мови.

Додаткові особливості

Об'єктноорієнтоване програмування

Nim НЕ є об'єктноорієнтованою мовою, але в ньому реалізована мінімальна підтримка роботи з об'єктами . До класів Python йому, звичайно, далеко.

Макроси

Nim підтримує макроси і метапрограмування, і, здається, розробники досить сильно акцентують на цьому увагу. Цьому присвячений власний розділ серії з трьох уроків.

Маленький приклад:

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

myMacro(1 + 2 * 3)

Базові типи даних

string, char, bool, int, uint и float.

Також можна використовувати ці типи:

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

Крім того, в Nim рядки є mutable-типами, на відміну від Python.

Коментарі

На відміну від Python, в Nim для коментарів на декілька рядків використовується символ «#» в поєднанні з «[» і «]».

# a comment#[
a
multi
line
comment
]#

Компіляція  JavaScript

Nim може транслювати свій код в JavaScript. Не впевнений, що буде багато охочих це використовувати. Але є ось такий приклад браузерної гри «Змійка», написаної на Nim.

Ітератори

Ітератори в Nim більше схожі на генератори в Python:

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

Чутливість до регістру і нижнє підкреслювання
Nim чутливий тільки до регістру першого символу.

Тобто, HelloWorld і helloWorld він розрізняє, а helloWorld, helloworld і hello_world - немає. Тому без проблем буде працювати, наприклад, така процедура:

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

Наскільки популярний Nim?

Чи варто переходити з Python на Nim заради продуктивності?
У Nim майже 10000 зірок на GitHub. Це явний плюс. Проте, я спробував оцінити популярність мови за іншими джерелами, і, звичайно, вона не така висока.

Наприклад, Nim навіть не згадувався в 2020 Stack Overflow Survey. Я не зміг знайти вакансії для розробників Nim в LinkedIn (навіть з географією Worldwide), а пошук по тегу [nim-lang] на StackOverflow видав тільки 349 питань (порівняйте з ~1 500 000 для Python або з 270 000 для Swift)

Таким чином, було б справедливо припустити, що більшість розробників не використали її, а багато навіть не чули про мову Nim.

Заміна Python?

Буду чесний: я вважаю Nim досить крутою мовою. Щоб написати цю статтю, я вивчив необхідний мінімум, але цього вистачило. Хоча я не дуже заглиблювався в неї, я планую використовувати Nim в майбутньому. Особисто я великий шанувальник Python, але мені також подобаються мови зі статичною типізацією. Тому для мене поліпшення продуктивності в деяких випадках більш ніж компенсує невелику синтаксичну надмірність.

Хоча базовий синтаксис дуже схожий на Python, вона складніша. Тому більшість фанатів Python, швидше за все, не зацікавляться нею.

Крім того, не варто забувати про мову Go. Я впевнений, що багато хто з вас думали саме про це під час читання, і це правильно. Попри те, що синтаксис Nim ближче до синтаксису Python, по продуктивності він конкурує саме з мовами а-ля «спрощений C ++».

Я свого часу тестував продуктивність Go. Зокрема, для Фібоначчі (40) він працював так само швидко, як C.

Але все-таки: чи може Nim конкурувати з Python? Я дуже сумніваюся в цьому. Ми спостерігаємо тенденцію зростання продуктивності комп'ютерів і спрощення програмування. І, як я вже зазначав, навіть якщо Nim запропонує хороший компроміс по співвідношенню синтаксису і продуктивності, я не думаю, що цього достатньо, щоб перемогти чистий і універсальний Python.

Я спілкувався з одним з розробників Nim Core. Він вважає, що Nim більше підходить для тих, хто переходить з C ++, ніж для пітоністов.

Чи може Nim конкурувати з Go? Можливо (якщо Google «дозволить»). Мова Nim така ж потужна, як і Go. Більш того, в Nim краще реалізована підтримка функцій C/C ++, в тому числі макроси та перевантаження.

Оригінал ENG: medium.com

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація