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?
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?
Câu trả lời:
Đâ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 Integer
và 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 0
bit. Đ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 10
miễ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 0
giâ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 1
và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 float
trong 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 false
C), true
như là địa chỉ 2
(điều này rất đúng biểu diễn C của true
dịch chuyển một bit) và nil
as 4
.
int
.
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.
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.
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.
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ư char
và enums sử dụng cùng một đại diện được gắn thẻ.