Performans odaklı kod yazı dizisinin 2. bölümünde otomatik arabirim oluşturulan native modulleri tartışacağız. Bir önceki performans odaklı kod tartışmamızda Python özelinde yaşanan performans sorununu çözmek için öncelikli olarak hesaplama alanlarında python doğal döngüsü kullanmamaya dikkat etmek, JIT compiler kullanmak(numba) ve numpy, pandas, tensorflow vb. modulleri kullanmaya çalışmak gibi yöntemlere başvurmak büyük oranda sorununuzu çözecektir demiştik. İlgili yazıya aşağıdaki linkten ulaşabilirsiniz.
Boost.Python
Boost.Python C++ ve Python dilleri arasında ortak çalışma arayüzü sağlayan zengin bir kütüphanedir. Boost.Python C++ kaynak kodunu eşlenik python paternlerine dönüştürür. Resmi sitesinde bahsedildiği üzere de aşağıdaki yapı dönüşümlerini gerçekleştirir:
- İşeretçileri referansları python referanslarına dönüştürebilir
- C++ temel tip dönüşümlerini yapabilir
- C++ modülleri arası dönüşümü python modül dönüşümüne çevirebilir
- Fonksiyon aşırı yüklemelerini(function overloading) dönüştürebilir
- C++ exception sınıflarını python exception sınıflarına dönüştürür
- Varsayılan parametreleri kullanabilir
- Anahtar değer(keyword) argüman dönüşümlerini yapabilir
- C++ üzerinden python nesnelerini manipüle edebilir
- C++ iteratörlerini Python iteratörlerine çevirebilir
- Dökümantasyon dönüşümünü yapabilir
Açıkçası bu kadar özelliği duyunca elimi kodla kirletmek için heyecanlandığımı söyleyebilirim.
Nereden başlayabilirim?
Aslında bu soruyu ben de sordum. Kendimi native dil uzmanı olarak görmüyorum. Fakat sık sık da C/C++ performansına veya kütüphanelerine ihtiyaç duyduğum için native modül geliştirmenin en hızlı yolunu devamlı araştırmaktayım. Normal şartlarda hem temiz bir CI/CD paradigması geliştirmek hem de bakım maliyetlerinizi azaltmak için ikincil bağımlılıklarınızın(modül, paket vb.) üçüncü bir bağımlılık yaratmaması güzel bir geliştirme alışkanlığıdır. Örneğin; uygulamanızın içerisinde OpenCV kullanıyorsunuz. Bunun derlemesini tamamen pip paket yükleme esnasında yapmak, python-dev ve opencv ubuntu paketlerini elle yüklemekten daha az maliyetlidir. Bu şekilde yaklaşım, ölçeklenen/büyüyen uygulamanızın oluşturduğu DevOps yapı değişikliklerinde oldukça zaman kazandırır. Şu anda bu yöntemi açıkçası uygulayamayacağız. Sebebiyse C++ için CI senaryosunu geliştirmeyi işleyemeyeceğiz.
Boost kütüphaneleri C++ özelinde kullanılan kütüphaneler bütünüdür. Java’nın Spring’idir. C++ versiyonları arasında cross platform bir standart oluşturan, dizayn paternlerine zorlayan standartlaştırma adımıdır diyebiliriz.
Boost Python ise Python için kapsayıcı moduller(wrapper) yazmamızı sağlayan bir pakettir.
Python 2x versiyonu artık kullanımda olmadığı için Python 3x üzerinde bu geliştirmeyi gerçekleştireceğiz. Bir önceki C modülü yazımızda derleme sonuçlarını bulunulan çevre içine paket olarak eklediği için içinde bulunulan virtualenv üzerinde saklamaktaydı fakat Boost.Python çıktısını sistem yolları üzerine modül olarak kaydetmekte. İzolasyonuna denk geldiğimde/ihtiyaç duyduğumda bu yazıya eklemeyi planlıyorum. Tabi geliştirmeyi derleyebilmeniz için python3-dev paketi de yüklü olmalı.
Python 2.x üzerinde yine de çalışmak istiyorum diyenler için bnim de büyük oranda kaynak olarak kullandığım Interfacing C++ and Python with Boost.Python yazısını önermekteyim.
sudo apt-get install libboost-all-dev
mkdir boostpython
cd boostpython
Boost dev paketi yüklenecek ve boostpython klasörü içerisinde kodlarımız olacak.
mkdir cmake
mkdir cmake/Modules
cmake/Modules içerisinde custom cmake modülümüz bulunacak.
cmake/Modules/BuildBoost.cmake dosyasını açın aşağıdaki cmake scriptini ekleyin.
#========================================================#
# Build the Boost dependencies for the project using a specific version of python #
#========================================================#
set(BoostVersion 1.73.0)
set(BoostMD5 9273c8c4576423562bbe84574b07b2bd)
string(REGEX REPLACE "beta\\.([0-9])$" "beta\\1" BoostFolderName ${BoostVersion})
string(REPLACE "." "_" BoostFolderName ${BoostFolderName})
set(BoostFolderName boost_${BoostFolderName})
ExternalProject_Add(Boost
PREFIX Boost
URL http://sourceforge.net/projects/boost/files/boost/${BoostVersion}/${BoostFolderName}.tar.bz2/download
URL_MD5 ${BoostMD5}
CONFIGURE_COMMAND ./bootstrap.sh
--with-libraries=python
--with-python=${PYTHON_EXECUTABLE}
BUILD_COMMAND ./b2 install
variant=release
link=static
cxxflags='-fPIC'
--prefix=${CMAKE_BINARY_DIR}/extern
-d 0
-j8
INSTALL_COMMAND ""
BUILD_IN_SOURCE 1
)
set(Boost_LIBRARY_DIR ${CMAKE_BINARY_DIR}/extern/lib/ )
set(Boost_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extern/include/boost/ )
if(${PYTHON_VERSION_STRING} GREATER 3.0)
message(STATUS "Using Python3")
set(Boost_LIBRARIES -lboost_python3 -lboost_numpy)
else()
message(STATUS "Using Python2")
set(Boost_LIBRARIES -lboost_python -lboost_numpy)
endif()
CMakeLists.txt dosyasını oluşturalım ve içerisine aşağıdaki kodu yapıştıralım.
cmake_minimum_required(VERSION 2.8)
include(ExternalProject)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/")
project(tutorial)
# Find default python libraries and interpreter
find_package(PythonInterp REQUIRED)
find_package(PythonLibs REQUIRED)
include(BuildBoost) # Custom module
include_directories(${Boost_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIR})
# Build and link the pylib module
add_library(pylib SHARED pylib.cpp)
target_link_libraries(pylib ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
add_dependencies(pylib Boost)
# Tweaks the name of the library to match what Python expects
set_target_properties(pylib PROPERTIES SUFFIX .so)
set_target_properties(pylib PROPERTIES PREFIX "")
Yukarıdaki Cmake modülü ve CMakeLists.txt dosyası büyük oranda https://github.com/EiffL/Tutorials/ reposundan alındı.
Merhaba.hpp isimli dosyayı oluşturun ve aşağıdaki kodu örnek olarak ekleyebilirsiniz.
#include <iostream>
#include <string>
class Merhaba
{
// Private
std::string m_msg;
public:
// Constructor
Merhaba(std::string msg):m_msg(msg) { }
// Metodlar
void yazdir(int times) {
for(int i=0; i<times; i++)
std::cout << m_msg << std::endl;
}
// Getter/Setter f
void set_msg(std::string msg) { this->m_msg = msg; }
std::string get_msg() const { return m_msg; }
};
Merhaba sınıfının metodlarını sınıfını modüle kaydetmemiz gerekiyor. Bunun için pylib.cpp dosyasını oluşturun ve aşağıdaki gibi modülünüzü kaydedin.
#include <boost/python.hpp>
#include "Merhaba.hpp"
using namespace boost::python;
BOOST_PYTHON_MODULE(pylib)
{
class_< Merhaba >("Merhaba", init<std::string>())
.def("yazdir", &Merhaba::yazdir)
.add_property("msg", &Merhaba::get_msg, &Merhaba::set_msg);
}
Modülünüzün ismiyle CMakeLists.txt içerisindeki ismin aynı olması önemli. Python 2 versiyonunda doğrudan derleme yapabiliyorsunuz fakat python3 versiyonunda GCC’nin bilmediğim bir sebepten pydev paketlerini görmemesinden ötürü manuel olarak include klasörünü göstermeniz gerekmekte.
mkdir build
python3 -m virtualenv venv
source venv/bin/activate
export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/usr/include/python3.6m/"
cmake ..
make pylib
3.6m versiyon gösterimi py-malloc ile çalıştığı için heap allocation işlemlerinde çok daha fazla performans sunacaktır. Ama arzu ederseniz tabiki de m uzantılı olmayan include klasörlerini gösterebilirsiniz.
Sonuç gayet güzel görünüyor. Modülümüzü tanımladık ve derledik. Peki ya tanımlamaların otomatik yapılmasını istiyorsak?
Py++ Otomatik Boost.Python Tanımlamaları
Boost.Python üzerinde Py++ scripti çalıştırarak C++ modülünüze göre otomatik tanımlamalar yaptırabilirsiniz. Referans olarak kullandığımız ve güncellediğimiz makalenin verdiği bilgiye göre Py++ Open Motion Planning Library altında geliştiriliyor ve güncel olarak dökümantasyonu yapılıyor. Başlamadan önce virtualenv içerisindeyken Py++ bağımlılıklarını yüklememiz gerekiyor.
sudo apt-get install castxml
pip install pygccxml
pip install pyplusplus
pylib_generator.py isimli bir dosya oluşturun ve aşağıdaki kodu yapıştırın.
#!/usr/bin/python
from pygccxml import parser
from pyplusplus import module_builder
# Configurations that you may have to change on your system
generator_path = "/usr/bin/castxml"
generator_name = "castxml"
compiler = "gnu"
compiler_path = "/usr/bin/gcc"
# Create configuration for CastXML
xml_generator_config = parser.xml_generator_configuration_t(
xml_generator_path=generator_path,
xml_generator=generator_name,
compiler=compiler,
compiler_path=compiler_path)
# List of all the C++ header of our library
header_collection = ["Merhaba.hpp"]
# Parses the source files and creates a module_builder object
builder = module_builder.module_builder_t(
header_collection,
xml_generator_path=generator_path,
xml_generator_config=xml_generator_config)
# Automatically detect properties and associated getters/setters
builder.classes().add_properties(exclude_accessors=True)
# Define a name for the module
builder.build_code_creator(module_name="pylib_auto")
# Writes the C++ interface file
builder.write_module('pylib_auto.cpp')
CMakeLists.txt altına aşağıdaki satırları ekleyelim ki pylib_auto modülümüz de derlenebilsin.
# Build and link the pylib_auto module
add_library(pylib_auto SHARED pylib_auto.cpp)
target_link_libraries(pylib_auto ${Boost_LIBRARIES} ${PYTHON_LIBRARIES})
add_dependencies(pylib_auto Boost)
# Tweaks the name of the library to match what Python expects
set_target_properties(pylib_auto PROPERTIES SUFFIX .so)
set_target_properties(pylib_auto PROPERTIES PREFIX "")
build klasörü içerisindeyken aşağıdaki adımları uygulayalım.
cmake ..
make pylib_auto
Python konsolu üzerinden deneme yaptığımızda gayet sağlıklı görünüyor.
Boost.Python ve Py++ bir araya geldiğinde karmaşık modülleri Python üzerine taşımak için gayet güçlü bir araç sağlanmış oluyor. Açıkçası C++ uzmanı değilim fakat modül geliştirmelerinde native performanstan yararlanmak güzel bir yaklaşım olacaktır.
Hepinize iyi kodlamalar 😁
Comments