Python’un Performansı ve Cython — Bölüm 1

Enes Mesut Yıldız
Akbank Teknoloji
Published in
6 min readJul 21, 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 dilin C#, Java gibi diğer yüksek seviyeli dillere göre performans açısından kötü bir izlenime sahip olduğunu mutlaka duymuştur. Python’un 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ızdan dolayı, Python programlama dili ile öyle veya böyle bir şekilde yolunuzun kesiştiğini varsayıyorum. 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ı dizisinde sadece Cython ve Python’un performansa etkisini değil, Python’un performans açısından avantajlarını, dezavantajlarını ve Cython’un masaya getirdiklerini de detaylı bir şekilde ele almaya çalıştım. Konuyu üçe ayırıp serinin ilk yazısında Python dilinin yapısının performans sorunlarındaki rolünü ve Cython’un teorik olarak bu sorunu nasıl çözmeye çalıştığından bahsedeceğim. Serinin ikinci yazısında Cython’u odağa alarak işin teorisinden, biraz daha somut olarak Cython’un kullanımından, Python dili ile geliştirme yaparken Cython’un 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, 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. (Örneğin; 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. (Örneğin; 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/azaltılmak 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 byte code 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 thread’lerin 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ığı bilgisinin “reference counter” benzeri bir yapıda saklanması gerekir.

Ç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ızasından 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ında verinin kodun hangi kısımlarında kullanıldığı bilgisini tutar. Bu refcount kısmının sağlıklı 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şkeni de 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, C ve 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ülebilir.

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. C derleyicisiyle derlenmiş Python dosyalarında ise tersine mühendislik mümkün olmadığından Cython sayesinde 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 bakarak etraflıca anlatmaya çalıştım. Teorik bilgilerin ardından yazının sonunda 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 değinecek ve Python dili ile geliştirme yaparken Cython’un nasıl kullanılabileceğini anlatacağım. Yazının sonunda ise Cython’un resmi dokümantasyonunda yer alan Cython ile yapılmış örnek performans testi sonuçlarından bahsedeceğim.

Konuyla ilgili değerlendirmelerinizi, düzeltmelerinizi bu yazının altına yorum olarak yazabilirsiniz.

Cython’u merak etmeye devam edin :)
Serinin ikinci yazısında görüşmek üzere…

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

--

--