deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak Hacker News – Front Page (hnrss.org)

PyO3 turu, pydantic-core gibi Rust eklentilerinin Python'u nasıl beslediğini gösteriyor

belderbos.dev'de yayımlanan bir eğitim, PyO3 ve maturin ile Rust kodunun Python'a nasıl açıldığını ve büyük sonuçların Python nesnelerine dönüştürülmesinin hesaplamanın kendisinden daha maliyetli olabileceğini anlatıyor.

PyO3 turu, pydantic-core gibi Rust eklentilerinin Python'u nasıl beslediğini gösteriyor

belderbos.dev'de yayımlanan ve şu anda Hacker News ana sayfasında yer alan uygulamalı bir rehber, PyO3 kullanarak Rust kodunun Python'a nasıl gömüldüğünü adım adım anlatıyor — PyO3, Pydantic v2'de doğrulamayı yapan Rust motoru olan pydantic-core'un da arkasındaki framework. Örnek proje bilinçli olarak küçük: Rust ile yazılmış ve herhangi bir sıradan Python paketi gibi içe aktarılabilen bir JSON parser. Kalıcı ders ise performansın gerçekte nerede harcandığıyla ilgili.

Rust'tan import'a dört adım

Yazıya göre bir Rust crate'inden çalışan bir Python modülüne giden yol dört aşamadan oluşuyor: normal bir Rust modülü yazmak, onu PyO3 makrolarıyla işaretlemek, maturin ile derleyip kurmak ve sonra sonucu import etmek.

Bağlantıyı iki makro kuruyor. #[pyfunction] ve #[pymodule] attribute makrolarıdır; yazar bunları Python decorator'larına benzetiyor çünkü bulundukları fonksiyonu yeniden yazarak Python'un Rust'ı çağırmasını sağlayan yapıştırmayı ekliyor ve diller arasındaki geçişte tip dönüşümleriyle reference counting'i yönetiyorlar. Maturin ardından crate'i paylaşımlı bir kütüphaneye derliyor — platforma göre .so, .dylib veya .dll — ve doğrudan virtual environment içine kuruyor; böylece sıradan bir import, ek tesisat gerektirmeden çalışıyor.

Parse Rust tarafında kalıyor

Örnek parser çıktısını düz bir Rust enum'u olarak kuruyor; varyantlar null, boolean, sayı, string, dizi ve nesneleri kapsıyor. Bu ağaç tamamen Rust tarafında yaşıyor; Python onu doğrudan hiç görmüyor. Üstteki PyO3 katmanı ince bir dönüşüm ara katmanı olarak tanımlanıyor.

Açığa çıkan fonksiyon kompakt. Interpreter'ı temsil eden bir token alıyor (Python<'py>), parse işlemini çalıştırıyor ve PyResult döndürüyor — bu da hata tipi olarak PyErr kullanan bir Result'tır; böylece başarısız bir parse, Rust hata değeri yerine Python'da exception olarak ortaya çıkar. Gerçek iş parse çağrısında olur; sondaki .into_pyobject adımı ise çağıran tarafın istediği Python nesnelerini kurandır.

Maliyetli olan dönüş yolculuğudur

İşte o son adım yazının kalbi. PyO3'nun IntoPyObject trait'ini uygulamak, bitmiş Rust ağacını dolaşmayı ve onu yerel Python nesneleri olarak yeniden kurmayı gerektiriyor: her JSON nesnesi için bir dict, her dizi için bir list, her yaprak değer için bir float ya da string. 100.000 değer içeren bir belge bu nedenle sınırda kabaca 100.000 Python nesnesi tahsisini tetikliyor — hepsi de parse işlemi bittikten sonra. Yazıya göre büyük belgelerde, uçtan uca süreyi belirleyen bu materialisation döngüsü olabilir, parser'ın kendisi değil.

Pratik öneri de buradan geliyor. Taşınan fonksiyon bir scalar döndürüyorsa sınır göz ardı edilecek kadar küçüktür. Büyük bir yapı döndürüyorsa, algoritma hızlandıktan sonra optimize edilecek sıradaki şey dönüşümün kendisidir. Dictionary'leri önceden tahsis etmek ancak kenarlarda yardımcı olur; asıl büyük kazanç mimaridir — tembel, Rust destekli bir view döndürmek ve Python nesnelerini yalnızca çağıran taraf onlara gerçekten dokunduğunda kurmak.

Hatalar da aynı köprüden geçiyor

Çevrilmesi gereken tek şey dönüş değerleri değil. Parser'ın Rust hata tipinden PyErr'ye tek bir From implementasyonu, question-mark operatörünün işi yapmasını sağlıyor: hatalı girdi, parse'ın kırıldığı konumu taşıyan bir ValueError yükseltiyor; her hata varyantı için bir match kolu var. Dosya işlemleri bedavaya gelir çünkü std::io::Error zaten karşılık gelen Python exception'ına eşleniyor — olmayan bir yol FileNotFoundError yükseltiyor. Çağıranlar, Rust katmanı sızmadan yerel Python semantiğine kavuşuyor.

Yazı ayrıca yazarın Python'dan Rust'a eğitiminden sonuçlara değiniyor; öğrenciler tam da bu tür bir parser kuruyor: bir öğrencinin versiyonunun gerçek dünya verilerinde CPython'ın C modülünü geçtiği, bir diğerinin saf Python karşılığından 3.5 kata kadar daha hızlı çalıştığı bildiriliyor. Bunlar yayımlanmış bir metodoloji olmadan kişisel bildirimler, ama desenin neden ilgi çektiğini gözler önüne seriyorlar.

Neden önemli

PyO3 meraklılık değil. Birçok Python uygulamasının dayandığı veri doğrulama kütüphanesi Pydantic v2 hızını PyO3 ile derlenmiş bir çekirdekten alıyor; yani bu mimari yaygın olarak kullanılan araçların altında yatıyor. Benzer bir taşıma düşünen ekipler için rehber sorunu yeniden çerçeveliyor: Rust'ı hızlı çalıştırmak daha kolay yarı. Taşınmanın ödeyip ödemeyeceğine karar veren yarı ise çıkış yolunda yaşanan — Rust değerlerinden Python nesnelerinin inşası. Bu, profillemenin yalnızca algoritmayı değil dönüşüm katmanını da kapsaması gerektiği, ve bütün yapıları materialize etmekten kaçınan tasarımların genellikle yalnızca hızlı parse edenlerden daha iyi sonuç vereceği anlamına geliyor.

  • #rust
  • #python
  • #pyo3
  • #extensions
  • #performance

İlgili yazılar