Python’un Performansı ve Cython I

Enes Mesut Yıldız
6 min readJun 8, 2023

--

Python son yıllarda çeşitli alanlarda –özellikle veri bilimi alanında- sıkça tercih edilen bir programlama dili haline geldi. Python ile ilgilenen, geliştirme yapan hemen herkes Python dilinin diğer yüksek seviyeli dillere göre –C#, Java vb.- performans açısından kötü bir izlenime sahip olduğunu mutlaka duymuştur. Pythonun performansının sanıldığı kadar kötü olmadığını yazının ilerleyen kısımlarında Cython ile ilişkilendirerek anlatacağım.

Bu yazıyı okumaya başlamanı Python programlama dili ile öyle veya böyle bir şekilde yollarının kesişmesine yoruyorum. Akıllara gelen ilk sorunun “Ne Kadar ?” olabileceğini düşünerek hemen konunun en can alıcı kısmı hakkında birkaç kelam etmek ve yazının kalanına devam edip etmemeniz konusundaki vereceğiniz o kritik karara yardımcı olmak istiyorum.

“Cython ile ne kadar performans farkı oluşturulabilir ?” sorusu sizin geliştirdiğiniz Python koduna ve üzerinde çalıştığınız probleme göre değişkenlik gösteren bir konu. Politikacılar gibi çok genel bir cevap verip işin içinden sıyrıldığımı düşünmemeniz için biraz daha detay vermeye çalışacağım. Cython NLP gibi alanlarda veriyi manuel olarak işlemeniz gereken durumlarda size –bizim testlerimizde 2 kata kadar, çeşitli araştırmalarda 30 kata kadar- ciddi performans artış imkanı sunan bir kütüphane. Cython’un en uygun kullanım alanlarıyla ilgili fikirlerimi ayrı bir başlıkta belirttim, okumaya devam etmeye karar verdiğinizde bu konuda daha detaylı bilgiler edineceksiniz.

Bu yazı dizisinin sadece Cython ve Python’un performansına etkisi ile alakalı olmasından ziyade Python’un performans açısından avantajları, dezavantajları ve Cython’un masaya getirdikleri hakkında detaylı bir şekilde ele almaya çalıştım. Konuyu üçe ayırıp serinin ilk yazısında Python dilinin yapısının Python’un performans sorunlarındaki rolünü ve Cython’un teorik olarak bu sorunu nasıl çözmeye çalıştığından bahsedeceğim. Serinin ikinci yazısı genel olarak Cython’a odağa alarak işin teorisinden biraz daha somut olarak Cython’un kullanımından, Python dili ile geliştirme yaparken Cython’u nasıl kullanılabileceğinden ve Cython ile yapılmış çalışmalardaki örnek performans değişimi sonuçlarından bahsedeceğim. Serinin son yazısında Cython ile ilgili kendi yaptığımız testlerden ve sonuçlarından bahsedeceğim.

Teori

Yavaşlık ve Bekleme

Bir programlama dilinin “yavaş” olması için birçok farklı sebep olabilir. Python dilinde programın yavaş çalışma sebepleri 2 başlık altına toplanabilir;

1. I/O işlemleri

2. CPU işlemleri

Veritabanından, lokal dosyadan, API’dan veri okuma gibi işlemler “I/O işlemleri” olarak genellenebilir. Herhangi bir dilde yazılmış program program veri okuma isteği gönderdiği kaynağı veri okuma işlemi bitene kadar beklemek zorundadır. I/O işlemlerinin programda neden olduğu yavaşlığın çözümü programlama dilinden ziyade dış kaynaklarla ilgilidir. Diğer programlama dilleri I/O işlemleri için dönecek yanıtı mevcut programlama dilinden daha hızlı bekleyemez. Yazılmış kodların daha hızlı çalışmasını sağlamak için I/O işlemleri yerine programın CPU işlemleriyle ilgili kısımlarını değiştirmek daha iyi bir çözüm olacaktır.

Dinamik Veri Tipi ve Statik Veri Tipi

Python yapısı gereği dinamik veri tipini kullanan bir programlama dilidir. C, Java, C++ gibi programlama dilleri statik veri tipini kullanır.

Python dilinde değişken tanımı yapıldığında değişkenin veri tipi belirtilmez(örn: sample_3000_df = prep_df.sample(3000) ). Statik veri tipi kullanılan programlama dillerinde değişken tanımlarında mutlaka veri tipine karar verilip programlama esnasında belirtilmelidir(örn: bool kontrol = True ).

Statik veri tipi kullanılan dillerde dizi(array) veri yapılarında dizinin tüm elemanlarının aynı veri tipinde olma zorunluluğu varken dinamik veri tipi kullanılan dillerde dizi(array) veri yapılarında dizinin elemanları farklı veri tiplerinde olabilmektedir. Statik veri tipi kullanılan dillerde dizilerin boyutu dizi tanımında sabit olup sonradan değiştirilemezken –bazı dillerde dizinin boyutu artırılmak/azaltmak isteniyorsa farklı ve daha karmaşık veri yapıları kullanılır- dinamik veri tipi kullanılan dillerde dizilerin boyutu dizi tanımlandıktan sonra artırılıp azaltılabilir.

C dizisinin RAM hafızasında dizilimi(https://dyclassroom.com/c/c-pointers-and-two-dimensional-array)
Python listesinin RAM hafızasında dizilimi(https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/)

Derleme(Compile) ve Yorumlama(Interpret)

Kodu derlemek programı başka bir programlama diline çevirme işlemi için kullanılan tabirdir. C gibi nispeten makine diline yakın ve hızlı dillerde programın kodları derleme aşamasında CPU’nun okuyabileceği makine koduna(machine code) dönüştürülür.

Python kodları makine koduna dönüşmeden önce “byte code” denilen ara bir forma dönüştürülür. “Byte Code” programın çalışma(runtime) sırasında derleyici tarafından CPU’nun anlayacağı makine kodlarına dönüştürülür.

Python dilinde veri tipleri program çalıştıktan sonra belirlendiği için bytecode ara dönüşümünü yapmak zorundadır. Çalışma anında Byte Code makine koduna dönüştürme işlemi Python dilinin nispeten yavaş çalışma sebeplerinden biridir.

Single-Thread ve Multi-Thread

CPU çekirdeklerinin ve threadlerin kullanımı programların çalışma hızını önemli ölçüde etkileyebilen faktörlerdir. Çoklu CPU ve çoklu thread üzerinde çalışabilen programlar tek çekirdek ve tek thread üzerinde çalışabilen programlara göre çok daha kısa zamanda işlemlerini tamamlayabilirler.

Java gibi dillerin aksine Python tasarımından dolayı tek çekirdek(CPU) ve tek thread üzerinde çalışabilen bir programlama dilidir. Python programları çalışırken Ram kısmında hafıza yönetimi yapan “Garbage Collector” arka planda işi biten nesneleri kaldırıp Ram hafızasını verimli bir şekilde kullanabilmemize olanak sağlar. Garbage Collector Python dilinin bileşeni olmayıp genel olarak programlama dillerinin -bazılarında bu işlem manuel yapılıyor- arka planda sunduğu bir imkandır.

Python dili özelinde Garbage Collector yapısının sağlıklı bir şekilde çalışabilmesi için herhangi bir nesnenin(değişken vs) program çalışırken kodun hangi kısımlarında aktif olarak kullanıldığı bilgisini “reference counter” benzeri bir yapıda saklaması gerekmektedir.

Çoklu thread(multi thread) kullanılan yapılarda bu reference counter yapısı aynı anda birden fazla kez değişebileceği için bu durum Garbage Collector yapısının hatalı çalışmasına neden olabilir. Garbage Collector Ram hafızasında gereksiz alan kullanabilir veya halihazırda kullanılan alanları serbest bırakarak programın ihtiyaç duyduğu nesnelerin hafızadan silinmesine neden olabilir. Garbage Collector yapısının sağlıklı çalışabilmesi için Python dilini yazanlar tek thread üzerinde çalışan bir yapı inşa etmişlerdir(bkz: GIL: Global Interpreter Lock).

Aşağıdaki örneklerde C dilinde ve Python dilinde değişken tanımlarının Ram’de nasıl göründüğüne dair örnekler paylaşılmıştır.

C dilinde 42 atanmış integer değişken tanımının Ram’de görüntüsü(https://towardsdatascience.com/why-is-python-so-slow-and-how-to-speed-it-up-485b5a84154e)
Python dilinde 42 atanmış integer değişken tanımının Ram’de görüntüsü(https://towardsdatascience.com/why-is-python-so-slow-and-how-to-speed-it-up-485b5a84154e)

Python dilinde Interpreter(yorumlayıcı) “py_num” şeklinde bir alan tanımladıktan sonra program çalışmaya başladığında py_num değişken alanına veri tipi ataması yapılır ve Ram bölgesinden ilgili hafıza bölgesi tahsis edilir ardından Byte Code makine koduna dönüştürülür.

Python kısmında yer alan “refcount” alanı Garbace Collector yapısına verinin kodun hangi kısımlarında kullanıldığı bilgisini tutar. Bu refcount kısmının sağlık bir biçimde artırılıp azaltılması için python single cpu ve thread üzerinde çalışacak şekilde tasarlanmıştır.

Daha önce oluşturulmuş değişkene yeni veri atanmak istendiğinde Python dillinde Ram’de sıfırdan bir obje oluşturulup değişkenin Interpreter’da isim alanıyla bağdaştırmak gerekir. C gibi statik değişken tanımı kullanılan dillerde daha önce Ram’de tanımlanan hafıza alanındaki değer değiştirilerek bu işlem tamamlanır. Statik veri tipi kullanan programlama dillerinde değişkenler üzerinde yapılan işlemler dinamik veri tipi kullanan programlama dillerine göre çok daha hızlı icra edilir.

Cython

Cython Python programlama dilindeki CPU bazlı yavaşlamayı minimize etmek için geliştirilmiş bir projedir. Cython, Python ve C, C++ gibi dillerin ara formu gibidir. Cython sayesinde Python kodları içine C, C++ kodları yazılabilir ve “.py” uzantılı Python dosyaları yüksek performanslı C dili derleyicisi ile derlenerek makine kodlarına dönüştürülür.

Python programları makine koduna dönüşmeden önce ara forma dönüştürüldüğü için derlenmiş dosyalar tersine mühendislik(reverse engineering) ile derlenmemiş Python kodlarına dönüştürülebilir. Cython sayesinde C derleyicisi derlenmiş Python dosyalarında ise tersine mühendislik mümkün olmadığından Python kodlarına erişim riski ortadan kalkar.

Python dilinde yazılan kodların C dilinde derlenebilmesi için syntax olarak C/C++ dili syntax kurallarının bir kısmının programlama esnasında kullanılması gerekmektedir. Cython projesi, Python syntaxı ile yazılmış kodların arasında C/C++ syntaxı ile yazılmış kodları kullanabilmeyi mümkün kılar.

Bu yazıda Python dilinin diğer dillerden farkını; hafıza yönetimi, değişken tanımları, dilin derlenme süreçleri gibi farklı pencerelerden etraflıca anlatmaya çalıştım. Teorik bilgilerin ardından yazının sonuna Python’daki yavaşlık sorununu çözmeyi amaçlayan Cython’dan bahsettim.

Serinin ikinci yazısında genel olarak Cython’u odağa alarak Cython’un kullanımına ve Python dili ile geliştirme yaparken Cython’u nasıl kullanılabileceğini anlatacağım. Yazının sonunda Cython’un resmi dokümantasyonunda yer alan Cython ile yapılmış örnek performans testisonuçlarından bahsedeceğim.

Konuyla ilgili değerlendirmelerinizi, düzeltmelerinizi bu yazının altıına yorum olarak yazarsanız çok sevinirim. Serinin ikinci yazısında görüşmek üzere kendinize iyi bakın ve Cython’u merak edin :)

Kaynakça

https://dyclassroom.com/c/c-pointers-and-two-dimensional-array

https://jakevdp.github.io/PythonDataScienceHandbook/02.01-understanding-data-types.html

https://towardsdatascience.com/why-is-python-so-slow-and-how-to-speed-it-up-485b5a84154e

--

--