OCaml'de bir int neden yalnızca 31 bittir?


115

Bu "özelliği" başka hiçbir yerde görmedim. 32. bitin çöp toplama için kullanıldığını biliyorum. Ama neden diğer temel türler için değil de yalnızca ints için bu şekilde?


10
64-bit işletim sistemlerinde, OCaml'de bir int'in 31 değil 63 bit olduğuna dikkat edin. Bu, etiket bitinin pratik problemlerinin çoğunu (dizi boyutu limitleri gibi) ortadan kaldırır. Ve tabii ki, bazı standart algoritmalar için gerçek bir 32 bit tam sayıya ihtiyacınız varsa, int32 türü vardır.
Porculus

1
nekoVM ( nekovm.org ) da yakın zamana kadar 31 bit girişe sahipti.
TheHippo

Yanıtlar:


244

Buna etiketli işaretçi gösterimi denir ve onlarca yıldır birçok farklı yorumlayıcıda, sanal makinede ve çalışma zamanı sisteminde kullanılan oldukça yaygın bir optimizasyon hilesidir. Hemen hemen her Lisp uygulaması bunları, birçok Smalltalk sanal makinesini, birçok Ruby yorumlayıcısını vb. Kullanır.

Genellikle, bu dillerde, her zaman işaretçilerin etrafından nesnelere geçersiniz. Bir nesnenin kendisi, nesne meta verilerini (bir nesnenin türü, sınıf (lar) ı, belki erişim kontrol kısıtlamaları veya güvenlik ek açıklamaları vb.) Ve ardından gerçek nesne verilerinin kendisini içeren bir nesne başlığından oluşur. Dolayısıyla, basit bir tamsayı, bir işaretçi artı meta verilerden ve gerçek tam sayıdan oluşan bir nesne olarak temsil edilecektir. Çok kompakt bir gösterimle bile, bu basit bir tamsayı için 6 Bayt gibi bir şeydir.

Ayrıca, hızlı tamsayı aritmetiği gerçekleştirmek için böyle bir tamsayı nesnesini CPU'ya iletemezsiniz. İki tam sayı eklemek istiyorsanız, gerçekten sadece iki işaretçiniz vardır, bunlar eklemek istediğiniz iki tamsayı nesnesinin nesne başlıklarının başlangıcına işaret eder. Bu nedenle, ofseti tamsayı verilerinin depolandığı nesneye eklemek için ilk işaretçide tamsayı aritmetiği gerçekleştirmeniz gerekir. O zaman bu adrese başvurmak zorundasın. Aynısını ikinci tamsayı için tekrar yapın. Artık CPU'dan eklemesini isteyebileceğiniz iki tamsayınız var. Elbette, şimdi sonucu tutmak için yeni bir tamsayı nesnesi oluşturmalısınız.

Yani, bir tamsayı toplamayı gerçekleştirmek için, aslında üç tamsayı toplama artı iki işaretçi dererefence artı bir nesne oluşturma yapmanız gerekir . Ve neredeyse 20 Byte alırsınız.

Bununla birlikte, işin püf noktası, tamsayılar gibi değişmez değer türlerinde , genellikle nesne başlığındaki tüm meta verilere ihtiyacınız olmamasıdır : tüm bunları dışarıda bırakabilir ve basitçe sentezleyebilirsiniz (bu, VM nerd- "Sahte" için konuşun), birisi bakmayı umursadığında. Bir tamsayı her zaman sınıfa sahip olacaktır Integer, bu bilgiyi ayrı ayrı saklamaya gerek yoktur. Birisi bir tamsayı sınıfına dışarı şekle yansıma kullanıyorsa, sadece cevap Integerve kimse aslında nesne başlığında bu bilgiyi depolamak olmadığını bilecek ve bu aslında, orada değil hatta bir nesne başlığı (ya da nesne).

Böylece, trick değeri saklamak için bir işaretçi içindeki nesnenin için etkili bir şekilde tek, iki çöken, nesne.

Bir işaretçi (sözde etiket bitleri ) içinde, işaretçi hakkında ekstra bilgi depolamanıza izin veren gerçekte ek alana sahip CPU'lar vardır . "Bu aslında bir işaretçi değil, bu bir tam sayıdır" gibi ekstra bilgiler. Örnekler arasında Burroughs B5000, çeşitli Lisp Makineleri veya AS / 400 bulunur. Ne yazık ki, mevcut ana CPU'ların çoğu bu özelliğe sahip değil.

Bununla birlikte, bir çıkış yolu var: mevcut ana CPU'ların çoğu, adresler kelime sınırları üzerinde hizalanmadığında önemli ölçüde daha yavaş çalışıyor. Hatta bazıları hizalanmamış erişimi desteklemiyor.

Bunun anlamı, pratikte tüm işaretçilerin 4'e bölünebileceği, yani her zaman iki 0bitle biteceği anlamına gelir . Bu bize gerçek işaretçiler (biten 00) ile kılık değiştirmiş tamsayılar (ile bitenler) arasında ayrım yapmamızı sağlar 1. Ve yine de bizi 10başka şeyler yapmak için ücretsiz olarak biten tüm işaretçilerle bırakıyor . Ayrıca, çoğu modern işletim sistemi çok düşük adresleri kendilerine ayırır, bu da bize uğraşmamız gereken başka bir alan sağlar (örneğin, 24 0s ile başlayan ve biten işaretçiler 00).

Dolayısıyla, 31 bitlik bir tamsayıyı basitçe 1 bit sola kaydırıp ekleyerek bir işaretçi olarak kodlayabilirsiniz 1. Ve bunlarla çok hızlı tamsayı aritmetiği gerçekleştirebilirsiniz , onları uygun şekilde kaydırarak (bazen bu bile gerekli değildir).

Diğer adres alanlarıyla ne yapıyoruz? Tipik örnekler arasında kodlama bulunurfloat diğer büyük adres alanı ve benzeri özel nesnelerin bir dizi s true, false, nil, yakın 127 ASCII karakterleri, yaygın olarak kullanılan bazı kısa dizeleri, boş liste boş nesne, boş dizi ve benzeri 0adres.

Örneğin, MRI, YARV ve Rubinius Ruby yorumlayıcılarında, tamsayılar yukarıda tanımladığım şekilde falsekodlanır, adres olarak kodlanır 0(ki bu aynı zamandafalse C'deki temsilidir ), trueadres olarak 2(ki bu da öyle olur truebir bit kaydırılmış C gösterimi ) ve nilas 4.


5
Orada bu cevabı kesin olmayan olduğunu söylemek insanlar . Durumun bu olup olmadığı ya da kusup kalmadıkları hakkında hiçbir fikrim yok. Biraz gerçeği içeriyorsa onu işaret edeceğimi düşündüm.
surfmuggle

5
@threeFourOneSixOneThree Bu cevap OCaml için tam olarak doğru değildir çünkü OCaml'de bu cevabın “sentezle” kısmı asla yer almaz. OCaml, Smalltalk veya Java gibi nesne yönelimli bir dil değildir. Bir OCaml'nin yöntem tablosunu almak için hiçbir zaman bir neden yoktur int.
Pascal Cuoq

Chrome'un V8 motoru ayrıca etiketli bir işaretçi kullanıyor ve optimizasyon olarak smi (Küçük Tamsayı) adı verilen 31 bitlik bir tamsayı
depoluyor

@phuclv: Elbette bu şaşırtıcı değil. HotSpot JVM gibi, V8 de Animorphic Smalltalk VM'yi temel alır ve bu da Self VM'ye dayanır. Ve V8, HotSpot JVM, Animorphic Smalltalk VM ve Self VM'yi geliştiren aynı kişiler (bazıları) tarafından geliştirildi. Özellikle Lars Bak, bunların hepsinde ve ayrıca OOVM adlı kendi Smalltalk sanal makinesinde çalıştı. Smalltalkers tarafından Smalltalk teknolojisine dayalı olarak yaratıldığından, V8'in Smalltalk dünyasından iyi bilinen hileleri kullanması hiç de şaşırtıcı değil.
Jörg W Mittag

28

İyi bir açıklama için https://ocaml.org/learn/tutorials/performance_and_profiling.html'nin "tam sayıların, etiket bitlerinin, yığın ayrılan değerlerin gösterimi" bölümüne bakın .

Kısa cevap, performans için olmasıdır. Bir işleve bir bağımsız değişken iletilirken, ya bir tamsayı ya da bir işaretçi olarak iletilir. Makine seviyesinde bir dil seviyesinde, bir kaydın tamsayı veya işaretçi içerip içermediğini anlamanın bir yolu yoktur, sadece 32 veya 64 bitlik bir değerdir. Dolayısıyla, OCaml çalışma zamanı, aldığı şeyin tam sayı mı yoksa işaretçi mi olduğunu belirlemek için etiket bitini kontrol eder. Etiket biti ayarlanmışsa, değer bir tamsayıdır ve doğru aşırı yüklemeye aktarılır. Aksi takdirde bir göstericidir ve yazı yukarı bakılır.

Neden bu etikete sadece tamsayılar sahip? Çünkü diğer her şey bir işaretçi olarak aktarılır. İletilen şey ya bir tamsayı ya da başka bir veri türüne göstericidir. Yalnızca bir etiket biti ile yalnızca iki durum olabilir.


1
"Kısa cevap, performans için olmasıdır". Özellikle Coq'un performansı. Hemen hemen her şeyin performansı bu tasarım kararından muzdariptir.
JD

17

Tam olarak "çöp toplama için kullanılmaz". Bir işaretçi ile kutusuz bir tamsayıyı dahili olarak ayırt etmek için kullanılır.


2
Ve buna doğal sonucu tam o olduğunu en azından bir başka türü, yani işaretçileri için bu şekilde. Yüzer sayılar da 31 bit değilse, bunun nedeni yığın üzerinde nesneler olarak depolandıkları ve işaretçilerle anıldıkları içindir. Yine de dizileri için kompakt bir form olduğunu tahmin ediyorum.
Tom Anderson

2
Bu bilgi, işaretçi grafiğinde gezinmek için GC'nin ihtiyaç duyduğu şeydir.
Tobu

"Bir işaretçi ile kutusuz bir tamsayıyı dahili olarak ayırt etmek için kullanılır". Bunun için GC dışında başka bir şey kullanıyor mu?
JD

13

OP'nin 64-bit OCaml için 63-bit kayan nokta tipini daha fazla anlamasına yardımcı olmak için bu bağlantıyı eklemeliyim

Makalenin başlığı hakkında görünse floatde aslındaextra 1 bit

OCaml çalışma zamanı, türlerin tek tip gösterimi yoluyla polimorfizme izin verir. Her OCaml değeri tek bir kelime olarak temsil edilir, böylece bu listelere erişilecek (örn. List.length) ve derlenecek (örn. List.map) işlevlerle "şeylerin listesi" için tek bir uygulamaya sahip olmak mümkündür. bunlar ister tam sayıların, ister kayan sayıların listeleri, ister tam sayı kümeleri listeleri olsun, aynı şekilde çalışır.

Bir kelimeye sığmayan her şey, öbek içindeki bir blokta tahsis edilir. Bu veriyi temsil eden kelime daha sonra bloğa bir göstericidir. Yığın yalnızca kelime blokları içerdiğinden, tüm bu işaretçiler hizalanır: en önemsiz birkaç biti her zaman ayarlanmamıştır.

Argümansız kurucular (şunun gibi: meyve türü = Elma | Portakal | Muz) ve tamsayılar, yığın içinde ayrılması gereken çok fazla bilgiyi temsil etmez. Temsilleri kutudan çıkarılmıştır. Veriler, aksi takdirde bir işaretçi olacak olan kelimenin doğrudan içindedir. Bu nedenle, bir liste listesi aslında işaretçilerin bir listesi iken, bir dizi ints, bir eksi yönlendirme ile tam metinleri içerir. Girişler ve işaretçiler aynı boyutta olduğundan, listelere erişen ve oluşturan işlevler fark edilmez.

Yine de, Çöp Toplayıcının işaretçileri tam sayılardan tanıyabilmesi gerekir. Bir işaretçi, öbek içindeki iyi biçimlendirilmiş bir bloğa işaret eder ve bu bloğun tanımı gereği canlıdır (GC tarafından ziyaret edildiğinden) ve bu şekilde işaretlenmelidir. Bir tamsayı herhangi bir değere sahip olabilir ve önlem alınmazsa yanlışlıkla bir işaretçi gibi görünebilir. Bu, ölü blokların canlı görünmesine neden olabilir, ancak daha da kötüsü, aslında bir işaretçi gibi görünen ve kullanıcıyı karıştıran bir tamsayıyı takip ederken, GC'nin canlı bir bloğun başlığı olduğunu düşündüğü şeydeki bitleri değiştirmesine neden olur. veri.

Kutusuz tam sayıların OCaml programlayıcıya 31 bit (32 bit OCaml için) veya 63 bit (64 bit OCaml için) sağlamasının nedeni budur. Temsilde, perde arkasında, bir tamsayı içeren bir kelimenin en önemsiz biti, onu bir işaretçiden ayırmak için her zaman ayarlanır. 31 veya 63 bitlik tam sayılar oldukça sıra dışıdır, bu nedenle OCaml kullanan herkes bunu bilir. OCaml kullanıcılarının genellikle bilmediği şey, 64-bit OCaml için 63-bit kutusuz kayan tipin neden olmadığıdır.


3

OCaml'de bir int neden yalnızca 31 bittir?

Temel olarak, baskın işlemin desen eşleştirme olduğu ve baskın veri türlerinin varyant türleri olduğu Coq teorem kanıtlayıcı üzerinde mümkün olan en iyi performansı elde etmek için. En iyi veri temsilinin, işaretçileri kutulu olmayan verilerden ayırmak için etiketleri kullanan tek tip bir temsil olduğu bulunmuştur.

Ama neden diğer temel türler için değil de yalnızca ints için bu şekilde?

Sadece değil int. charVe numaralandırmalar gibi diğer türler aynı etiketli gösterimi kullanır.

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.