Tại sao một int trong OCaml chỉ có 31 bit?


115

Không thấy "tính năng" này ở bất cứ nơi nào khác. Tôi biết rằng bit thứ 32 được sử dụng để thu gom rác. Nhưng tại sao nó chỉ dành cho ints mà không phải cho các loại cơ bản khác?


10
Lưu ý rằng trên các hệ điều hành 64 bit, một int trong OCaml là 63 bit, không phải 31. Điều này loại bỏ hầu hết các vấn đề thực tế (như giới hạn kích thước mảng) của bit thẻ. Và dĩ nhiên, có loại int32 nếu bạn cần một số nguyên 32 bit thực tế cho một số thuật toán tiêu chuẩn.
Porculus

1
nekoVM ( nekovm.org ) cũng có ints 31 bit cho đến gần đây.
TheHippo

Câu trả lời:


244

Đây được gọi là biểu diễn con trỏ được gắn thẻ và là một mẹo tối ưu hóa khá phổ biến được sử dụng trong nhiều trình thông dịch, VM và hệ thống thời gian chạy khác nhau trong nhiều thập kỷ. Khá nhiều triển khai Lisp sử dụng chúng, nhiều máy ảo Smalltalk, nhiều trình thông dịch Ruby, v.v.

Thông thường, trong các ngôn ngữ đó, bạn luôn truyền xung quanh con trỏ tới các đối tượng. Bản thân một đối tượng bao gồm một tiêu đề đối tượng, chứa siêu dữ liệu đối tượng (như loại đối tượng, lớp của nó, có thể truy cập các hạn chế kiểm soát hoặc chú thích bảo mật, v.v.), sau đó là chính dữ liệu đối tượng thực tế. Vì vậy, một số nguyên đơn giản sẽ được biểu diễn dưới dạng con trỏ cộng với một đối tượng bao gồm siêu dữ liệu và số nguyên thực tế. Ngay cả với một đại diện rất nhỏ gọn, đó là khoảng 6 Byte cho một số nguyên đơn giản.

Ngoài ra, bạn không thể truyền một đối tượng số nguyên như vậy cho CPU để thực hiện số học số nguyên nhanh. Nếu bạn muốn thêm hai số nguyên, bạn thực sự chỉ có hai con trỏ, trỏ đến phần đầu của các tiêu đề đối tượng của hai đối tượng số nguyên bạn muốn thêm. Vì vậy, trước tiên bạn cần thực hiện số học số nguyên trên con trỏ đầu tiên để thêm phần bù vào đối tượng cho nó nơi lưu trữ dữ liệu số nguyên. Sau đó, bạn phải bãi bỏ địa chỉ đó. Làm tương tự một lần nữa với số nguyên thứ hai. Bây giờ bạn có hai số nguyên mà bạn thực sự có thể yêu cầu CPU thêm. Tất nhiên, bây giờ bạn cần xây dựng một đối tượng số nguyên mới để giữ kết quả.

Vì vậy, để thực hiện một phép cộng số nguyên, bạn thực sự cần phải thực hiện ba phép cộng số nguyên cộng với hai phép trừ con trỏ cộng với một cấu trúc đối tượng. Và bạn mất gần 20 Byte.

Tuy nhiên, mẹo ở đây là với các loại giá trị bất biến như số nguyên, bạn thường không cần tất cả siêu dữ liệu trong tiêu đề đối tượng: bạn có thể bỏ qua tất cả những thứ đó và chỉ cần tổng hợp nó (đó là VM-nerd- nói cho "giả nó"), khi bất cứ ai quan tâm để nhìn. Một số nguyên sẽ luôn có lớp Integer, không cần lưu trữ thông tin đó một cách riêng biệt. Nếu ai đó sử dụng phản xạ để tìm ra lớp của một số nguyên, bạn chỉ cần trả lời Integervà không ai biết rằng bạn thực sự không lưu trữ thông tin đó trong tiêu đề đối tượng và trên thực tế, thậm chí không có tiêu đề đối tượng (hoặc một vật).

Vì vậy, mẹo là lưu trữ giá trị của đối tượng trong con trỏ tới đối tượng, thu gọn hai thành một.

Có những CPU thực sự có thêm không gian trong một con trỏ (được gọi là các bit thẻ ) cho phép bạn lưu trữ thêm thông tin về con trỏ trong chính con trỏ. Thông tin bổ sung như "đây không thực sự là một con trỏ, đây là một số nguyên". Các ví dụ bao gồm Burroughs B5000, các máy Lisp khác nhau hoặc AS / 400. Thật không may, hầu hết các CPU chính hiện tại không có tính năng đó.

Tuy nhiên, có một lối thoát: hầu hết các CPU chính hiện tại hoạt động chậm hơn đáng kể khi các địa chỉ không được căn chỉnh trên ranh giới từ. Một số thậm chí không hỗ trợ truy cập không được chỉ định ở tất cả.

Điều này có nghĩa là trong thực tế, tất cả các con trỏ sẽ chia hết cho 4, có nghĩa là chúng sẽ luôn kết thúc bằng hai 0bit. Điều này cho phép chúng ta phân biệt giữa các con trỏ thực (kết thúc bằng 00) và các con trỏ thực sự là số nguyên được ngụy trang (những con trỏ kết thúc bằng 1). Và nó vẫn để lại cho chúng ta tất cả các con trỏ kết thúc 10miễn phí để làm những thứ khác. Ngoài ra, hầu hết các hệ điều hành hiện đại đều dành riêng các địa chỉ rất thấp, điều này cho chúng ta một khu vực khác để giải quyết (các con trỏ bắt đầu bằng, giả sử, 24 0giây và kết thúc bằng 00).

Vì vậy, bạn có thể mã hóa một số nguyên 31 bit thành một con trỏ, chỉ cần dịch chuyển 1 bit sang trái và thêm 1vào đó. Và bạn có thể thực hiện số học số nguyên rất nhanh với những số đó, bằng cách đơn giản thay đổi chúng một cách thích hợp (đôi khi thậm chí không cần thiết).

Chúng ta làm gì với những không gian địa chỉ khác? Chà, ví dụ điển hình bao gồm mã hóa floattrong không gian địa chỉ lớn khác và một số đối tượng đặc biệt như true,false , nil, 127 ký tự ASCII, một số chuỗi ngắn thường được sử dụng, danh sách rỗng, đối tượng rỗng, mảng trống và vân vân gần 0Địa chỉ.

Ví dụ: trong các trình thông dịch MRI, YARV và Rubinius Ruby, các số nguyên được mã hóa theo cách tôi mô tả ở trên, falseđược mã hóa dưới dạng địa chỉ 0( cũng chính là đại diện của falseC), truenhư là địa chỉ 2(điều này rất đúng biểu diễn C của truedịch chuyển một bit) và nilas 4.


5
những người nói rằng câu trả lời này là không chính xác . Tôi không biết nếu đây là trường hợp hoặc nếu họ đang nitpicking. Tôi chỉ nghĩ rằng tôi sẽ chỉ ra nó trong trường hợp nó chứa một số sự thật.
lướt sóng

5
@threeFourOneSixOneThree Câu trả lời này không hoàn toàn chính xác cho OCaml bởi vì, ở OCaml, câu trả lời này đã tổng hợp nó một phần không bao giờ diễn ra. OCaml không phải là ngôn ngữ hướng đối tượng như Smalltalk hay Java. Không bao giờ có bất kỳ lý do để lấy bảng phương thức của một OCaml int.
Pascal Cuoq

Công cụ V8 của Chrome cũng sử dụng một con trỏ được gắn thẻ và lưu trữ một số nguyên 31 bit được gọi là smi (Số nguyên nhỏ) dưới dạng tối ưu hóa \
phuclv

@phuclv: Điều này không đáng ngạc nhiên, tất nhiên. Giống như JVM của HotSpot, V8 dựa trên VM hình động nhỏ, mà lần lượt dựa trên Self VM. Và V8 được phát triển bởi (một số) cùng những người đã phát triển HotSpot JVM, Animularic Smalltalk VM và Self VM. Lars Bak, đặc biệt, đã làm việc trên tất cả những thứ đó, cộng với VM Smalltalk của riêng anh ta được gọi là OOVM. Vì vậy, không có gì đáng ngạc nhiên khi V8 sử dụng các thủ thuật nổi tiếng từ thế giới Smalltalk, vì nó được tạo ra bởi Smalltalkers dựa trên công nghệ Smalltalk.
Jörg W Mittag

28

Xem phần "biểu diễn số nguyên, bit thẻ, giá trị phân bổ heap" của https://ocaml.org/learn/tutorials/performance_and_profiling.html để biết mô tả hay.

Câu trả lời ngắn gọn là nó là cho hiệu suất. Khi truyền một đối số cho một hàm, nó được truyền dưới dạng một số nguyên hoặc một con trỏ. Ở cấp độ ngôn ngữ cấp độ máy, không có cách nào để biết liệu thanh ghi có chứa số nguyên hay con trỏ hay không, nó chỉ là giá trị 32 hoặc 64 bit. Vì vậy, thời gian chạy OCaml kiểm tra bit thẻ để xác định xem những gì nó nhận được là số nguyên hay con trỏ. Nếu bit thẻ được đặt, thì giá trị là một số nguyên và nó được chuyển đến quá tải chính xác. Nếu không, nó là một con trỏ và loại được tra cứu.

Tại sao chỉ có số nguyên có thẻ này? Bởi vì mọi thứ khác được thông qua như một con trỏ. Những gì được thông qua là một số nguyên hoặc một con trỏ tới một số loại dữ liệu khác. Chỉ với một bit tag, chỉ có thể có hai trường hợp.


1
"Câu trả lời ngắn gọn là nó dành cho hiệu suất". Cụ thể là hiệu suất của Coq. Hiệu suất của hầu hết mọi thứ khác phải chịu từ quyết định thiết kế này.
JD

17

Nó không chính xác "được sử dụng để thu gom rác." Nó được sử dụng để phân biệt bên trong giữa một con trỏ và một số nguyên không có hộp.


2
Và hệ quả của điều đó là nó cách đó cho ít nhất một loại khác, cụ thể là con trỏ. Nếu số float cũng không phải là 31 bit, thì tôi cho rằng đó là vì chúng được lưu trữ dưới dạng đối tượng trên heap và được gọi bằng con trỏ. Tôi đoán rằng có một hình thức nhỏ gọn cho các mảng của họ, mặc dù.
Tom Anderson

2
Thông tin đó chính xác là những gì mà GC cần để điều hướng biểu đồ con trỏ.
Tobu

"Nó được sử dụng để phân biệt bên trong giữa một con trỏ và một số nguyên không có hộp". Có bất cứ điều gì khác sử dụng nó cho điều đó ngoài GC?
JD

13

Tôi phải thêm liên kết này để giúp OP hiểu thêm Loại dấu phẩy động 63 bit cho OCaml 64 bit

Mặc dù tiêu đề của bài viết có vẻ như float, nhưng nó thực sự nói vềextra 1 bit

Thời gian chạy OCaml cho phép đa hình thông qua biểu diễn thống nhất của các loại. Mỗi giá trị OCaml được biểu diễn dưới dạng một từ duy nhất, do đó, có thể có một triển khai duy nhất cho, ví dụ, danh sách các thứ, một chức năng để truy cập (ví dụ như List.length) và xây dựng (ví dụ List.map) các danh sách này hoạt động giống nhau cho dù chúng là danh sách số nguyên, số float hay danh sách các bộ số nguyên.

Bất cứ điều gì không phù hợp trong một từ được phân bổ trong một khối trong heap. Từ đại diện cho dữ liệu này sau đó là một con trỏ tới khối. Vì heap chỉ chứa các khối từ, nên tất cả các con trỏ này được căn chỉnh: các bit có ý nghĩa nhỏ nhất của chúng luôn luôn không được đặt.

Các nhà xây dựng không tranh cãi (như thế này: gõ fruit = Apple | Orange | Banana) và các số nguyên không thể hiện quá nhiều thông tin mà chúng cần được phân bổ trong heap. Đại diện của họ là unboxed. Dữ liệu trực tiếp bên trong từ mà nếu không sẽ là một con trỏ. Vì vậy, trong khi một danh sách các danh sách thực sự là một danh sách các con trỏ, một danh sách các int chứa các int với một ít chỉ định. Các chức năng truy cập và xây dựng danh sách không nhận thấy vì ints và con trỏ có cùng kích thước.

Tuy nhiên, Garbage Collector cần có khả năng nhận ra các con trỏ từ các số nguyên. Một con trỏ trỏ đến một khối được tạo tốt trong heap theo định nghĩa còn sống (vì nó đang được truy cập bởi GC) và nên được đánh dấu như vậy. Một số nguyên có thể có bất kỳ giá trị nào và nếu không thực hiện các biện pháp phòng ngừa, vô tình trông giống như một con trỏ. Điều này có thể khiến các khối chết trông sống động, nhưng tệ hơn nữa, nó cũng sẽ khiến cho GC thay đổi các bit theo cái mà nó nghĩ là tiêu đề của một khối sống, khi nó thực sự đi theo một số nguyên trông giống như một con trỏ và gây rối cho người dùng dữ liệu.

Đây là lý do tại sao các số nguyên không có hộp cung cấp 31 bit (cho OCaml 32 bit) hoặc 63 bit (cho OCaml 64 bit) cho lập trình viên OCaml. Trong biểu diễn, đằng sau hậu trường, phần nhỏ nhất của một từ có chứa một số nguyên luôn được đặt, để phân biệt nó với một con trỏ. Số nguyên 31 hoặc 63 bit khá bất thường, vì vậy bất cứ ai sử dụng OCaml đều biết điều này. Những gì người dùng OCaml thường không biết là tại sao không có loại float không có hộp thư 63 bit cho OCaml 64 bit.


3

Tại sao một int trong OCaml chỉ có 31 bit?

Về cơ bản, để có được hiệu suất tốt nhất có thể trên máy chủ định lý Coq trong đó hoạt động chi phối là khớp mẫu và các kiểu dữ liệu chi phối là các loại biến thể. Biểu diễn dữ liệu tốt nhất được tìm thấy là một biểu diễn thống nhất sử dụng các thẻ để phân biệt các con trỏ với dữ liệu không được hộp.

Nhưng tại sao nó chỉ dành cho ints mà không phải cho các loại cơ bản khác?

Không chỉ int. Các loại khác như charvà enums sử dụng cùng một đại diện được gắn thẻ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.