Python betiğimin eşdeğer bir C ++ programı kadar hızlı olmasını engelleyen teknik sınırlamalar veya dil özellikleri var mı?


10

Ben uzun zamandır Python kullanıcısıyım. Birkaç yıl önce, hız açısından neler sunabileceğini görmek için C ++ öğrenmeye başladım. Bu süre zarfında, Python'u prototipleme aracı olarak kullanmaya devam edeceğim. Bu, iyi bir sistemdi: Python ile çevik geliştirme, C ++ 'da hızlı uygulama.

Son zamanlarda, Python'u tekrar tekrar kullanıyorum ve önceki yıllarda dil ile hızlıca kullandığım tuzaklardan ve anti-kalıplardan nasıl kaçınacağımı öğreniyorum. Anladığım kadarıyla, belirli özelliklerin (liste kavrayışları, numaralandırmalar, vb.) Kullanılması performansı artırabilir.

Python betiğimin eşdeğer bir C ++ programı kadar hızlı olmasını engelleyen teknik sınırlamalar veya dil özellikleri var mı?


2
Evet yapabilir. Python derleyicilerindeki son teknoloji için PyPy'ye bakınız .
Greg Hewgill

5
Python'daki tüm değişkenler polimorfiktir, yani değişkenin türü sadece çalışma zamanında bilinir. C benzeri dillerde (+ tamsayı varsayarak) x + y görürseniz, bir tamsayı eklemesi yaparlar. Python'da x ve y değişken tiplerinde bir anahtar olacak ve daha sonra uygun ekleme fonksiyonu seçilecek ve daha sonra bir taşma kontrolü olacak ve ardından ekleme yapılacaktır. Python statik yazmayı öğrenmediği sürece bu ek yük asla ortadan kalkmayacaktır.
nwp

1
@nwp Hayır, bu kolay, PyPy'ye bakın. Hala açık olan sorunlara şunlar dahildir: JIT derleyicilerinin başlatma gecikme süresinin üstesinden nasıl gelileceği, karmaşık uzun ömürlü nesne grafikleri için tahsislerin nasıl önleneceği ve önbelleğin genel olarak nasıl kullanılacağı.

Yanıtlar:


11

Birkaç yıl önce tam zamanlı bir Python programlama işi aldığımda bu duvara çarptım. Python'u seviyorum, gerçekten yapıyorum, ancak bazı performans ayarlamaları yapmaya başladığımda, bazı kaba şoklar yaşadım.

Katı Pythonistalar beni düzeltebilir, ama işte bulduğum şeyler, çok geniş vuruşlarla boyanmış.

  • Python bellek kullanımı biraz korkutucu. Python her şeyi bir dikte olarak temsil eder - bu son derece güçlüdür, ancak basit veri türlerinin bile devasa bir sonucu vardır. "A" karakterinin 28 bayt bellek aldığını hatırlıyorum. Python'da büyük veri yapıları kullanıyorsanız, doğrudan bayt dizisi uygulamasıyla desteklendiği için numpy veya scipy'e güvendiğinizden emin olun.

Bunun performans etkisi vardır, çünkü bu, diğer dillere kıyasla çok fazla miktarda belleğin yanmasına ek olarak, çalışma zamanında fazladan dolaylama düzeyleri olduğu anlamına gelir.

  • Python'un global bir yorumlayıcı kilidi vardır, bu da çoğunlukla süreçlerin tek iş parçacıklı çalıştığı anlamına gelir. Görevleri süreçler arasında dağıtan kütüphaneler olabilir, ancak python betiğimizin 32 veya daha fazla örneğini döndürüyor ve her bir parçayı çalıştırıyorduk.

Diğerleri yürütme modeliyle konuşabilir, ancak Python çalışma zamanında derlenir ve daha sonra yorumlanır, bu da makine koduna kadar gitmediği anlamına gelir. Bunun da bir performans etkisi var. C veya C ++ modüllerine kolayca bağlayabilir veya bulabilirsiniz, ancak Python'u doğrudan çalıştırırsanız, bir performans isabeti olacaktır.

Şimdi, web hizmeti kıyaslamalarında Python, Ruby veya PHP gibi diğer çalışma zamanında derleme dillerini olumlu bir şekilde karşılaştırır. Ancak derlenen dillerin çoğunun oldukça gerisindedir. Ara dile derlenen ve bir VM'de (Java veya C # gibi) çalışan diller bile çok daha iyi sonuç verir.

İşte zaman zaman bahsettiğim gerçekten ilginç bir kıyaslama testleri seti:

http://www.techempower.com/benchmarks/

(Bütün bunlar, hala Python'u çok seviyorum ve çalıştığım dili seçme şansına sahip olursam, bu benim ilk seçimim. Çoğu zaman, zaten çılgın verim gereksinimleriyle kısıtlanmıyorum.)


2
"A" dizgisi ilk madde işareti noktası için iyi bir örnek değildir. Bir Java dizgisi ayrıca tek karakter dizeleri için önemli bir ek yüke sahiptir ve ancak dize uzunluğu arttıkça (amortismana, derleme seçeneklerine ve dize içeriğine bağlı olarak bir ila dört baytlık karakter) oldukça iyi bir şekilde amortismana tabi tutulur. Yine de kullanıcı tanımlı nesneler konusunda haklısınız, en azından kullanmayan nesneler __slots__. PyPy bu konuda çok daha iyisini yapmalı ama yargılayacak kadar bilgim yok.

1
Belirttiğiniz ikinci sorun yalnızca belirli uygulamalarla ilgilidir ve dile özgü değildir. İlk sorun açıklama gerektirir: 28 bayt "ağırlığında" olan şey karakterin kendisi değil, kendi yöntem ve özellikleriyle gelen bir dize sınıfında paketlenmiş olmasıdır. Tek karakteri bayt dizisi olarak temsil eden (değişmez b'a ') "yalnızca" Python 3.3'te 18 bayt ağırlığındadır ve uygulamanızın gerçekten ihtiyacı varsa bellekte karakter depolamasını optimize etmenin daha fazla yolu olduğundan eminim.
Kırmızı

C # yerel olarak derleyebilir (örn. Yaklaşan MS teknolojisi, iOS için Xamarin).
Den

13

Python referans uygulaması “CPython” yorumlayıcısıdır. Oldukça hızlı olmaya çalışır, ancak şu anda gelişmiş optimizasyonları kullanmamaktadır. Ve birçok kullanım senaryosu için, bu iyi bir şeydir: Bazı aracı kodların derlenmesi çalışma süresinden hemen önce gerçekleşir ve program her yürütüldüğünde kod yeniden derlenir. Dolayısıyla, optimizasyon için gereken zamanın optimizasyonlarla kazanılan zamana göre tartılması gerekir - net bir kazanç yoksa optimizasyon değersizdir. Çok uzun süren bir program veya çok sıkı döngülere sahip bir program için, gelişmiş optimizasyonların kullanılması yararlı olacaktır. Ancak, CPython agresif optimizasyonu engelleyen bazı işler için kullanılır:

  • Kısa süreli komut dosyaları, örneğin sysadmin görevleri için kullanılır. Ubuntu gibi birçok işletim sistemi altyapılarının önemli bir bölümünü Python üzerine inşa eder: CPython iş için yeterince hızlıdır, ancak neredeyse hiç başlatma süresi yoktur. Bash'den daha hızlı olduğu sürece iyidir.

  • CPython, referans bir uygulama olduğu için açık bir anlambilime sahip olmalıdır. Bu, “foo operatörünün uygulanmasını optimize etme” veya “liste anlamalarını daha hızlı bayt koduna derleme” gibi basit optimizasyonlara izin verir, ancak genellikle satır içi işlevler gibi bilgileri yok eden optimizasyonları engeller.

Tabii ki, sadece CPython'dan daha fazla Python uygulaması var:

  • Jython, JVM'nin üzerine inşa edilmiştir. JVM, sağlanan bayt kodunu yorumlayabilir veya JIT olarak derleyebilir ve profil kılavuzlu optimizasyonlara sahiptir. Yüksek başlatma süresinden muzdariptir ve JIT devreye girene kadar biraz zaman alır.

  • PyPy son teknoloji ürünü, JITting Python VM. PyPy, Python'un kısıtlı bir alt kümesi olan RPython'da yazılmıştır. Bu altküme, Python'dan bazı ifadeleri kaldırır, ancak herhangi bir değişkenin türünün statik olarak çıkarılmasına izin verir. RPython ile yazılmış VM daha sonra RPython C benzeri performans sağlayan C'ye aktarılabilir. Bununla birlikte, RPython hala yeni optimizasyonların daha hızlı geliştirilmesine izin veren C'den daha etkileyici. PyPy, derleyici önyüklemesinin bir örneğidir. PyPy (RPython değil!) Çoğunlukla CPython referans uygulamasıyla uyumludur.

  • Cython (RPython gibi) statik yazımla uyumsuz bir Python lehçesidir. Ayrıca C koduna aktarır ve CPython yorumlayıcısı için kolayca C uzantıları oluşturabilir.

Python kodunuzu Cython veya RPython'a çevirmek istiyorsanız, C benzeri bir performans elde edersiniz. Ancak, bunlar “Python'un bir alt kümesi” olarak değil, “Pythonic sözdizimiyle C” olarak anlaşılmalıdır. PyPy'ye geçerseniz, vanilya Python kodunuz önemli bir hız artışı elde eder, ancak C veya C ++ ile yazılmış uzantılarla arabirim kuramaz.

Ancak hangi özellikler veya özellikler vanilya Python'un uzun başlatma sürelerinin yanı sıra C benzeri performans düzeylerine ulaşmasını engelliyor?

  • Katkıda bulunanlar ve finansman. Java veya C # 'dan farklı olarak, dilin arkasında bu dili sınıfının en iyisi yapmak isteyen tek bir sürücü şirketi yoktur. Bu, gelişimi esas olarak gönüllüler ve nadiren verilen hibelerle kısıtlar.

  • Geç bağlama ve statik yazım eksikliği. Python böyle saçmalıklar yazmamızı sağlıyor:

    import random
    
    # foo is a function that returns an empty list
    def foo(): return []
    
    # foo is a function, right?
    # this ought to be equivalent to "bar = foo"
    def bar(): return foo()
    
    # ooh, we can reassign variables to a different type – randomly
    if random.randint(0, 1):
       foo = 42
    
    print bar()
    # why does this blow up (in 50% of cases)?
    # "foo" was a function while "bar" was defined!
    # ah, the joys of late binding

    Python'da herhangi bir değişken herhangi bir zamanda yeniden atanabilir. Bu önbelleğe almayı veya satırlamayı önler; herhangi bir erişim değişken üzerinden geçmek zorundadır. Bu dolaylama performansı yavaşlatır. Tabii ki: kodunuz böyle çılgınca şeyler yapmazsa, her değişkene derlemeden önce kesin bir tip verilebilir ve her değişkene sadece bir kez atanırsa, o zaman - teoride - daha verimli bir yürütme modeli seçilebilir. Bunu göz önünde bulunduran bir dil, tanımlayıcıları sabit olarak işaretlemenin ve en azından isteğe bağlı tür ek açıklamalarına (“kademeli yazma”) izin vermenin bir yolunu sağlayacaktır.

  • Şüpheli bir nesne modeli. Yuvalar kullanılmadıkça, bir nesnenin hangi alanlara sahip olduğunu bulmak zordur (bir Python nesnesi esasen bir karma tablodur). Ve oradayken bile, bu alanların ne tür olduğu hakkında hiçbir fikrimiz yok. Bu, C ++ 'da olduğu gibi nesneleri sıkıca paketlenmiş yapılar olarak temsil etmeyi önler. (Elbette, C ++ 'ın nesneleri temsil etmesi de ideal değildir: yapıya benzer doğa nedeniyle, özel alanlar bile bir nesnenin genel arayüzüne aittir.)

  • Çöp toplama. Birçok durumda, GC tamamen önlenebilir. C ++ bize statik geçerli kapsam bırakıldığında otomatik olarak yok edilir de nesne sağlar: Type instance(args);. O zamana kadar, nesne canlıdır ve diğer işlevlere ödünç verilebilir. Bu genellikle “referans yoluyla” yoluyla yapılır. Rust gibi diller, derleyicinin böyle bir nesneye yönelik hiçbir işaretçinin nesnenin ömrünü aşmadığını statik olarak denetlemesine izin verir. Bu bellek yönetimi şeması tamamen öngörülebilir, yüksek verimlidir ve karmaşık nesne grafikleri olmayan çoğu duruma uygundur. Ne yazık ki, Python bellek yönetimi düşünülerek tasarlanmamıştı. Teoride, GC'den kaçınılabilecek vakaları bulmak için kaçış analizi kullanılabilir. Uygulamada, basit yöntem zincirlerifoo().bar().baz() yığın üzerinde çok sayıda kısa ömürlü nesne tahsis etmek zorunda kalacak (kuşak GC bu sorunu küçük tutmanın bir yoludur).

    Diğer durumlarda, programcı zaten liste gibi bazı nesnelerin son boyutunu bilebilir. Ne yazık ki, Python yeni bir liste oluştururken bunu iletmenin bir yolunu sunmuyor. Bunun yerine, birden fazla yeniden tahsis gerektirebilecek yeni öğeler sonuna kadar itilecektir. Birkaç not:

    • Belirli bir boyuttaki listeler gibi oluşturulabilir fixed_size = [None] * size. Ancak, bu listedeki nesnelerin hafızasının ayrı olarak ayrılması gerekir. Kontrast C ++ yapabiliriz std::array<Type, size> fixed_size.

    • Belirli bir yerel türden paketlenmiş diziler arrayyerleşik modül aracılığıyla Python'da oluşturulabilir . Ayrıca, numpyyerel sayısal türler için belirli şekillere sahip veri tamponlarının etkin temsillerini sunar.

özet

Python performans için değil, kullanım kolaylığı için tasarlanmıştır. Tasarımı, yüksek verimli uygulama yaratmayı oldukça zorlaştırmaktadır. Programcı sorunlu özelliklerden uzak durursa, kalan deyimleri anlayan bir derleyici, performansta C ile rekabet edebilecek verimli bir kod yayabilir.


8

Evet. Birincil sorun, dilin dinamik olarak tanımlanmasıdır - yani, yapmak üzereyken ne yaptığınızı asla bilemezsiniz. Yani ne üretim makina koduna bilmiyorum çünkü çok zor, verimli makine kodu üretmek için yapar için . JIT derleyicileri bu alanda bazı işler yapabilir, ancak C ++ ile hiçbir zaman karşılaştırılamaz çünkü JIT derleyicisi sadece zaman ve bellek harcayamaz, çünkü bu programınızı çalıştırmak için harcadığınız zaman ve bellek değildir ve dinamik dil semantiğini bozmadan başarabilirler.

Bunun kabul edilemez bir takas olduğunu iddia etmeyeceğim. Ancak gerçek uygulamaların hiçbir zaman C ++ uygulamaları kadar hızlı olmayacağı Python'un doğası için esastır.


8

Tüm dinamik dillerin performansını etkileyen üç ana faktör vardır, bazıları diğerlerinden daha fazla.

  1. Yorumlayıcı ek yük. Çalışma zamanında makine talimatları yerine bir tür bayt kodu vardır ve bu kodu yürütmek için sabit bir ek yük vardır.
  2. Tepegöz gönderin. Bir işlev çağrısının hedefi çalışma zamanına kadar bilinmemektedir ve hangi çağrı yönteminin bir maliyet taşıdığını bulmak.
  3. Bellek yönetimi yükü. Dinamik diller, tahsis edilmesi ve yeniden yerleştirilmesi gereken ve performans yükü taşıyan nesnelerde bir şeyler depolar.

C / C ++ için bu 3 faktörün göreceli maliyeti neredeyse sıfırdır. Talimatlar doğrudan işlemci tarafından yürütülür, gönderme en fazla bir veya iki dolaylı sürer, siz söylemedikçe yığın bellek asla ayrılmaz. İyi yazılmış kod, montaj diline yaklaşabilir.

JIT derlemeli C # / Java için ilk ikisi düşüktür ancak çöp toplanan belleğin bir maliyeti vardır. İyi yazılmış kod 2x C / C ++ 'ya yaklaşabilir.

Python / Ruby / Perl için bu faktörlerin üçünün de maliyeti nispeten yüksektir. C / C ++ ile karşılaştırıldığında 5 kat veya daha kötüsünü düşünün. (*)

Çalışma zamanı kitaplığı kodunun programlarınızla aynı dilde yazılmış olabileceğini ve aynı performans sınırlamalarına sahip olabileceğini unutmayın.


(*) Just-In_Time (JIT) derlemesi bu dillere genişletildiği için, iyi yazılmış C / C ++ kodunun hızına da (tipik olarak 2x) yaklaşacaklardır.

Boşluğun daralmasıyla (rakip diller arasında), o zaman farklılıkların algoritmalar ve uygulama ayrıntıları tarafından domine edildiğine dikkat edilmelidir. JIT kodu C / C ++ 'ı yenebilir ve C / C ++ iyi bir kod yazmak daha kolay olduğu için montaj dilini yenebilir.


"Çalışma zamanı kitaplığı kodunun programlarınızla aynı dilde yazılmış olabileceğini ve aynı performans sınırlamalarına sahip olabileceğini unutmayın." ve "Python / Ruby / Perl için bu faktörlerin üçünün de maliyeti nispeten yüksektir. C / C ++ ile karşılaştırıldığında 5 kat veya daha kötüsünü düşünün." Aslında bu doğru değil. Örneğin, Rubinius Hashsınıfı ( Ruby'deki temel veri yapılarından biri) Ruby'de yazılmıştır ve YARV'nin HashC ile yazılmış sınıfından daha karşılaştırılabilir, hatta bazen daha hızlı performans gösterir. sistemi Ruby'de yazılmıştır, böylece onlar ...
Jörg W Mittag

… Örneğin Rubinius derleyicisi tarafından işaretlenmelidir. Bunun en uç örnekleri Klein VM (Self için metacircular VM) ve Maxine VM (Java için metacircular VM), her şeyin , hatta yöntem dağıtım kodunun, çöp toplayıcı, bellek ayırıcı, ilkel tipler, çekirdek veri yapıları ve algoritmaların yazıldığı Benlik veya Java. Bu şekilde, çekirdek VM'nin parçaları bile kullanıcı koduna eklenebilir ve VM, kullanıcı programındaki çalışma zamanı geri bildirimlerini kullanarak kendisini yeniden derleyebilir ve yeniden optimize edebilir.
Jörg W Mittag

@ JörgWMittag: Hala doğru. Rubinius'un JIT'i vardır ve JIT kodu genellikle C / C ++ 'yı bireysel kriterlerde yener. Bu metasirküler şeylerin JIT'in yokluğunda hız için çok şey yaptığını gösteren hiçbir kanıt bulamıyorum. [JIT hakkında netlik için düzenlemeye bakın.]
david.pfx

1

Python betiğimin eşdeğer bir C ++ programı kadar hızlı olmasını engelleyen teknik sınırlamalar veya dil özellikleri var mı?

Hayır. Bu sadece C ++ 'nın hızlı çalışmasını sağlamak için dökülen para ve kaynaklar ile Python'un hızlı çalışmasını sağlamak için dökülen kaynaklar.

Örneğin, Self VM ortaya çıktığında, sadece en hızlı dinamik OO dili değil, aynı zamanda en hızlı OO dil periyoduydu. İnanılmaz derecede dinamik bir dil olmasına rağmen (örneğin Python, Ruby, PHP veya JavaScript'ten çok daha fazlası), mevcut olan C ++ uygulamalarının çoğundan daha hızlıydı.

Ancak Sun, TV set üstü kutularındaki animasyonlu menüler için küçük bir komut dosyası diline odaklanmak için Self projesini (büyük sistemler geliştirmek için olgun bir genel amaçlı OO dili) iptal etti (bunu duymuş olabilirsiniz, buna Java denir) daha fazla fon. Aynı zamanda Intel, IBM, Microsoft, Sun, Metrowerks, HP ve ark. büyük miktarda para ve kaynak harcadı C ++ hızlı. CPU üreticileri, C ++ 'ı hızlı hale getirmek için yongalarına özellikler ekledi. İşletim Sistemleri C ++ 'ı hızlı yapmak için yazılmıştır veya değiştirilmiştir. Yani, C ++ hızlıdır.

Python'a çok aşina değilim, daha çok Ruby insanıyım, bu yüzden Ruby'den bir örnek vereceğim: Rubinius Ruby uygulamasındaki Hashsınıf (işlev ve önemde eşdeğer dict)% 100 saf Ruby'de yazılmıştır; yine de Hashel ile optimize edilmiş C ile yazılmış YARV sınıfından daha iyi rekabet ediyor ve hatta bazen daha iyi performans gösteriyor. .

Python'da onu yavaşlatan hiçbir şey yoktur. Bugünün işlemcilerinde ve işletim sistemlerinde Python'a zarar veren özellikler var (örneğin, sanal belleğin çöp toplama performansı için korkunç olduğu biliniyor). C ++ 'a yardım eden ancak Python'a yardım etmeyen özellikler vardır (modern CPU'lar önbellek hatalarından kaçınmaya çalışırlar, çünkü çok pahalıdırlar. Java için tasarlanan Azul Vega CPU bunu yapıyor.)

Python'u C ++ için yapıldığı gibi hızlı hale getirmek için çok fazla para, araştırma ve kaynak harcarsanız ve Python programlarını C ++ için yapıldığı kadar hızlı çalıştıran işletim sistemlerini yapmak için çok fazla para, araştırma ve kaynak harcarsanız ve Python programlarının C ++ için yapıldığı gibi hızlı çalışmasını sağlayan CPU'ların yapılmasına ilişkin çok fazla para, araştırma ve kaynak, o zaman Python'un C ++ ile karşılaştırılabilir performansa ulaşabileceğine şüphe yok.

ECMAScript ile sadece bir oyuncu performans konusunda ciddileşirse ne olabileceğini gördük. Bir yıl içinde, tüm büyük satıcılar için temelde 10 kat performans artışı elde ettik.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.