Python, Veri Bilimi / 2 Ağustos 2024 / Miraç ÖZTÜRK
Merhaba iyi günler.
Bugün; popülaritesi gün geçtikçe yükselen, birçok makine öğrenmesi-yapay zeka temelli uygulamalarda açık ara ön planda olan, veri madenciliği-bilimsel analiz çözümlemelerinde esnek çözümlemeler sunan, hızlı ve çevik yapısı ile kullanıcıların gözdesi olarak yer edinmiş olan Python dili üzerinde bir modelin performansının nasıl analiz edilip, ölçüleceğine yönelik kısa çözüm ve yöntemleri inceliyor olacağım.
Şimdiden iyi okumalar.
Makalede Neler Var ?
Neden Performans Analizi?
Python’da performans analizi ve performans ölçümü, bir dizi nedenle önemlidir:
- Hız ve Verimlilik: Performans analizi, kodun hızını ve verimliliğini değerlendirmeye yardımcı olur. Bu, daha hızlı ve daha düşük kaynak tüketimiyle çalışan uygulamalar geliştirmeye yardımcı olur.
- Kullanıcı Deneyimi: Performans, kullanıcı deneyimi açısından önemlidir. Yavaş veya düşük performanslı uygulamalar kullanıcıların ilgisini kaybetmesine ve daha hızlı alternatiflere geçmesine neden olabilir.
- Ölçeklenebilirlik: Performans analizi, uygulamanın büyüyen taleplerle nasıl başa çıktığını değerlendirmeye yardımcı olur. Bu, özellikle web uygulamaları ve dağıtılmış sistemler gibi büyük ölçekli sistemler için kritik öneme sahiptir.
- Kaynak Optimizasyonu: Performans analizi, sistem kaynaklarının (CPU, bellek, disk alanı ve ağ bant genişliği) kullanımını ölçmeye yardımcı olur. Bu, kaynakların daha etkili bir şekilde kullanılmasını ve maliyetlerin azaltılmasını sağlar.
- Hata Teşhisi: Performans sorunları, genellikle kodda veya yapılandırmada hataların veya darboğazların bulunmasına yardımcı olabilir. Performans ölçümü, bu tür sorunları teşhis etmeye ve düzeltmeye yardımcı olur.
- Karşılaştırmalar ve Kararlar: Performans ölçümü, farklı algoritmaların, kütüphanelerin veya teknolojilerin karşılaştırılmasında önemli bir faktördür. Bu, daha hızlı ve daha iyi performans gösteren seçeneklere yönelmenize ve uygun teknoloji kararları vermenize yardımcı olur.
- Sürekli İyileştirme: Performans analizi ve ölçümü, sürekli iyileştirme sürecinin bir parçasıdır. Düzenli olarak performansı izleyerek ve analiz ederek, kodunuzu sürekli olarak optimize edebilir ve geliştirebilirsiniz.
Sonuç olarak, Python’da performans analizi ve performans ölçümü, uygulamalarınızın ve sistemlerinizin daha iyi performans göstermesini, daha hızlı ve daha etkili olmasını sağlamak için kritik öneme sahiptir.
Performans Analizi ve Ölçüm Yöntemleri
Python’da performans analizi ve performans ölçümü gerçekleştirmek için birtakım yöntem, modül ve araç mevcuttur.
Bazı temel yöntem, modül ve araçlar:
time Modülü
timeit: timeit modülü; ilkel seviyede Python üzerindeki yerleşik küçük kod parçalarının çalışma süresini ölçmek için kullanılır.
Doküman içeriği için lütfen tıklayınız…
Modül genel kullanım örneği;
# Oncelikle ilgili kutuphanemizi eklememiz gerekmektedir. import time # Ilgili kutuphane; Python icerisinde varsayilan olarak gelmektedir. # Ek bir kurulum yapmayınız. # Yuklemede bulunduysaniz; pip uninstall kutuphane_adi # kodu ile yuklediginiz kutuphaneyi siliniz. # Hatali yukleme; https://pypi.org/project/python-time/
import time
start_time = time.time() def test_function(): '''BU ALANDA TEST ETMEK ISTEDIGINIZ KOD BLOGU YER ALMALI.''' a=5 b=6 c=((a**98)-755646544*b)/25464561.0546545 return c test_function() end_time = time.time() print(f"Islem suresi: {end_time - start_time} saniye.")
Kodumuzun çıktı karşılığı ise;
Islem suresi: 0.0009970664978027344 saniye.
şeklindedir.
Buradaki saniye bağlamındaki ifade; kodun çalışmaya başladığı noktada başlayan ve bittiği noktada ölçümü sonlandıran bir sayaç çıktısı olarak düşünülebilir.
Gerçekleşmesi planlanan çalışmaya yönelik zaman dönüşümü gerçekleştirilerek kullanım sağlanabilir.
timeit Modülü
timeit: timeit modülü; Python üzerinde yerleşik küçük kod parçalarının çalışma süresini ölçmek için kullanılır.
Doküman içeriği için lütfen tıklayınız…
Modül genel kullanım örneği;
# Oncelikle ilgili kutuphanemizi eklememiz gerekmektedir. import timeit # Ilgili kutuphane; Python icerisinde varsayilan olarak gelmektedir. # Ek bir kurulum yapmayınız. # Yuklemede bulunduysaniz; pip uninstall kutuphane_adi # kodu ile yuklediginiz kutuphaneyi siliniz. # Hatali yukleme; https://pypi.org/project/pytest-timeit/
# Varsayilan kullanim parametreleri. timeit.timeit(
stmt='pass',
setup='pass',
timer=<default timer>,
number=1000000,
globals=None
)
# Parametrik kullanimlari kendi testimize yonelik duzenleyelim. timeit.repeat(
stmt='pass',
setup='pass',
timer=<default timer>,
number=1000000,
globals=None,
repeat=5
)
- stmt: Yürütme süresini ölçmek istediğiniz kod bloğunun dizesini tutar.
Varsayılan değer pass‘tir, bu parametrik alana ilgili analiz edeceğiniz kodu atamanız gerekmektedir. - setup: Genellikle stmt kod bloğumuz için temel modülleri içe aktaran import ifadesini tutar.
Varsayılan değer pass‘tir. - timer: Timer nesnesi tutar, otomatik olarak ayarlanabilir.
- number: Kod parçacığınızı kaç kez çalıştırmak istediğinizi belirtmek için kullanılır.
Varsayılan değer 1 milyon’dur. (1.000.000) - repeat: Yürütmenin ne sıklıkta tekrarlanması gerektiğini belirtir.
- globals: Fonksiyonumuzla ilişkilendirilecek global isim alanını belirtir.
Belirli modüllerdeki bazı değişkenlere erişmek istendiğinde bu parametreyi kullanabilir.
Örnek bir test kodu hazırlayacak olursak;
import timeit def test_function(): '''BU ALANDA TEST ETMEK ISTEDIGINIZ KOD BLOGU YER ALMALI.''' a=5 b=6 c=((a**6)-7*b)/2.05 return c # Degiskenlerin tanimlanmasi. setup_code = ''' ''' ''' from __main__ import LIBRARY, import LIBRARY, vb. ''' # setup ile kodlar yuklenir. execution_time = timeit.timeit( stmt=test_function, setup=setup_code, number=1000000 ) # repeat ile tekrarli olarak islem gerceklesitirilir. execution_repeat_time = timeit.repeat( stmt=test_function, setup=setup_code, number=1000000, repeat=5 ) print(f"Calisma zamani: {execution_time:.6f} saniye") print(f"Tekrarli calisma zamani: {execution_repeat_time:} saniye")
Kodumuzun çıktı karşılığı ise;
Calisma zamani: 0.377303 saniye
Tekrarli calisma zamani:
[0.3729029999813065,
0.35637819999828935,
0.30232779996003956,
0.2879481000127271,
0.28245920001063496] saniye
şeklindedir.
Bu çıktıyı biraz açıklayacak olursak;
- Çalışma zamanı 0.377303 saniye sürmüş.
Bu değer; test_function fonksiyonunun, bir milyon kez çalıştırılmasının toplam süresini gösterir.
Bu, fonksiyonun; her bir çalışmasının yaklaşık 0.000377303 saniye sürdüğü anlamına gelmektedir. - Tekrarlı çalışma zamanı ise;
[0.3729029999813065,
0.35637819999828935,
0.30232779996003956,
0.2879481000127271,
0.28245920001063496]
saniye sürdüğünü gösterir.
Bu liste; fonksiyonun bir milyon kez çalıştırılmasının, beş farklı denemede ne kadar süre aldığını gösterir.
Her bir değer; o denemedeki, toplam çalışma süresine karşılık gelmektedir.
Tabiki bu çıktıların net anlamlı olduğu yada yorumlanabildiği durumlar; büyük verilerin ya da yoğun işlemli akışların gerçekleştiği çalışmalardır.
cProfile Modülü
cProfile: cProfile modülü; Python üzerinde kod parçalarının çalışma süresini ölçmek için kullanılır.
Doküman içeriği için lütfen tıklayınız…
Modül genel kullanım örneği;
# Oncelikle ilgili kutuphanemizi eklememiz gerekmektedir.
pip install cprofilev
- enable(): Profillemeye başlar ve profil verilerini toplamaya devam eder.
- disable(): Profillemeyi durdurur ve daha fazla veri toplamayı keser.
- create_stats(): Profillemeyi durdurur ve toplanan verileri içsel olarak kaydeder.Bu işlem; profilin mevcut durumunu kaydeder.
- print_stats(sort=-1): İçsel olarak kaydedilmiş profil verilerine dayanarak bir Stats nesnesi oluşturur ve sonuçları standart çıkışa (stdout) yazdırır.sort parametresi ile sonuçların nasıl sıralanacağını belirleyebilirsiniz.
- dump_stats(filename): Mevcut profilin sonuçlarını belirtilen dosya adına kaydeder.Bu dosya; daha sonra pstats modülü ile analiz edilebilir.
- run(cmd): Verilen cmd komutunu exec() ile çalıştırır ve bu sırasında profil verileri toplar.
- runctx(cmd, globals, locals): Verilen cmd komutunu, belirtilen global globals ve yerel locals değişken ortamlarını kullanarak exec() ile çalıştırır ve bu sırasında profil verileri toplar.
- runcall(func, /, *args, **kwargs): Belirtilen func fonksiyonunu argümanları args ve anahtar kelime argümanları kwargs ile çağırır ve bu fonksiyonun çalışması sırasında profil verileri toplar.
Not: Profilleme; sadece, çağrılan komut/fonksiyon geri dönüş yaparsa işler.
Eğer yorumlayıcı interpreter çağrı sırasında sonlandırılırsa.
Örneğin; komut/fonksiyon içinde yapılan bir sys.exit() çağrısı ile, profil sonuçları yazdırılmaz.
Özet kod kullanımı;
# Varsayilan kullanim parametreleri. cProfile.Profile.enable()
cProfile.Profile.disable()
cProfile.Profile.create_stats()
# sort icin varsayilan deger -1'dir. Herhangi bir sıralama yapmadan cikar. cProfile.Profile.print_stats(sort='-1')
# filename profil verilerinin kayit edilecegi dizini verir. cProfile.Profile.dump_stats(filename='profile_output.prof') # cmd profillemek istenen Python kodunu kullanir.
cProfile.run( cmd='for _ in range(1000000): test_function()', filename=None, sort='cumulative' )
# globals komutun calistirilmasi icin gerekli global degiskenlerin bulundugu sozluk. cProfile.runctx( cmd='for _ in range(1000000): test_function()', globals=globals(), locals=locals(), filename=None, sort='cumulative' )
# func profillemek istenen fonksiyon.
# *args fonksiyonda kullanilacak argumanlar.
# **kwargs fonksiyona aktarilacak anahtar argumanlar. cProfile.Profile.runcall( func=test_function, *args, **kwargs )
şeklindedir.
Örnek bir kullanımda bulunacak olursak;
# cProfile yanı sıra; istatistikler icin pstas kutuphanesini kullaniyoruz. import cProfile import pstats def test_function(): a = 5 b = 6 c = ((a**6) - 7*b) / 2.05 return c profiler = cProfile.Profile() # Dongu olarak kullanim. profiler.enable() for _ in range(1000000): test_function() profiler.disable() # Python kodunu cProfile ile run ederek. cProfile.run( 'for _ in range(1000000): test_function()', 'profile_output.prof' ) profiler.dump_stats('output.prof') # pstats kutuphanesi kullanarak. stats = pstats.Stats('output.prof') stats.sort_stats('cumulative') stats.print_stats(10) # cProfile kutuphanesi kullanarak. profiler.create_stats() profiler.print_stats() # Lokal ve global degiskenleri kullanarak. cProfile.runctx( statement=''"for _ in range(1000000): test_function()"'', globals=globals(), locals=locals(), filename=None, sort='cumulative' ) profiler.runcall(test_function)
Kodumuzun çıktı karşılığı ise;
Sat Apr 20 19:08:43 2024 output.prof 1000038 function calls in 0.361 seconds Ordered by: cumulative time List reduced from 18 to 10 due to restriction <10> ncalls tottime percall cumtime percall filename:lineno(function) 2 0.000 0.000 0.540 0.270 C:\Users\user\AppData\Roaming\Python\Python310\site-packages\IPython\core\interactiveshell.py:3490(run_code) 2 0.000 0.000 0.540 0.270 {built-in method builtins.exec} 1000000 0.360 0.000 0.360 0.000 C:\Users\user\AppData\Local\Temp\ipykernel_24636\438736628.py:4(test_function) 2 0.000 0.000 0.000 0.000 c:\Users\user\AppData\Local\Programs\Python\Python310\lib\codeop.py:117(__call__) 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile} 2 0.000 0.000 0.000 0.000 c:\Users\user\AppData\Local\Programs\Python\Python310\lib\contextlib.py:279(helper) 2 0.000 0.000 0.000 0.000 c:\Users\user\AppData\Local\Programs\Python\Python310\lib\contextlib.py:139(__exit__) 4 0.000 0.000 0.000 0.000 {built-in method builtins.next} 2 0.000 0.000 0.000 0.000 c:\Users\user\AppData\Local\Programs\Python\Python310\lib\contextlib.py:130(__enter__) 2 0.000 0.000 0.000 0.000 c:\Users\user\AppData\Local\Programs\Python\Python310\lib\contextlib.py:102(__init__) 1000038 function calls in 0.361 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 438736628.py:1(<module>) 1000000 0.360 0.000 0.360 0.000 438736628.py:4(test_function) 2 0.000 0.000 0.000 0.000 codeop.py:117(__call__) 4 0.000 0.000 0.000 0.000 compilerop.py:180(extra_flags) 2 0.000 0.000 0.000 0.000 contextlib.py:102(__init__) 2 0.000 0.000 0.000 0.000 contextlib.py:130(__enter__) 2 0.000 0.000 0.000 0.000 contextlib.py:139(__exit__) 2 0.000 0.000 0.000 0.000 contextlib.py:279(helper) 2 0.000 0.000 0.000 0.000 interactiveshell.py:1231(user_global_ns) 2 0.000 0.000 0.000 0.000 interactiveshell.py:3442(compare) 2 0.000 0.000 0.540 0.270 interactiveshell.py:3490(run_code) 2 0.000 0.000 0.000 0.000 traitlets.py:654(get) 2 0.000 0.000 0.000 0.000 traitlets.py:692(__get__) 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile} 2 0.000 0.000 0.540 0.270 {built-in method builtins.exec} 4 0.000 0.000 0.000 0.000 {built-in method builtins.getattr} 4 0.000 0.000 0.000 0.000 {built-in method builtins.next} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1000003 function calls in 0.542 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.542 0.542 {built-in method builtins.exec} 1 0.175 0.175 0.542 0.542 <string>:1(<module>) 1000000 0.367 0.000 0.367 0.000 438736628.py:4(test_function) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} saniye
şeklindedir.
Bu çıktıyı biraz açıklayacak olursak;
- test_function bir milyon kez çağrılmış ve toplamda 0.360 saniye sürmüş. (tottime)
Her bir çağrı ise; ortalama 0.000 saniye sürmüş. (Sanki çalışmamışçasına çok hızlı). - Kümülatif süre de 0.360 saniye, yani bu fonksiyonun kendisi dışında başka bir zaman harcayan alt fonksiyon çağrısı olmamış. (cumtime)
- {built-in method builtins.exec}
Python’ın yerleşik exec metodunun, iki kez çağrıldığını ve bu çağrıların; her birinin ortalama 0.270 saniye sürdüğünü göstermektedir.Bu; genellikle bir scriptin veya kod bloğunun yürütülmesi sırasında görülür.
Tabiki bu çıktıların net anlamlı olduğu durumlar, birden çok fonksiyon ve çağrının olduğu durumda;
- Hangi fonksiyonların en fazla süreyi harcadığını,
- Hangi çağrıların optimizasyona ihtiyaç duyduğunu
- Potansiyel darboğazların nerede olduğunu
gibi problemleri belirlemede etkili olmaktadır.
line_profiler Modülü
line_profiler: line_profiler modülü; ilkel seviyede Python üzerindeki fonksiyonlarınızın, herbir satır süresini ölçmek için kullanılır.
Doküman içeriği için lütfen tıklayınız…
Modül genel kullanım örneği;
# Oncelikle ilgili kutuphanemizi eklememiz gerekmektedir.
import line_profiler
Örnek bir test kodu hazırlayacak olursak;
from line_profiler import LineProfiler # type: ignore def test_function(): a = [1] * (10 ** 6) b = [2] * (2 * 10 ** 7) del b return a profiler = LineProfiler() profiler.add_function(test_function) profiler.run('test_function()') profiler.print_stats()
Kodumuzun çıktı karşılığı ise;
Timer unit: 1e-07 s
Total time: 0.0759075 s
File: C:\Users\user\AppData\Local\Temp\ipykernel_26600\3869493580.py
Function: test_function at line 3
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def test_function():
4 1 24748.0 24748.0 3.3 a = [1] * (10 ** 6)
5 1 400928.0 400928.0 52.8 b = [2] * (2 * 10 ** 7)
6 1 333335.0 333335.0 43.9 del b
7 1 64.0 64.0 0.0 return a
şeklindedir.
- Line #: Her satırın kod numarasını gösterir.
- Hits: Her bir satırın kaç kez çalıştığını gösterir.
- Time: Herbir satırda harcanan toplam süreyi belirtir.
- Per Hit: Her bir satır çalıştırılmasında harcanan ortalama süreyi gösterir.
- % Time: Her satırın toplam çalıştırma süresi içindeki yüzdesini gösterir.
- Line Contents: Profil oluşturulan kodun (Çalıştırılan kodun) kendisini gösterir.
şeklindedir.
İlkel bağlamdaki fonksiyonlar üzerinde önemli seviyede analiz edilebilir çıktılar sağlamayabilir; fakat, kompleks yada işlem hacmi yüksek fonksiyonlara yönelik sonuç odaklı analizler sunmaktadır.
Kısa çözüm ve örnekler ile; Python üzerindeki geliştirmiş olduğumuz kodlara yönelik, performans incelemesi sağlayacak yöntemleri incelemeye çalıştık.
Kullanmış olduğumuz kodlara;
https://github.com/miracozturk17/python-performance-analyze
bağlantısı üzerinden erişebilirsiniz.
Umarım faydalı olur.
İyi çalışmalar.