Rehber Basitçe Nuitka Kullanımı ve PyInstaller ile Karşılaştırılması

Merhabalar, karşıma Nuitka adlı çok güzel bir PyInstaller alternatifi çıktı ve ben de nasıl kullanılacağı ve artıları-eksileri hakkında basit, karşılaştırmalı ve performans değerlendirmeli bir makale yazmak istedim. Makale biraz uzun olduğundan sadece performans testlerini görmek istiyorsanız doğrudan makalenin sonuna gitmenizi tavsiye ederim.


Öncelikle Nuitka veya PyInstaller gibi Python kodlarını executable haline getiren uygulamaları neden kullanmanız gerekebileceğinize dair önceki makalemden bir alıntı yapmak istiyorum.

Diyelim ki bir Python kodu yazıyorsunuz ve yazdığınız kodu herhangi bir sebepten dolayı arkadaşlarınıza, müşterilerinize vb. kişilere dağıtmak istiyorsunuz. Elbette, direkt olarak program.py dosyanızı gönderebilirsiniz yalnız bu durumda karşı tarafın yapması gereken bazı şeyler olacak:

  • Programı attığınız kişide Python yüklü olmayabilir ve programı çalıştırmak için yüklemek zorunda kalacaktır.
  • Bilgisayarında Python yüklü olsa bile eğer kodunuzda Requests, PyQt5, Kivy, lxml gibi Python ile hazırda yüklü gelmeyen external kütüphaneler kullanılıyorsa pip kullanarak bu kütüphaneleri teker teker yüklemesi gerekecektir.
Yolladığınız programı kullanacak olan kişi, eğer bilgisayara veya bu yukarıdaki şeylerin yapımına aşina değilse, uğraşmak istemiyorsa veya uğraşsa da beceremiyorsa ama programı kullanmak istiyorsa peki o zaman ne yapacak? Cevap basit, PyInstaller kullanarak programınızı bir executable haline getirebilir ve bilgisayarlarda tek tıklamayla çalıştırılmasını sağlayabilirsiniz.


Şimdi eğer “Yahu bu Nuitka’nın PyInstaller’dan ne farkı var da bir de bunu öğrenelim arkadaş?” diye soracak olursanız cevabınızı vereyim. Nuitka bir Python derleyicisi. Nasıl mı? PyInstaller, Python interpreter’ını (kusura bakmayın, Türkçe kullanmak doğru gelmedi) programınızın içine paketler ve kodları o paketlenmiş interpreter üzerinde çalıştırır, arada da bazı optimizasyonlar yaparak kodunuzu bir ihtimal hızlandırmayı hedefler lakin PyInstaller’ın ana kullanım amacı kodunuzun çalışma süresini hızlandırmak değil kodunuzu kolayca taşınabilir hale getirmektir. Nuitka ise ilk önce yazmış olduğunuz Python kodlarını alır ve C kodlarına dönüştürür, daha sonra C kodlarını derleyerek size executable dosyanızı verir. Nuitka da PyInstaller gibi import edilen kütüphaneleri otomatik olarak dönüştürme özelliğine sahip, ekstra bir şey yapmanızı gerektirmiyor lakin kullanımı bir tık daha karmaşık. Yalnız C kodları derlendiği için gözle görülür bir hız artışı olması çok muhtemel. Şimdi gelelim nasıl kullanılacağına.


Nuitka Kullanımı:



Elimdeki bilgisayarda Windows 10 yüklü olduğundan bundan böyle Windows spesifik ilerleyeceğim. Diğer işletim sistemleri üzerindeki kullanımları neredeyse aynı olmasına rağmen indirme vb. konularda farklılıklar olduğundan dolayı Nuitka’nın GitHub sayfasına bakmanızı şiddetle tavsiye ederim. Windows için direkt pip kullanarak indirmemizi yapabiliriz.


Bash:
pip install nuitka


Nuitka’nın Python’u C koduna çevirip sonrasında derlediğinden bahsetmiştik. Bunu yapabilmesi için de bilgisayarınızda bir C derleyicisi yüklü olmalı. Merak etmeyin, Nuitka’yı çalıştırdığınızda bilgisayarınızda herhangi bir derleyici bulamazsa kendisi indirmek isteyip istemediğinizi size soracak ve evet derseniz de otomatik olarak indirecek. Benim bilgisayarımda GCC halihazırda yüklü olduğundan onunla devam edeceğiz.


Tüm programımızın main.py dosyasından ibaret olduğunu varsayalım. Nuitka’yı da PyInstaller gibi komut satırı üzerinden kullanıyoruz. Nuitka’nın import’lanan tüm Python paketlerini programa dahil etmesi adına özellikle --standalone ayarını belirtmeniz önemli, aksi takdirde her birini elle ayrı argümanlar olarak eklemeniz gerekecek.


Bash:
python -m nuitka --standalone main.py


Öncelikle bu satırı çalıştırdığınızda birkaç şeye dikkat etmeniz gerekiyor. Eğer bu, Nuitka’yı ilk çalıştırmanızsa o zaman derleme işlemi sırasında sizden birkaç Yes/no yazmanızı isteyecektir. Çok detay istemiyorsanız hepsine yes deyip geçin. --assume-yes-for-downloads ayarı ile de tüm “İndirmek istiyor musunuz?” sorularına otomatik olarak evet cevabının verilmesini sağlayabilirsiniz.


İşlem tamamlandığında Nuitka bize çalıştırdığımız konumda 2 adet klasör oluşturmuş olacak.
  • main.build
  • main.dist
Bizim istediğimiz asıl executable, main.dist klasörünün içinde bulunacak. Dikkat edin, --standalone ayarı bize bir klasör veriyor. Bu ayarda programı dağıtırken klasör yapısı aynı şekilde kalmalı, exe dosyası klasörün içinden çıkmamalı.


Eğer programı klasörden bağımsız tek bir exe dosyasına çevirmek istiyorsak --standalone yerine --onefile ayarını kullanabiliriz. Yalnız bu noktada önemli bir detay var. --onefile ayarıyla çıkan exe dosyası aslında --standalone ayarındaki klasörün içerisinde bulunan her şeyin tek bir dosyaya arşivlenmiş hali. Çalıştırıldığı anda içinde sıkıştırılmış olan diğer verileri geçici bir klasöre açıyor ve sonrasında programı çalıştırıyor. Bu arşivden çıkartma işlemi bazı durumlarda programınızın açılma süresini istenmeyen şekilde uzatabilir.


Bash:
python -m nuitka --onefile main.py


Bu --onefile seçeneğinizi kullandığınız takdirde eğer programınızda Python harici dosyalardan yararlanıyorsanız --include-data-files ayarını kullanmanız gerekiyor. Örneğin main.py dosyamız içinde bir message.txt dosyası okunuyor olsun ve biz bunu tek bir exe’ye dönüştürmek isteyelim. O zaman Nuitka’yı aşağıdaki gibi çalıştırmanız gerekmekte.


Bash:
python -m nuitka --onefile --include-data-files=message.txt=message.txt main.py


Eğer programınızı bir exe değil de modül/kütüphane olarak paylaşmak istiyorsanız da --module seçeneği işinizi fazlasıyla görecektir.


Bash:
python -m nuitka --module main.py


--module seçeneği çalışmayı bitirdikten sonra main[B].so[/B] adlı bir dosya sunacaktır.


Eğer program çalışırken arka planda konsolun gözükmesini istemiyorsak --windows-disable-console ayarını da kullanabiliriz.


Daha detaylı kullanım şekilleri için yine Nuitka’nın GitHub sayfasına bakabilirsiniz. Ayrıca nuitka -h komutu da size genel olarak ayarları gösterecektir.


Performans Testleri:



Not: Sadece hız ölçümü yapılmıştır.


Toplamda 5 adet farklı test yapılmıştır:
  • Saf Python (dümdüz çalıştırıyoruz)
  • PyInstaller (herhangi bir ek ayar yok)
  • Nuitka (--standalone)
  • PyInstaller (--onefile)
  • Nuitka (--onefile)

Testlerin yapıldığı ortam:
  • Python 3.10.6
  • Nuitka 1.1.6
  • PyInstaller 5.5
  • Windows 10 21H1 19043.2130
  • Ryzen 5600X (PBO Açık)
  • 3600 Mhz cl 18 2x8 GB (16 GB) RAM
  • O kadar uğraşmak istemediğim için testi VM’de yapmadım ancak özellikle herhangi bir ek servisin açık olmadığından emin olmaya çalıştım.
  • Tüm testler venv sanal ortamı içinde yapılmıştır.

main.py dosyası:
Python:
from time import perf_counter_ns, perf_counter


def generate_list(tn: int):
    arr = []

    for i in range(0, tn + 1):
        arr.append(i)
  
    for j in range(tn - 1, -tn - 1, -1):
        arr.append(j)
  
    for k in range(-tn + 1, 0):
        arr.append(k)
  
    return arr


TARGET_NUM = 1000
CALL_COUNT = 100_000

sec_list = []
nsec_list = []

total_s_s = perf_counter()
total_s_ns = perf_counter_ns()

for _ in range(CALL_COUNT):
    start_s = perf_counter()
    start_ns = perf_counter_ns()
  
    generate_list(TARGET_NUM)

    end_ns = perf_counter_ns()
    end_s = perf_counter()

    sec_list.append(end_s - start_s)
    nsec_list.append(end_ns - start_ns)

total_e_ns = perf_counter_ns()
total_e_s = perf_counter()


sum_seclist = sum(sec_list)
sum_nseclist = sum(nsec_list)

print(
    f"Seconds     - Total: {total_e_s-total_s_s}\t\tAverage: {sum_seclist/CALL_COUNT}\t\tMin: {min(sec_list)}\t\tMax: {max(sec_list)}\n"
    f"Nanoseconds - Total: {total_e_ns-total_s_ns}\t\tAverage: {sum_nseclist/CALL_COUNT}\t\tMin: {min(nsec_list)}\t\tMax: {max(nsec_list)}\n"
)

test.py dosyası:
Python:
from os import system
from time import perf_counter
from shutil import rmtree

print("Running pure Python...")
system("venv\\Scripts\\python.exe main.py")

print("Building PyInstaller standalone...")
pyins_start = perf_counter()
system("venv\\Scripts\\pyinstaller.exe --log-level CRITICAL main.py")
pyins_end = perf_counter()
print(f"Build took {pyins_end-pyins_start} seconds!")

print("Running PyInstaller exe...")
system("dist\\main\\main.exe")

print("Building Nuitka standalone...")
nuitka_start = perf_counter()
system("venv\\Scripts\\nuitka.bat --assume-yes-for-downloads --standalone main.py")
nuitka_end = perf_counter()
print(f"Build took {nuitka_end-nuitka_start} seconds!")

print("Running Nuitka exe...")
system("main.dist\\main.exe")

rmtree("build")
rmtree("dist")
rmtree("main.build")
rmtree("main.dist")

print("Building PyInstaller onefile...")
pyins_start = perf_counter()
system("venv\\Scripts\\pyinstaller.exe --onefile --log-level CRITICAL main.py")
pyins_end = perf_counter()
print(f"Build took {pyins_end-pyins_start} seconds!")

print("Running PyInstaller onefile...")
system("dist\\main.exe")

print("Building Nuitka onefile...")
nuitka_start = perf_counter()
system("venv\\Scripts\\nuitka.bat --assume-yes-for-downloads --onefile main.py")
nuitka_end = perf_counter()
print(f"Build took {nuitka_end-nuitka_start} seconds!")

print("Running Nuitka onefile...")
system("main.dist\\main.exe")


Sonuçlar:​

Kod:
Running pure Python...
Seconds     - Total: 10.944929599998432         Average: 0.00010920352499982983         Min: 0.00010230000043520704             Max: 0.0003119999964837916
Nanoseconds - Total: 10944929100                Average: 109065.223                     Min: 102200                             Max: 311500

Building PyInstaller standalone...
Build took 0.7359169000010297 seconds!
Running PyInstaller exe...
Seconds     - Total: 11.079449100001511         Average: 0.00011053845000653382         Min: 0.00010439999823574908             Max: 0.0002640999991854187
Nanoseconds - Total: 11079448800                Average: 110394.164                     Min: 104300                             Max: 263700

Building Nuitka standalone...
Build took 7.137196499999845 seconds!
Running Nuitka exe...
Seconds     - Total: 12.664008300002024         Average: 0.0001264015700035452          Min: 0.00012069999866071157             Max: 0.0003451000011409633
Nanoseconds - Total: 12664007700                Average: 126289.214                     Min: 120600                             Max: 344900

Building PyInstaller onefile...
Build took 5.338545499998872 seconds!
Running PyInstaller onefile...
Seconds     - Total: 11.08876280000186          Average: 0.00011064028700471681         Min: 0.00010280000060447492             Max: 0.0004840000001422595
Nanoseconds - Total: 11088762400                Average: 110502.601                     Min: 102700                             Max: 483500

Building Nuitka onefile...
Build took 8.08047119999901 seconds!
Running Nuitka onefile...
Seconds     - Total: 12.725925499998993         Average: 0.0001270347909973134          Min: 0.00012210000204504468             Max: 0.00030449999758275226
Nanoseconds - Total: 12725924900                Average: 126936.702                     Min: 122000                             Max: 304100

Yani açıkçası biliyorum, yukarıda yazdığım performans kazancı vb. şeylerin üstüne bu sonuçlar biraz garip oldu, tekrar tekrar çalıştırdım ama yine de hiçbir şey değişmedi. Bu kod özelinde Nuitka daha yavaş çıktı. Nedeni konusunda en ufak bir fikrim yok ancak belirli optimizasyon sıkıntıları olduğu kesin. Bu makaleyi yazarken yapmaya çok üşendim ancak VisualStudio veya clang derleyicileriyle daha farklı sonuçlar elde etmek mümkün olabilir. Tabii yine test etmek lazım ama başka bir zamana artık. Ayrıca kodda bolca “append” bulunduğu için ctype arrayleriyle kod bir miktar hızlandırılabilir diye düşünüyorum. En iyi ihtimalle numpy bile kullanılabilir.


Bu sonuçlar göz önünde bulundurarak PyInstaller ile karşılaştırdığımda yalnızca 2 sebep yüzünden yerine kullanılabileceğini düşünüyorum:
  • PyInstaller’a göre yaklaşık 1MB daha düşük --onefile dosya boyutu.
    • PyInstaller 5.8MB
    • Nuitka 4.7MB
    • Fark: 1.1MB
  • Eğer telif hakkına sahip olduğunuz bir kodun satışını yapıyorsanız ve kodunuzun kaynağına erişimi tamamen engellemek istiyorsanız Nuitka çok daha iyi bir seçim. PyInstaller’ın oluşturduğu dosyaları her ne kadar zor olsa da Python bytecode’una çevrilebiliyor lakin Nuitka kodları C’ye çevirip doğrudan compile ettiğinden bu işlem neredeyse imkânsız diyebiliriz.
 
Gerçekten kaliteli bir rehber olmuş. Türkçe bir Nuitka kaynağı yoktu sanırım, yeni başlayan geliştiriceler için gayet yararlı.

Kendi fikrimi belirtecek olursam programın tek negatif yanı çevirme işlemi çok uzun sürmesi, büyük bir projemi 4 saat gibi bir sürede çevirmişti. Ama buna kesinlikle değiyor. Ayrıca false-positive neredeyse hiç yemiyor.
 

Geri
Yukarı