İçeriğe geç →

Python Performans Odaklı Kod|C/C++ Eklentisi Oluşturma

Python neden yavaştır? Neden python dünyanın çok tercih edilen programlama dillerinden biri olmasına rağmen, basit bir for döngüsünde dahi eşlenik bir C/C++, Java işlevinden neredeyse 100 kat daha yavaş performans sunar? Sebebi oldukça basit aslında. Python yorumlayıcı tabanlı bir dildir. Yorumlayıcı tabanlı bir dil olmasının dezavantajı bulunmakla beraber bu dezavantajı python C/C++ eklentisi oluşturarak atlatabiliriz.

Nedir bu yorumlayıcı (interpreter)?

Yorumlayıcı tabanlı diller derleme tabanlı dillerin aksine çalışma zamanında yorumlanarak çalıştırılır ve işleyiciyle arasında yorumlayıcıya ihtiyaç duyar. Günümüzde sistem kaynaklarının artmasıyla beraber yorumlayıcı tabanlı dillere de rağbet artmıştır. Nitekim stackoverflow geliştirici araştırması da bunu doğrulamaktadır. (Bkz: https://jaxenter.com/stack-overflow-dev-survey-2019-157815.html )

Üçüncü parti kütüphaneler

Python üzerinde derleyici tabanlı dil performansına erişmek için not almak gereken bazı paradigmalar mevcut. Bunlardan ilki python doğal döngüleri içerisinde hesap yaptırmamak ve bu işlemleri olabildiğince üçüncü parti modüllerle(numpy, pandas etc.) gerçekleştirmek. Python dilinin modül konusunda oldukça gelişmiş olduğunu söylememe gerek yok diye düşünüyorum. Aktif olan topluluk desteği sayesinde uzay hesapları, grafiksel hesaplamalar vb. karmaşık uygulamalar için dahi oldukça geniş bir modül perspektifi mevcut.

Implementasyonu tamamen C üzerinde yapılan numpy ve pandas hesaplama ve veri operasyonları işlemlerinde neredeyse bütün ihtiyacınızı karşılayacaktır. Bunun yanında tensorflow gibi tensor hesaplama kütüphaneleriyle GPU üzerinde işlemler de planlayabilirsiniz.

JIT Derleyici (Just in Time)

JIT tekrar kullanılacak kod bloklarını derleme yöntemi olarak tanımlanabilir. Bu yöntemle yorumlayıcı tabanlı diller özelinde tekrarlanan kod bloklarını ikili olarak derleyebilir, böylelikle performans artışı sağlayabilirsiniz.

Python özelinde numba kütüphanesiyle bu işlemi gerçekleştirebilirsiniz.

Hiçbiri İşime Yaramadı Diyorsanız…

Eğer perfomansın da ötesinde bir probleminiz bulunuyorsa veya python işlevleri size yetemeyecek duruma geldiyse C/C++ ile modül geliştirebilirsiniz.

C modulleri doğrudan python üzerine entegre edilebilir olduğu için kapsayıcı ara programlara ihtiyaç duymamaktadır. Yani C dili ile yazılmış bir modülü python kurulumunu Ubuntu için bu linkte bahsedildiği gibi gerçekleştirdiyseniz, sıkıntı yaşamadan derleyebilirsiniz.

Adımlar

C üzerinde long tipinde bir sayı alıp bu sayının faktoriyelini istenen dosyaya yazan bir modül yazalım. Sonuç olarak da yazılan byte sayısını geri döndürsün. Aşağıdaki adımların hepsini Ubuntu 18.04 WSL üzerinde gerçekleştirdim.

Ubuntu WSL konsolu üzerinde aşağıdaki adımları çalıştırın. Yeni bir klasör oluşturacak, mymodule.c dosyasını ekleyecek ve klasörde Visual Studio Code çalıştıracaksınız.

mkdir pythoncmodule
cd pythoncmodule
touch mymodule.c
code .

Modülün testlerini yapmak için virtualenv ile izole ortam sağlamak mantıklı olacaktır.

python3 -m virtualenv wslenv
source wslenv/bin/activate

Environment değiştirkten sonra terminalde de görüldüğü gibi ortam ön eki oluşur ve yükleyeceğimiz modül paketi izole ortam içinde denenmiş olur.

module.c dosyasının içeriği aşağıdaki gibi olmalıdır.

#include <Python.h>
//#include "python3.6/Python.h"
//Geliştirme sırasında intellisense'i kullanabilmek için python3.6 klasöründeki modülü çağırıyoruz
//Python setup modülü derleme sırasında 1. satırdaki header ismiyle bulabilecektir. 

static PyObject *method_factorial(PyObject *self, PyObject *args) {
    char *filename = NULL;
    long number;
    int bytes_copied = -1;

    /* Parametreleri al */
    if(!PyArg_ParseTuple(args, "ls", &number, &filename)) {
        return NULL;
    }
    //Faktoriyel hesapla
    long result = 1;
    for(long i=1; i<=number; ++i){
        result *= i;
    }

    FILE *fp = fopen(filename, "w");
    bytes_copied = fprintf(fp, " %ld", result);
    fclose(fp);

    return PyLong_FromLong(bytes_copied);
}


static PyMethodDef FactorialMethods[] = {
    {"factorial", method_factorial, METH_VARARGS, "Python factorial metodu."},
    {NULL, NULL, 0, NULL}
};


static struct PyModuleDef factorialmodule = {
    PyModuleDef_HEAD_INIT,
    "factorialwriter",
    "Python factorial modülü. ",
    -1,
    FactorialMethods
};

PyMODINIT_FUNC PyInit_factorialwriter(void) {
    return PyModule_Create(&factorialmodule);
}

İlk bakışta biraz karmaşık görünebilir. Adım adım neler yaptık inceleyelim.

#include <Python.h>
//#include "python3.6/Python.h"
//Geliştirme sırasında intellisense'i kullanabilmek için python3.6 klasöründeki modülü çağırıyoruz
//Python setup modülü derleme sırasında 1. satırdaki header ismiyle bulabilecektir. 

Python üzerinde modül geliştirebilmek için Python.h kütüphanesindeki fonksiyon ve yapıları kullanmalısınız. Bu adımda #include <Python.h> önişleme komutuyla kütüphaneyi ekleyebilirsiniz. Geliştirme sırasında intellisense’in düzgün çalışması için 2.satırdaki önişleyiciyi kullanabilirsiniz.


static PyObject *method_factorial(PyObject *self, PyObject *args) {
    char *filename = NULL;
    long number;
    int bytes_copied = -1;

    /* Parametreleri al */
    if(!PyArg_ParseTuple(args, "ls", &number, &filename)) {
        return NULL;
    }
.....

PyObject modül metodunu dönüş sağlayacağı tipi belirtir. filename dosya ismini, number faktoriyel sonucunu bytes_copied ise dosyaya kaç byte yazılacağını tutacaktır.

PyArg_ParseTuple metodu args içerisinde gelen metot parametrelerini C tiplerine parse eder. Python resmi dökümantasyonuna göre ls ise tuple içeriğinin sırayla long ve char* veri tipleri olarak parse edilmesi gerektiğini belirtir.

..........
    //Faktoriyel hesapla
    long result = 1;
    for(long i=1; i<=number; ++i){
        result *= i;
    }

    FILE *fp = fopen(filename, "w");
    bytes_copied = fprintf(fp, " %ld", result);
    fclose(fp);

    return PyLong_FromLong(bytes_copied);
}
.........

Yukarıda gördüğünüz kodda ise faktoriyel hesaplanır ve parametreyle getirilen dosyanın içerisine yazılır. Dönüş ifadesinde gördüğünüz PyLong_FromLong fonksiyonuysa tahmin edeceğiniz üzere C long tipinden python long tipine dönüşüm yapar.

.............
static PyMethodDef FactorialMethods[] = {
    {"factorial", method_factorial, METH_VARARGS, "Python factorial metodu."},
    {NULL, NULL, 0, NULL}
};


static struct PyModuleDef factorialmodule = {
    PyModuleDef_HEAD_INIT,
    "factorialwriter",
    "Python factorial modülü. ",
    -1,
    FactorialMethods
};
............

FactorialMethods dizisinde metotlar tanımlanır. {NULL, NULL, 0, NULL} ifadesi ise dizinin sonuna getirilmesi gereken iterasyon sonlandırma ifadesidir.

Metot içeriğindeki METH_VARARGS gelen parametrelerin değişken olarak geleceğini ve Tuple olarak parse edilmesi gerektiğini belirtir.

factorialmodule struct’ı ise modül metot tanımlamalarını yapar.

..........
PyMODINIT_FUNC PyInit_factorialwriter(void) {
    return PyModule_Create(&factorialmodule);
}

Modül nesnesi oluşturulduktan ve paketlendikten sonra python yorumlayıcısı dinamik olarak PyInit_<modül ismi> olarak paket içerisinde arama gerçekleştirir. Metot ismine de birlikte oluşturacağımız setup.py ile karar verebiliyorsunuz. Biz paketimizin ismini factorialwriter olarak belirledik. PyMODINIT_FUNC ise bu metotun bir EXTERN C ifadesi olduğunu ve C veya C++ farketmeksizin derleyici tabanlı dönüştürmelere girilmeden dinamik kütüphane olarak çıkarılmasını belirtir. Yani C++ ile de bu kütüphaneyi geliştirebilirsiniz.

Paketleme

Geliştirlen modülün python içerisine alınabilmesi için setup.py dosyasını oluşturun ve aşağıdaki kodu ekleyin.


from distutils.core import setup, Extension

def main():
    setup(name="factorialwriter",
          version="1.0.0",
          description="Python interface for the fputs C library function",
          author="Pyturk",
          author_email="[email protected]",
          ext_modules=[Extension("factorialwriter", ["module.c"])])

if __name__ == "__main__":
    main()

Artık modülümüz kurulup kullanılabilir. Kurulum için az önce oluşturduğumuz environment içerisindeyken python setup.py install komutunu çalıştırdığınızda modül kurulacaktır.

Yukarıda da gördüğünüz üzere başarıyla kurulumu yaptık ve modülümüz sorunsuz çalıştı 😃.

Bu kadar mı?

Tabiki de değil. Büyük bir modül geliştirecekseniz bu yöntem biraz hantal gelebilir. Bu hantallığı giderebilmek adına Boost Python veya SWIG gibi 3. parti paketleyiciler mevcut. 3. parti paketleyici kullanarak belki onlarca fonksiyon olan native projeyi python üzerine modül olarak taşımak için daha az efor sarfedeceksiniz. Bunun yanında .so veya .dll olarak çıkartılan dinamik kütüphanelere erişim için de ctypes resmi paketi bulunmakta. Sonraki yazımızda da alternatif paketleyicilerle C++ sınıflarına wrapper class yazmayı öğreneceğiz.

İyi kodlamalar 😃

Kategori: Gelişmiş Python

Yorumlar

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir