Temel türlerin (int gibi) sınıf olarak uygulanmasının uyarıları nelerdir?


27

Tasarımı ve nesne yönelimli bir programlama dili implenting, bazı nokta birinde temel türleri uygulama hakkında bir seçim (gibi yapmalıdır int, float, doublesınıflar ya da başka bir şey olarak veya eşdeğerleri). Açıkçası, C ailede diller eğilimi olmayan (Java özel ilkel türleri, değişmez yapılar olarak onları C # uygular, vb vardır) sınıfları olarak tanımlamak.

Temel türler sınıf olarak uygulandığında (birleşik bir hiyerarşiye sahip bir tür sisteminde) çok önemli bir avantaj düşünebilirim: bu türler kök türünün uygun Liskov alt türleri olabilir. Bu nedenle, dili boks / unboxing (açık veya kapalı), ambalaj türleri, özel değişkenlik kuralları, özel davranış vb.

Tabii ki, dil tasarımcılarının neden böyle karar verdiklerini kısmen anlayabiliyorum: sınıf örnekleri, uzamsal bir ek yüke sahip olma eğilimindedir (çünkü örnekler, bellek düzeninde boş veya başka bir meta veri içerebilir), ilkellerin / yapıların gerekmediğini var (eğer dil bunlar üzerinde mirasa izin vermiyorsa).

Mekansal verimlilik (ve özellikle geniş dizilerde geliştirilmiş mekansal konum) temel türlerin genellikle sınıf olmaması için tek neden midir?

Genelde cevabın evet olduğunu varsaydım, ancak derleyiciler kaçış analizi algoritmalarına sahipti ve bu nedenle bir örnek (yalnızca temel bir tür değil) kesin olarak kanıtlandığında, uzamsal yükü (seçici olarak) ihmal edip edemeyeceklerini tespit edebiliyorlar. yerel.

Yukarıdaki yanlış mı, yoksa kaçırdığım bir şey mi var?


Yanıtlar:


19

Evet, verimlilik hemen hemen aşağı iniyor. Ancak, bu etkiyi küçümsüyor gibi görünüyorsunuz (veya çeşitli optimizasyonların ne kadar iyi çalıştığını abartıyorsunuz).

İlk önce, sadece "mekansal ek yük" değil. İlkellerin kutulu / yığın ayrılmış olarak yapılması da performans maliyetine sahiptir. Bu nesnelerin tahsis edilmesi ve toplanması için GC üzerinde ilave baskı var. "İlkel nesneler" olması gerektiği gibi değişmez ise bu iki katına çıkar. Öyleyse daha fazla önbellek özeti var (hem dolaylı olduğu hem de daha az veri verilen önbellek miktarına uyduğu için). Ayrıca, "bir nesnenin adresini yükleyin, sonra gerçek adresi bu adrese yükleyin" ifadesi "değeri doğrudan yükleyin" den daha fazla talimat alır.

İkincisi, kaçış analizi daha hızlı peri tozu değildir. Sadece kaçmayan değerler için geçerlidir. Yerel hesaplamaları (döngü sayaçları ve hesaplamaların ara sonuçları gibi) optimize etmek kesinlikle güzeldir ve ölçülebilir faydalar sağlayacaktır. Ancak, değerlerin çok daha büyük bir kısmı nesneler ve diziler alanlarında yaşamaktadır. Kabul edilirse, bunlar kaçış analizine tabi tutulabilirler, ancak genellikle değişken referans türleri olduklarından, bunların herhangi bir diğer adı, şimdi bu takma adların (1) ya da kaçmadıklarını kanıtlamak zorunda olan kaçış analizine önemli bir meydan okuma sunar. ve (2) tahsisleri elemek için bir fark yaratmıyor.

(Getters dahil) yöntemini çağırarak veya argüman olarak nesneyi geçirmeden göz önüne alındığında herhangi bir nesne kaçış yardımcı olabilecek başka yöntemle, tüm içinde interprocedural analiz ama en önemsiz davaları gerekir. Bu çok daha pahalı ve karmaşık.

Ve sonra olayların gerçekten kaçtığı ve makul bir şekilde optimize edilemeyeceği durumlar vardır. Aslında, çoğu, C programcılarının yığın ayırma sıkıntısı yaşadıklarını düşünüyorsanız, aslında. Bir int içeren bir nesne kaçtığında, kaçış analizi de int'e uygulamak için durur. Etkili ilkel alanlara elveda deyin .

Bu da bir başka noktaya bağlanıyor: Gereken analizler ve optimizasyonlar ciddi şekilde karmaşık ve aktif bir araştırma alanı. Herhangi bir dil uygulamasının önerdiğiniz optimizasyon derecesine ulaşıp ulaşmadığı tartışmalıdır ve öyle olsa bile, nadir ve herculean bir çaba oldu. Kuşkusuz bu devlerin omuzlarında durmak kendin için dev olmaktan daha kolaydır, ama yine de önemsiz olmaktan uzaktır. İlk birkaç yıl içinde, hiç olsaydı, hiçbir zaman rekabetçi performans beklemeyin.

Bu tür dillerin geçerli olamaz demek değildir. Açıkçası onlar. Sadece özel ilkelleri olan diller kadar hızlı bir şekilde sıraya gireceğini varsaymayın. Başka bir deyişle, yeterince akıllı bir derleyicinin görüntüleriyle kendinizi şaşırtmayın .


Kaçış analizi hakkında konuşurken, aynı zamanda otomatik depolamaya tahsis etmek istedim (her şeyi çözmez, ancak sizin dediğiniz gibi, bazı şeyleri çözer). Ayrıca, alanların ve takma adların kaçış analizinin daha sık başarısız olabileceğini ne kadar küçümsemediğimi de itiraf ediyorum. Önbellek özlüyor, mekansal verimlilik hakkında konuşurken en çok endişe duyduğum şeydi.
Theodoros Chatzigiannakis

@TheodorosChatzigiannakis kaçış analizine değişen tahsisat stratejisini dahil ediyorum (çünkü dürüst olmak gerekirse, şu ana kadar kullandığı tek şey bu gibi görünüyor).

İkinci paragrafınızı yerleştirin: Nesnelerin her zaman yığınla ayrılması veya referans türleri olması gerekmez. Aslında, olmadıklarında, bu gerekli optimizasyonları nispeten kolay hale getirir. Erken bir örnek için C ++ 'ın yığınla ayrılmış nesnelerine ve kaçış analizini doğrudan dile çevirmenin bir yolu için Rust'un sahiplik sistemine bakın.
amon

@ amon biliyorum ve belki de bunu daha netleştirmeliydim, ama OP görünüyor ki, referans semantiği ve alt türler arasındaki kayıpsız yayınlar nedeniyle yığın tahsisi neredeyse zorunlu (ve üstü kapalı) olan Java ve C # benzeri dillerle ilgileniyor. Analizden kaçmak için gereken miktarları kullanarak Rust hakkında iyi bir nokta!

@delnan Doğru, çoğunlukla depolama ayrıntılarını özetleyen dillerle ilgileniyorum, ancak bu dillerde geçerli olmasa bile, alakalı olduğunu düşündüğünüz bir şeyi eklemekten çekinmeyin.
Theodoros Chatzigiannakis

27

Mekansal verimlilik (ve özellikle geniş dizilerde geliştirilmiş mekansal konum) temel türlerin genellikle sınıf olmaması için tek neden midir?

Yok hayır.

Diğer bir konu, temel türlerin temel işlemler tarafından kullanılma eğiliminde olmasıdır. Derleyicinin int + intbir işlev çağrısına derlenmeyeceğini, ancak bazı temel CPU komutlarına (veya eşdeğer bayt koduna) bilmesi gerekir . Bu noktada, eğer intnormal bir nesne olarak sahipseniz, her şeyin etkin bir şekilde kutucuğunu kaldırmanız gerekecektir.

Bu tür işlemler aynı zamanda alt yazarak da pek hoş değil. Bir CPU komutunu gönderemezsiniz. Sen dağıtamazsınız gelen bir işlemci talimatı. Demek istediğim, altyazı meselesinin anlamı, Dnerede istersen onu kullan B. CPU talimatları polimorfik değildir. İlkellerin bunu yapmasını sağlamak için, işlemlerini işlemlerin basit katkısı (veya her neyse) katı katına mal eden gönderim mantığıyla sarmanız gerekir. intTip hiyerarşisinin bir parçası olmanın faydası, mühürlendiğinde / sonlandırıldığında biraz kararsız hale gelir. Bu da ikili operatörler için gönderme mantığına sahip tüm baş ağrıları görmezden geliyor

Temel olarak, ilkel türlerin, derleyicinin bunları nasıl kullandığı ve kullanıcının yine de türleri ile neler yapabileceği konusunda birçok özel kural olması gerekir ; bu nedenle, onlara yalnızca tamamen farklı davranmaları çoğu zaman daha kolaydır.


4
Tamsayılı ve nesneler gibi dinamik olarak yazılmış dillerden herhangi birinin uygulanmasına göz atın. Son ilkel CPU komutu, çalışma zamanı kitaplığındaki bir şekilde ayrıcalıklı sınıf uygulamasındaki bir yöntemde (operatörün aşırı yüklenmesi) çok iyi gizlenebilir. Statik tip bir sistem ve derleyici ile detaylar farklı görünecek fakat bu temel bir problem değil. En kötüsü sadece işleri daha da yavaşlatıyor.

3
int + intYerel CPU tamsayısı ekleme işlemine derleme (veya böyle davranma) garantisi olan özgün bir talimatı başlatan normal dil düzeyinde bir operatör olabilir. intMiras almanın yararı, objectsadece başka bir tipten miras alma imkanı değil int, aynı zamanda intbokssuz olarak davranma olasılığıdır object. C # generiklerini göz önünde bulundurun: kovaryans ve kontravaryansınız olabilir, ancak bunlar yalnızca sınıf tiplerine uygulanabilir - yapı tipleri otomatik olarak hariç tutulur, çünkü bunlar yalnızca object(örtülü, derleyici tarafından oluşturulan) boks yapabilirler.
Theodoros Chatzigiannakis,

3
@delnan - kesinlikle, statik olarak yazılmış uygulamalar konusundaki deneyimlerime rağmen, her sistem dışı çağrı ilkel işlemlere bağlı olduğundan, ek yüke sahip olmak performans üzerinde çarpıcı bir etkiye sahiptir - ki bu da benimseme üzerinde daha etkileyici bir etkiye sahiptir.
Telastyn

@TheodorosChatzigiannakis - harika, bu yüzden kullanışlı bir alt / süper tip içermeyen türlerde farklılık ve çelişki elde edebilirsiniz ... Ve CPU komutunu çağırmak için bu özel operatörü uygulamak onu özel kılar. Bu fikre katılmıyorum - oyuncak dillerimde çok benzer şeyler yaptım, ancak uygulama sırasında beklediğiniz kadar temiz olmayan pratik pratikler olduğunu gördüm.
Telastyn

1
@TheodorosChatzigiannakis Kütüphane sınırlarını aşmak kesinlikle mümkün, yine de "Sahip olmak istediğim en iyi optimizasyonlar" alışveriş listesinde bir başka ürün. İşe yaramaz olacak kadar muhafazakar olmadan tamamen doğru olmanın çok zor olduğunu belirtmek zorunda kaldığımı belirtmek zorundayım.

4

Tam nesneler için “temel türler” uygulamanız gereken çok az durum vardır (burada bir nesne, gönderme mekanizmasına bir işaretçi içeren ya da gönderme mekanizması tarafından kullanılabilecek bir tür ile etiketlenmiş verilerdir):

  • Kullanıcı tanımlı türlerin temel türlerden miras alabilmesini istiyorsunuz. Bu genellikle performans ve güvenlikle ilgili baş ağrıları yarattığı için istenmez. Derleme intbelirli bir sabit boyuta sahip olacağını veya hiçbir yöntemin geçersiz kılınmadığını varsayacağı için derleme bir performans sorunudur ve ints'nin semantiği altüst edilebileceği için bir güvenlik sorunudur (herhangi bir sayıya eşit bir tam sayı düşünün veya bu değişmez olmaktan ziyade değerini değiştirir).

  • İlkel tiplerinizin üst tipleri vardır ve siz, ilkel tipte bir üst tip tipi olan değişkenlere sahip olmak istersiniz. Örneğin, ints ' lerinizin olduklarını Hashableve Hashablenormal nesneler de alabilecek bir parametre alan ancak aynı zamanda ints alan bir işlev bildirmek istediğinizi varsayalım .

    Bu tür yasadışı hale getirilerek “çözülebilir”: alt türlemeden kurtulun ve arayüzlerin tür değil, tür sınırlamaları olduğuna karar verin. Açıkçası, bu sizin tip sisteminizin etkililiğini azaltır ve böyle bir tip sistem artık nesneye yönelik olarak adlandırılmaz. Bu stratejiyi kullanan bir dil için Haskell'e bakınız. C ++ orada yarı yolda çünkü ilkel tiplerin üst tipleri yok.

    Alternatif, temel türlerin tam veya kısmi boksudur. Boks tipi kullanıcı tarafından görülebilmek zorunda değildir. Temel olarak, her temel tür için iç kutulu bir tür tanımlayın ve kutulu ve temel tür arasında örtük dönüşümler. Kutulu türlerin farklı semantikleri varsa, bu durum garipleşebilir. Java iki sorun sergiler: kutulu tipler bir kimlik kavramına sahipken, ilkellerin sadece bir değer eşdeğerliği kavramı vardır ve kutulu tipler geçersizdir; Bu türler, değer türleri için bir kimlik kavramı sunmamak, operatöre aşırı yüklenme sunmak ve tüm nesneleri varsayılan olarak geçersiz kılmakla tamamen önlenebilir.

  • Statik yazım özelliği yoktur. Bir değişken, ilkel türler veya nesneler dahil olmak üzere herhangi bir değeri tutabilir. Bu nedenle, güçlü yazmayı garanti etmek için tüm ilkel tiplerin her zaman kutulanması gerekir.

Statik yazımı olan diller, mümkün olan her yerde ilkel türleri kullanmaya yarar ve yalnızca son çare olarak kutulu türlere geri döner. Birçok program muazzam bir performansa duyarlı olmasa da, ilkel türlerin boyutunun ve yapısının aşırı derecede alakalı olduğu durumlar vardır: Milyarlarca veri noktasını belleğe sığdırmanız gereken yerlerde büyük ölçekli sayıların çatlamasını düşünün. Geçiş doubleiçinfloatC'de uygulanabilir bir alan optimizasyon stratejisi olabilir, ancak tüm sayısal türler her zaman kutulanmışsa (ve bu nedenle bir gönderme mekanizması işaretçisi için belleğinin en az yarısını boşa harcarsanız) etkisiz kalır. Kutulu ilkel türler yerel olarak kullanıldığında, boksörün derleyici içselleri kullanılarak kaldırılması oldukça basittir, ancak dilinizin genel olarak "yeterli düzeyde gelişmiş bir derleyici" ile oynayacağına dair kısa bir görüş olacaktır.


Bir intdil tüm dillerde değişmezdir.
Scott Whitlock

6
@ScottWhitlock Neden böyle düşündüğünüzü anlıyorum, ancak genel olarak ilkel türler değişmez değer türleridir. Aklı başında hiçbir dil, yedi sayının değerini değiştirmenize izin vermez. Bununla birlikte, birçok dil, ilkel türün değerini tutan bir değişkeni farklı bir değere yeniden atamanıza izin verir. C benzeri dillerde, değişken adlandırılmış bir bellek konumudur ve işaretçi gibi davranır. Bir değişken, gösterdiği değerle aynı değildir. Bir intdeğer değişmezdir, ancak bir intdeğişken değildir.
amon

1
@amon: Aklı başında bir dil yok; sadece Java: thedailywtf.com/articles/Disgruntled-Bomb-Java-Edition
Mason Wheeler

get rid of subtyping and decide that interfaces aren't types but type constraints.... such a type system wouldn't be called object-oriented any longer fakat bu prototip tabanlı bir programlamaya benziyor, ki bu kesinlikle OOP.
Michael

1
@ScottWhitlock sorusu eğer int b = a varsa, b değerini a değiştirecek bir şey yapıp yapamayacağınızdır. Bunun mümkün olduğu bazı dil uygulamaları olmuştur, ancak bir dizi için aynısını yapmaktan farklı olarak, genellikle patolojik ve istenmeyen olarak kabul edilir.
Random832

2

Çoğu uygulama, derleyicinin zamanın büyük çoğunluğunun temelini temsil eden ilkel türleri etkin bir şekilde kullanmasına izin veren bu tür sınıflara üç kısıtlama getirdiğini biliyorum. Bu kısıtlamalar şunlardır:

  • değişmezlik
  • Kesinlik (elde edilememesi)
  • Statik yazarak

Bir derleyicinin , temeli temsil eden bir nesneye ilkel bir kutu koyması gereken durumlar , örneğin bir Objectreferansı işaret ettiği zaman olduğu gibi nispeten nadirdir .

Bu, derleyicide bir miktar özel durum işleme ekler, ancak yalnızca bazı efsanevi süper gelişmiş derleyicilerle sınırlı değildir. Bu optimizasyon ana dillerde gerçek üretim derleyicileridir. Scala, kendi değer sınıflarınızı tanımlamanıza bile izin verir.


1

Smalltalk'ta hepsi (int, float vb.) Birinci sınıf nesnelerdir. Sadece özel durum SmallIntegers kodlanmış ve verimlilik uğruna Virtual Machine tarafından farklı muamele ve bu nedenle SmallInteger sınıf alt sınıflarını kabul etmeyecek olmasıdır (pratik bir sınırlama değildir ki.) Bu özel bir değerlendirme gerektirmeyen Not Programcının parçası olarak, ayrım kod oluşturma veya çöp toplama gibi otomatik rutinlere sınırlandırılmıştır.

Smalltalk Compiler (kaynak kod -> VM bayt kodları) ve VM doğallaştırıcısı (bayt kodları -> makine kodu), bu temel nesnelerle temel işlemlerin cezasını azaltmak için oluşturulan kodu (JIT) optimize eder.


1

Bir OO dili ve çalışma zamanı tasarlıyordum (bu tamamen farklı sebeplerden dolayı başarısız oldu).

İnt gerçek sınıflar gibi şeyler yapmak doğal olarak yanlış bir şey yoktur; aslında bu GC'yi tasarım yapmak için kolaylaştırır, çünkü şu anda 3 (sınıf, dizi ve ilkel) yerine yalnızca 2 tür yığın başlığı (sınıf ve dizi) vardır. [bundan sonra sınıf ve diziyi birleştirebiliriz. ].

Asıl önemli durum, ilkel türlerin çoğunlukla nihai / mühürlü yöntemlere sahip olması gerektiğidir (+ gerçekten önemlidir, ToString çok fazla değil). Bu, derleyicinin hemen hemen tüm çağrıları işlevlerin kendilerine statik olarak çözümlemesini ve bunları satır içi yapmasını sağlar. Çoğu durumda bu, kopyalama davranışı olarak önemli değildir (katıştırmayı dil düzeyinde [bu yüzden .NET] kullanmayı seçtim), ancak bazı durumlarda, yöntemler mühürlü değilse derleyici çağrı yapmak zorunda kalır. int + int'yi uygulamak için kullanılan işlev.

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.