Net 4.0'daki yeni Tuple türü neden bir değer türü (yapı) değil de bir başvuru türü (sınıf)


89

Cevabı bilen ve / veya bununla ilgili bir fikri olan var mı?

Tuples normalde çok büyük olmayacağından, bunlar için sınıflardan çok yapıları kullanmanın daha mantıklı olacağını varsayıyorum. Ne diyorsun?


1
2016'dan sonra burada tökezleyen herkes için. C # 7 ve daha yeni sürümlerde, Tuple değişmezleri aile tipindedir ValueTuple<...>. En referansına bakın C # tanımlama grubu tipleri
Tamir Daniely

Yanıtlar:


94

Microsoft, basitlik adına tüm demet türlerini referans türlerini yaptı.

Şahsen bunun bir hata olduğunu düşünüyorum. 4'ten fazla alan içeren diziler çok sıra dışıdır ve yine de daha tipik bir alternatifle değiştirilmelidir (F #'daki bir kayıt türü gibi), bu nedenle yalnızca küçük demetler pratik ilgi çekicidir. Kendi kıyaslamalarım, 512 bayta kadar kutulanmamış tupleların, kutulu tuplelardan daha hızlı olabileceğini gösterdi.

Bellek verimliliği bir endişe kaynağı olsa da, bence en yaygın sorun .NET çöp toplayıcısının ek yükü. .NET üzerinde ayırma ve toplama çok pahalıdır , çünkü çöp toplayıcısı çok yoğun bir şekilde optimize edilmemiştir (örneğin JVM ile karşılaştırıldığında). Ayrıca, varsayılan .NET GC (iş istasyonu) henüz paralelleştirilmemiştir. Sonuç olarak, tüm çekirdekler paylaşılan çöp toplayıcı için mücadele ettiğinden, demetleri kullanan paralel programlar durma noktasına gelir ve ölçeklenebilirliği yok eder. Bu sadece baskın endişe değil, AFAIK, bu sorunu incelerken Microsoft tarafından tamamen ihmal edildi.

Diğer bir endişe de sanal gönderimdir. Başvuru türleri alt türleri destekler ve bu nedenle, üyeleri genellikle sanal gönderim yoluyla çağrılır. Buna karşılık, değer türleri alt türleri destekleyemez, bu nedenle üye çağırma tamamen belirsizdir ve her zaman doğrudan bir işlev çağrısı olarak gerçekleştirilebilir. Sanal dağıtım, modern donanımda oldukça pahalıdır çünkü CPU, program sayacının nerede biteceğini tahmin edemez. JVM, sanal dağıtımı optimize etmek için büyük çaba gösterir, ancak .NET bunu yapmaz. Ancak .NET, değer türleri biçiminde sanal dağıtımdan bir çıkış sağlar. Dolayısıyla, tuple'ları değer türleri olarak temsil etmek, yine burada performansı önemli ölçüde artırabilir. Örneğin, aramaGetHashCode 2-tuple üzerinde bir milyon kere 0.17s alır ama onu eşdeğer bir yapıda çağırmak sadece 0.008s alır, yani değer türü referans tipinden 20 kat daha hızlıdır.

Tuple'larla ilgili bu performans sorunlarının yaygın olarak ortaya çıktığı gerçek bir durum, tupleların sözlüklerde anahtar olarak kullanılmasıdır. Aslında Stack Overflow sorusundan bir bağlantıyı izleyerek bu konuya rastladım F # algoritmamı Python'dan daha yavaş çalıştırıyor! yazarın F # programının Python'undan daha yavaş olduğu ortaya çıktı, çünkü o kutulu tuple kullanıyordu. Elle yazılmış bir structtür kullanarak manuel olarak kutudan çıkarmak , F # programını Python'dan birkaç kat daha hızlı ve daha hızlı hale getirir. Başlıklar değer türleri ile temsil edilse ve başlangıçta başvuru türleri değil, bu sorunlar asla ortaya çıkmazdı ...


2
@Bent: Evet, F # 'da sıcak bir yolda tuples ile karşılaştığımda tam olarak yaptığım şey bu. Yine de .NET Framework'te hem kutulu hem de kutulu olmayan kayıtlar sağlamış olsalardı iyi olurdu ...
JD

18
Sanal gönderimle ilgili olarak, suçunuzun yanlış yerleştirildiğini düşünüyorum: Tuple<_,...,_>türler mühürlenmiş olabilir, bu durumda referans türler olmasına rağmen sanal gönderime gerek kalmaz. Neden referans tip olduklarından çok neden mühürlenmediklerini merak ediyorum.
kvb

2
Testlerime göre, bir işlevde bir demetinin oluşturulup başka bir işleve geri döndüğü ve sonra bir daha asla kullanılmadığı senaryo için, açık alan yapıları , patlayacak kadar büyük olmayan herhangi bir boyuttaki veri öğesi için üstün performans sunuyor gibi görünüyor. yığın. Değişmez sınıflar, yalnızca, referanslar inşaat maliyetlerini haklı çıkarmak için yeterince etrafından geçirilirse daha iyidir (veri öğesi ne kadar büyükse, takasın lehine olması için o kadar az geçirilmesi gerekir). Bir demet, birbirine yapışmış bir grup değişkeni temsil etmesi gerektiğinden, yapı ideal görünecektir.
supercat

2
"512 bayta kadar kutulanmamış tuplelar kutudan daha hızlı olabilir" - bu hangi senaryo? Sen belki daha hızlı veri 512B tutan bir sınıf örneğini daha 512B yapı tahsis edebilmek, ancak etrafında geçerek 100'den fazla kat daha yavaş (x86 önünde bulundurun) olacaktır. Gözden kaçırdığım bir şey mi var?
Groo


45

Bunun nedeni büyük olasılıkla, küçük bir bellek ayak izine sahip olacağından, yalnızca küçük demetler değer türleri olarak mantıklı olacaktır. Daha büyük kayıtlar (yani daha fazla özelliğe sahip olanlar), 16 bayttan daha büyük olacakları için gerçekte performans açısından zarar göreceklerdir.

Bazı demetlerin değer türü, diğerlerinin de referans türü olması ve geliştiricilerin hangilerinin hangileri olduğunu bilmesini sağlamak yerine, Microsoft'taki insanların hepsini referans türlerini yapmanın daha basit olduğunu düşündüm.

Ah, şüpheler doğrulandı! Lütfen Building Tuple'a bakın :

İlk büyük karar, demetleri referans veya değer türü olarak ele alıp almamaktı. Bir demetin değerlerini değiştirmek istediğiniz her an değişmez olduklarından, yeni bir tane oluşturmanız gerekir. Referans türlerse, bu, sıkı bir döngüdeki bir demetteki öğeleri değiştirirseniz çok fazla çöp oluşabileceği anlamına gelir. F # tuplelar referans türleriydi, ancak iki ve belki de üç öğe demetleri bunun yerine değer türleri olsaydı, takımdan bir performans iyileştirmesi gerçekleştirebileceklerine dair bir his vardı. Dahili kayıtlar oluşturan bazı ekipler, senaryoları çok sayıda yönetilen nesne oluşturmaya çok duyarlı olduğundan referans türleri yerine değer kullandı. Bir değer türü kullanmanın onlara daha iyi performans sağladığını buldular. Demet spesifikasyonunun ilk taslağımızda, iki, üç ve dört elemanlı demetleri değer türleri olarak tuttuk, geri kalanlar referans türlerdi. Bununla birlikte, diğer dillerden temsilcilerin katıldığı bir tasarım toplantısında, bu "bölünmüş" tasarımın, iki tür arasındaki biraz farklı anlambilim nedeniyle kafa karıştırıcı olacağına karar verildi. Davranış ve tasarımdaki tutarlılığın, potansiyel performans artışlarından daha öncelikli olduğu belirlendi. Bu girdiye dayalı olarak tasarımı değiştirdik, böylece F # ekibinden bazı boyutlardaki tuple'lar için bir değer türü kullanırken bir hızlanma yaşayıp yaşamadığını görmek için bazı performans araştırması yapmalarını istedik. F # ile yazılan derleyicisinden bu yana bunu test etmenin iyi bir yolu vardı. çeşitli senaryolarda demetler kullanan büyük bir programa iyi bir örnekti. Sonunda, F # ekibi, bazı demetler referans türleri yerine değer türleri olduğunda performans artışı elde etmediğini buldu. Bu, demet için referans türlerini kullanma kararımız konusunda bizi daha iyi hissettirdi.



Ahh, anlıyorum. Değer türlerinin burada pratikte hiçbir şey ifade etmediği konusunda hala biraz kafam karıştı: P
Bent Rasmussen

Sadece genel arayüzlerin olmadığı hakkındaki yorumu okudum ve koda daha önce baktığımda, bu tam olarak beni etkileyen başka bir şeydi. Tuple türlerinin ne kadar jenerik olmadığı gerçekten oldukça sönük. Ama sanırım her zaman kendinizinkini yapabilirsiniz ... Zaten C # 'da sözdizimsel destek yok. Yine de en azından ... Gene de jeneriklerin kullanımı ve sahip olduğu kısıtlamalar .Net'te hala sınırlı hissediliyor. Çok genel çok soyut kitaplıklar için önemli bir potansiyel vardır, ancak jenerikler muhtemelen kovaryant dönüş türleri gibi fazladan şeylere ihtiyaç duyar.
Bent Rasmussen

7
"16 bayt" sınırınız sahte. Bunu .NET 4'te test ettiğimde, GC'nin çok yavaş olduğunu ve 512 bayta kadar kutulmamış tupleların daha hızlı olabileceğini buldum. Ayrıca Microsoft'un karşılaştırma sonuçlarını da sorgulardım. Bahse girerim paralelliği göz ardı etmişlerdir (F # derleyicisi paralel değildir) ve işte bu noktada GC'den kaçınmak işe yarar çünkü .NET'in iş istasyonu GC'si de paralel değildir.
JD

Meraktan, derleyici ekibinin demetler oluşturma fikrini EXPOSED-FIELD yapıları olarak test edip etmediğini merak ediyorum. Birinin çeşitli özelliklere sahip bir türün örneği varsa ve farklı olan bir özellik dışında aynı olan bir örneğe ihtiyaç duyuyorsa, açık alan yapısı bunu diğer türlerden çok daha hızlı gerçekleştirebilir ve avantaj yalnızca yapılar elde ettikçe artar. daha büyük.
supercat

7

.NET System.Tuple <...> türleri yapı olarak tanımlandıysa, ölçeklenebilir olmazlardı. Örneğin, uzun tamsayılardan oluşan üçlü bir demet şu anda aşağıdaki gibi ölçeklenir:

type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4

Üçlü demet bir yapı olarak tanımlandıysa, sonuç aşağıdaki gibi olacaktır (uyguladığım bir test örneğine göre):

sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104

Tuplelar, F # 'da yerleşik sözdizimi desteğine sahip olduğundan ve bu dilde son derece sık kullanıldığından, "struct" tuple, F # programcılarının farkında olmadan verimsiz programlar yazma riskiyle karşı karşıya kalmasına neden olur. Çok kolay olurdu:

let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3

Bana göre, "yapı" demetleri günlük programlamada önemli verimsizlikler yaratma olasılığının yüksek olmasına neden olur. Öte yandan, şu anda var olan "sınıf" demetler de @Jon tarafından belirtildiği gibi bazı verimsizliklere neden olur. Bununla birlikte, "oluşma olasılığı" çarpı "potansiyel hasar" çarpımının, yapılarda şu anda sınıflarda olduğundan çok daha yüksek olacağını düşünüyorum. Bu nedenle, mevcut uygulama daha az kötülüktür.

İdeal olarak, her ikisi de F # 'da sözdizimsel desteği olan "sınıf" demetleri ve "yapı" demetleri olacaktır!

Düzenle (2017-10-07)

Struct tuples artık aşağıdaki gibi tam olarak desteklenmektedir:


2
Gereksiz kopyalamadan kaçınılırsa, herhangi bir boyuttaki açık alan yapısı, her bir örnek, bu tür bir kopyalamanın maliyetinin bir yığın nesnesi oluşturma maliyetini aşacak kadar kopyalanmadığı sürece, aynı boyuttaki değişmez bir sınıftan daha verimli olacaktır ( çift ​​kopya sayısı nesne boyutuna göre değişir). Tek değişmez olmak miş bir yapı istiyor, ama (yapı budur değişkenlerin koleksiyonlar olarak görünür şekilde tasarlanmıştır yapılar eğer böyle kopyalama kaçınılmaz olabilir vardır onlar büyük olsa bile) etkin bir şekilde kullanılabilir.
supercat

2
F # 'nin yapıları geçirme fikriyle iyi oynamaması refveya sözde "değişmez yapılar" ın özellikle kutulandığında olmadığı gerçeğinden hoşlanmaması olabilir. Bu çok kötü .net, parametrelerin geçme kavramını hiçbir zaman bir yürütülebilir tarafından uygulanmadı const ref, çünkü çoğu durumda gerçekten gerekli olan bu tür anlambilimdir.
supercat

1
Bu arada, GC'nin amortize edilmiş maliyetini, nesneleri tahsis etme maliyetinin bir parçası olarak görüyorum; Her megabayt ayırmadan sonra bir L0 GC gerekliyse, 64 bayt ayırmanın maliyeti, bir L0 GC'nin maliyetinin yaklaşık 1 / 16.000'i, artı olarak gerekli hale gelen herhangi bir L1 veya L2 GC'nin maliyetinin bir kısmıdır. bunun sonucu.
supercat

4
"Oluşma olasılığı çarpı potansiyel hasarın çarpımının, yapılarda şu anda sınıflarda olduğundan çok daha yüksek olacağını düşünüyorum". FWIW, vahşi doğada çok nadiren tuple demetleri gördüm ve bunları bir tasarım hatası olarak görüyorum, ancak insanların (ref) demetleri a'da anahtar olarak kullanırken kötü performansla mücadele ettiğini çok sık görüyorum Dictionary, örneğin burada: stackoverflow.com/questions/5850243 /…
JD

3
@Jon Bu cevabı yazdığımdan bu yana iki yıl geçti ve şimdi size katılıyorum, en az 2- ve 3-tuplelar yapı olsaydı tercih edilir olurdu. Bu konuda bir F # dili kullanıcı sesi önerisi yapılmıştır. Son yıllarda büyük veri, kantitatif finans ve oyun alanındaki uygulamalarda muazzam bir artış olduğu için, sorunun biraz aciliyeti var.
Marc Sigrist

4

2 tuple'lar için, Common Type System'in önceki sürümlerinden KeyValuePair <TKey, TValue> kullanmaya devam edebilirsiniz. Bu bir değer türüdür.

Matt Ellis makalesine küçük bir açıklama, referans ve değer türleri arasındaki kullanım anlambilimindeki farkın, değişmezlik yürürlükte olduğunda yalnızca "küçük" olduğudur (tabii ki burada durum böyle olacaktır). Bununla birlikte, BCL tasarımında, Tuple'ın bir eşikte bir referans tipine geçmesi konusundaki kafa karışıklığını ortaya koymamak en iyisi olacağını düşünüyorum.


Bir değer döndürüldükten sonra bir kez kullanılacaksa, herhangi bir boyuttaki açık alan yapısı, yalnızca yığını patlatacak kadar canavarca büyük olmaması koşuluyla, diğer türlerden daha iyi performans gösterecektir. Bir sınıf nesnesi oluşturmanın maliyeti, yalnızca referans birden çok kez paylaşılırsa telafi edilecektir. Genel amaçlı sabit boyutlu heterojen bir türün bir sınıf olmasının yararlı olduğu zamanlar vardır, ancak bir yapının daha iyi olacağı başka zamanlar da vardır - "büyük" şeyler için bile.
supercat

Bu kullanışlı pratik kuralı eklediğiniz için teşekkür ederiz. Bununla birlikte, konumumu yanlış anlamamışsınızdır umarım: Ben değer tipi bir bağımlıyım. ( stackoverflow.com/a/14277068 şüpheye yer bırakmamalıdır).
Glenn Slayden

Değer türleri .net'in harika özelliklerinden biridir, ancak maalesef msdn dox'u yazan kişi, kendileri için birden fazla ayrık kullanım durumu olduğunu ve farklı kullanım durumlarının farklı yönergelere sahip olması gerektiğini fark edemedi. Msdn yapısının stili yalnızca homojen bir değeri temsil eden yapılarla kullanılmalıdır, ancak koli bandı ile birbirine bağlanmış bazı bağımsız değerleri temsil etmek gerekirse, bu yapı tarzı kullanılmamalıdır - kişi ile bir yapı kullanılmalıdır. maruz kalan kamu alanları.
supercat

0

Bilmiyorum ama F # Tuples kullandıysanız, dilin bir parçasıdır. Bir .dll yapıp bir tür Tuple döndürdüysem, bunu yerleştirmek için bir türe sahip olmak güzel olur. Şimdi F # dilinin bir parçası olduğundan şüpheleniyorum (.Net 4) bazı ortak yapıları barındırmak için CLR'de bazı değişiklikler yapıldı F #

Gönderen http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records

let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;

val scalarMultiply : float -> float * float * float -> float * float * float

scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)
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.