다른 곳에서는이 "기능"을 본 적이 없습니다. 32 번째 비트가 가비지 수집에 사용된다는 것을 알고 있습니다. 그러나 왜 다른 기본 유형이 아닌 int에만 그런 식입니까?
다른 곳에서는이 "기능"을 본 적이 없습니다. 32 번째 비트가 가비지 수집에 사용된다는 것을 알고 있습니다. 그러나 왜 다른 기본 유형이 아닌 int에만 그런 식입니까?
답변:
이를 태그 된 포인터 표현 이라고하며 수십 년 동안 다양한 인터프리터, VM 및 런타임 시스템에서 사용되는 매우 일반적인 최적화 트릭입니다. 거의 모든 Lisp 구현은 이들, 많은 Smalltalk VM, 많은 Ruby 인터프리터 등을 사용합니다.
일반적으로 이러한 언어에서는 항상 개체에 대한 포인터를 전달합니다. 객체 자체는 객체 메타 데이터 (객체 유형, 클래스, 액세스 제어 제한 또는 보안 주석 등)와 실제 객체 데이터 자체를 포함하는 객체 헤더로 구성됩니다. 따라서 간단한 정수는 포인터와 메타 데이터 및 실제 정수로 구성된 개체로 표시됩니다. 매우 간결한 표현으로도 단순한 정수의 경우 6 바이트와 같습니다.
또한 이러한 정수 객체를 CPU에 전달하여 빠른 정수 산술을 수행 할 수 없습니다. 두 개의 정수를 추가하려면 실제로 추가하려는 두 정수 개체의 개체 헤더 시작을 가리키는 포인터가 두 개뿐입니다. 따라서 먼저 첫 번째 포인터에서 정수 산술을 수행하여 정수 데이터가 저장된 개체에 오프셋을 추가해야합니다. 그런 다음 해당 주소를 역 참조해야합니다. 두 번째 정수로 다시 똑같이하십시오. 이제 두 개의 정수가 있으므로 실제로 CPU에 추가하도록 요청할 수 있습니다. 물론 결과를 담기 위해 새로운 정수 객체를 생성해야합니다.
따라서 하나의 정수 더하기 를 수행하려면 실제로 세 개의 정수 더하기, 두 개의 포인터 역 참조 및 하나의 객체 생성 을 수행해야합니다 . 그리고 거의 20 바이트를 차지합니다.
그러나 트릭은 정수와 같은 소위 불변 값 유형을 사용 하면 일반적으로 개체 헤더의 모든 메타 데이터 가 필요 하지 않습니다 . 모든 항목을 제외하고 간단히 합성 할 수 있습니다 (VM-nerd- 누군가가보고 싶어 할 때 "가짜"라고 말합니다. 정수에는 항상 class Integer
가 있으므로 해당 정보를 별도로 저장할 필요가 없습니다. 누군가가 정수의 클래스 알아낼 반사를 사용하는 경우, 당신은 단순히 응답 Integer
하고 아무도 당신이 실제로 객체 헤더에 정보를 저장하지 않았 음을 알 수 없으며 그 사실이 없는 경우에도 객체 헤더 (또는 목적).
그래서, 트릭의 값은 저장하는 것입니다 의 포인터 내에서 객체를 에 효과적으로 하나에 두 개의 붕괴, 객체.
포인터 자체 내에 포인터에 대한 추가 정보를 저장할 수 있는 포인터 (소위 태그 비트 ) 내에 실제로 추가 공간이있는 CPU가 있습니다. "이것은 실제로 포인터가 아닙니다. 이것은 정수입니다."와 같은 추가 정보. 예로는 Burroughs B5000, 다양한 Lisp Machines 또는 AS / 400이 있습니다. 안타깝게도 현재의 대부분의 메인 스트림 CPU에는 해당 기능이 없습니다.
그러나 탈출구가 있습니다. 대부분의 최신 메인 스트림 CPU는 주소가 단어 경계에 정렬되지 않으면 훨씬 느리게 작동합니다. 일부는 정렬되지 않은 액세스를 전혀 지원하지 않습니다.
이것이 의미하는 바는 실제로 모든 포인터는 4로 나눌 수 있다는 것입니다. 즉, 항상 2 0
비트로 끝납니다 . 이것은 실제 포인터 (로 끝나는 00
)와 실제로 변장 된 정수인 포인터 (로 끝나는 것)를 구별 할 수있게합니다 1
. 그리고 그것은 우리에게 10
다른 일을 할 수 있는 자유로 끝나는 모든 포인터를 남깁니다 . 또한 대부분의 최신 운영 체제는 자체적으로 매우 낮은 주소를 예약하므로 다른 영역 (예 : 24 0
초로 시작 하고로 끝나는 포인터)을 엉망으로 만들 수 00
있습니다.
따라서 31 비트 정수를 왼쪽으로 1 비트 이동하고 추가 1
하여 포인터로 인코딩 할 수 있습니다 . 그리고 그것들을 적절하게 이동함으로써 (때로는 필요하지 않은 경우도 있음) 매우 빠른 정수 산술을 수행 할 수 있습니다 .
다른 주소 공간으로 우리는 무엇을합니까? 음, 전형적인 예는 인코딩 등이 float
다른 큰 주소 공간과 같은 특수 목적의 숫자에들 true
, false
, nil
, 가까운 127 개 ASCII 문자, 일반적으로 사용되는 짧은 문자열, 빈리스트, 빈 오브젝트, 빈 배열 등 0
주소.
예를 들어, MRI, YARV 및 Rubinius 루비 인터프리터에, 정수, I는 전술 한 방법을 인코딩 false
주소로 인코딩된다 0
(너무 발생 도 의 표현으로 false
, C)에 true
어드레스로서 2
너무 우연히 ( C의 표현은 true
하나의 비트 시프트)와 nil
같은 4
.
int
.
좋은 설명 은 https://ocaml.org/learn/tutorials/performance_and_profiling.html의 "정수, 태그 비트, 힙 할당 값 표시"섹션을 참조하십시오 .
짧은 대답은 성능을위한 것입니다. 함수에 인수를 전달할 때 정수 또는 포인터로 전달됩니다. 기계 수준 언어 수준에서는 레지스터에 정수 또는 포인터가 포함되어 있는지 알 수있는 방법이 없습니다. 단지 32 비트 또는 64 비트 값입니다. 따라서 OCaml 런타임은 수신 한 것이 정수인지 포인터인지 결정하기 위해 태그 비트를 확인합니다. 태그 비트가 설정된 경우 값은 정수이며 올바른 오버로드로 전달됩니다. 그렇지 않으면 포인터이고 유형이 조회됩니다.
정수에만이 태그가있는 이유는 무엇입니까? 다른 모든 것은 포인터로 전달되기 때문입니다. 전달되는 것은 정수 또는 다른 데이터 유형에 대한 포인터입니다. 태그 비트가 하나만 있으면 케이스가 두 개만있을 수 있습니다.
정확히 "가비지 수집에 사용"되지 않습니다. 포인터와 unboxed 정수를 내부적으로 구별하는 데 사용됩니다.
64 비트 OCaml에 대한 63 비트 부동 소수점 유형을 더 많이 이해할 수 있도록 OP가이 링크를 추가해야합니다.
기사의 제목은에 대한 것처럼 보이지만 float
실제로는extra 1 bit
OCaml 런타임은 유형의 균일 한 표현을 통해 다형성을 허용합니다. 모든 OCaml 값은 단일 단어로 표시되므로 이러한 목록에 액세스 (예 : List.length) 및 빌드 (예 : List.map)하는 함수를 사용하여 "사물 목록"과 같은 단일 구현을 가질 수 있습니다. 정수 목록, 부동 소수점 또는 정수 세트 목록이든 똑같이 작동합니다.
단어에 맞지 않는 것은 힙의 블록에 할당됩니다. 이 데이터를 나타내는 단어는 블록에 대한 포인터입니다. 힙에는 단어 블록 만 포함되어 있으므로 이러한 모든 포인터가 정렬됩니다. 최하위 비트는 항상 설정되지 않습니다.
인수없는 생성자 (예 : type fruit = Apple | Orange | Banana) 및 정수는 힙에 할당해야 할 정보가 많지 않습니다. 그들의 표현은 상자가 없습니다. 데이터는 그렇지 않으면 포인터 였을 단어 안에 직접 있습니다. 따라서 목록 목록은 실제로 포인터 목록이지만 int 목록에는 간접적이 하나 적은 int가 포함됩니다. 목록에 액세스하고 작성하는 함수는 int와 포인터의 크기가 같기 때문에 알아 차리지 못합니다.
그래도 가비지 콜렉터는 정수의 포인터를 인식 할 수 있어야합니다. 포인터는 정의상 살아있는 (GC에서 방문하기 때문에) 힙에서 잘 구성된 블록을 가리키며 그렇게 표시되어야합니다. 정수는 모든 값을 가질 수 있으며 예방 조치를 취하지 않으면 실수로 포인터처럼 보일 수 있습니다. 이로 인해 데드 블록이 살아있는 것처럼 보일 수 있지만 훨씬 더 나쁜 것은 실제로 포인터처럼 보이는 정수를 따르고 사용자를 엉망으로 만들 때 GC가 라이브 블록의 헤더라고 생각하는 비트를 변경하게 만듭니다. 데이터.
이것이 unboxed 정수가 OCaml 프로그래머에게 31 비트 (32 비트 OCaml의 경우) 또는 63 비트 (64 비트 OCaml의 경우)를 제공하는 이유입니다. 표현에서,이면에서 정수를 포함하는 단어의 최하위 비트는 포인터와 구별하기 위해 항상 설정됩니다. 31 비트 또는 63 비트 정수는 다소 드문 경우이므로 OCaml을 사용하는 사람은 누구나 이것을 알고 있습니다. OCaml 사용자가 일반적으로 알지 못하는 것은 64 비트 OCaml 용 63 비트 unboxed float 유형이없는 이유입니다.