OCaml의 정수가 31 비트 인 이유는 무엇입니까?


115

다른 곳에서는이 "기능"을 본 적이 없습니다. 32 번째 비트가 가비지 수집에 사용된다는 것을 알고 있습니다. 그러나 왜 다른 기본 유형이 아닌 int에만 그런 식입니까?


10
64 비트 운영 체제에서 OCaml의 int는 31 비트가 아니라 63 비트입니다. 이렇게하면 태그 비트의 실제 문제 (배열 크기 제한 등)의 대부분이 제거됩니다. 물론 표준 알고리즘에 실제 32 비트 정수가 필요한 경우 int32 유형이 있습니다.
Porculus

1
nekoVM ( nekovm.org )도 최근까지 31 비트 정수를 가졌습니다.
TheHippo 2013-06-26

답변:


244

이를 태그 된 포인터 표현 이라고하며 수십 년 동안 다양한 인터프리터, 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.


5
이 대답이 정확하지 않다고 말하는 사람들 이 있습니다 . 나는 이것이 사실인지 또는 그들이 nitpicking인지 전혀 모른다. 나는 단지 그것이 어떤 진실을 담고 있다면 그것을 지적 할 것이라고 생각했습니다.
surfmuggle 2013-06-28

5
@threeFourOneSixOneThree OCaml에서이 답변의 "합성"부분이 발생하지 않기 때문에이 답변은 OCaml에 대해 완전히 정확하지 않습니다. OCaml은 Smalltalk 또는 Java와 같은 객체 지향 언어가 아닙니다. OCaml의 메소드 테이블을 검색 할 이유가 없습니다 int.
Pascal Cuoq 2013 년

Chrome의 V8 엔진은 또한 태그 된 포인터를 사용하고 최적화로 smi (Small Integer) 라고하는 31 비트 정수를 저장합니다. \
phuclv

@phuclv : 물론 놀라운 일이 아닙니다. HotSpot JVM과 마찬가지로 V8은 Animorphic Smalltalk VM을 기반으로하며, 이는 차례로 Self VM을 기반으로합니다. V8은 HotSpot JVM, Animorphic Smalltalk VM 및 Self VM을 개발 한 동일한 사람들에 의해 개발되었습니다. 특히 Lars Bak은이 모든 작업과 OOVM이라는 자신의 Smalltalk VM을 작업했습니다. 따라서 V8이 Smalltalk 기술을 기반으로 Smalltalkers에 의해 만들어 졌기 때문에 Smalltalk 세계에서 잘 알려진 트릭을 사용한다는 것은 전혀 놀라운 일이 아닙니다.
Jörg W Mittag 19

28

좋은 설명 은 https://ocaml.org/learn/tutorials/performance_and_profiling.html의 "정수, 태그 비트, 힙 할당 값 표시"섹션을 참조하십시오 .

짧은 대답은 성능을위한 것입니다. 함수에 인수를 전달할 때 정수 또는 포인터로 전달됩니다. 기계 수준 언어 수준에서는 레지스터에 정수 또는 포인터가 포함되어 있는지 알 수있는 방법이 없습니다. 단지 32 비트 또는 64 비트 값입니다. 따라서 OCaml 런타임은 수신 한 것이 정수인지 포인터인지 결정하기 위해 태그 비트를 확인합니다. 태그 비트가 설정된 경우 값은 정수이며 올바른 오버로드로 전달됩니다. 그렇지 않으면 포인터이고 유형이 조회됩니다.

정수에만이 태그가있는 이유는 무엇입니까? 다른 모든 것은 포인터로 전달되기 때문입니다. 전달되는 것은 정수 또는 다른 데이터 유형에 대한 포인터입니다. 태그 비트가 하나만 있으면 케이스가 두 개만있을 수 있습니다.


1
"짧은 대답은 성능을위한 것입니다." 특히 Coq의 성능. 이 디자인 결정으로 인해 거의 모든 것의 성능이 저하됩니다.
JD

17

정확히 "가비지 수집에 사용"되지 않습니다. 포인터와 unboxed 정수를 내부적으로 구별하는 데 사용됩니다.


2
그리고 그에게 추론이 있다는 것입니다 이다 적어도 하나의 다른 형태, 즉 포인터에 대한 그런 식으로. float가 31 비트가 아니라면 힙에 객체로 저장되고 포인터로 참조되기 때문이라고 가정합니다. 그래도 배열에 대한 압축 형식이 있다고 생각합니다.
Tom Anderson

2
이 정보는 정확히 GC가 포인터 그래프를 탐색하는 데 필요한 것입니다.
Tobu

"포인터와 unboxed 정수를 내부적으로 구분하는 데 사용됩니다." GC 이외의 다른 용도로 사용합니까?
JD

13

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 유형이없는 이유입니다.


3

OCaml의 정수가 31 비트 인 이유는 무엇입니까?

기본적으로, 지배적 인 연산이 패턴 일치이고 지배적 인 데이터 유형이 변형 유형 인 Coq 정리 증명 자에서 최상의 성능을 얻으려면. 가장 좋은 데이터 표현은 태그를 사용하여 포인터를 박스 화되지 않은 데이터와 구별하는 균일 한 표현으로 밝혀졌습니다.

그러나 왜 다른 기본 유형이 아닌 int에만 그런 식입니까?

뿐만 아니라 int. char및 enum 과 같은 다른 유형 은 동일한 태그 표시를 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.