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.
Ş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.
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
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
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
Ö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ç
İşlem tamamlandığında Nuitka bize çalıştırdığımız konumda 2 adet klasör oluşturmuş olacak.
Eğer programı klasörden bağımsız tek bir exe dosyasına çevirmek istiyorsak
Bu
Eğer programınızı bir exe değil de modül/kütüphane olarak paylaşmak istiyorsanız da
Eğer program çalışırken arka planda konsolun gözükmesini istemiyorsak
Daha detaylı kullanım şekilleri için yine Nuitka’nın GitHub sayfasına bakabilirsiniz. Ayrıca nuitka
Not: Sadece hız ölçümü yapılmıştır.
Toplamda 5 adet farklı test yapılmıştır:
Testlerin yapıldığı ortam:
main.py dosyası:
test.py dosyası:
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:
Ö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:
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.
- 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.
Ş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
--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.