int와 같은 기본 유형을 클래스로 구현할 때의주의 사항은 무엇입니까? 유형이 있고

객체 지향 프로그래밍 언어를 디자인하고 함축 할 때, 어떤 시점에서 기본 유형 (예 int: float, double또는 등가물)을 클래스 또는 다른 것으로 구현하는 방법을 선택해야합니다 . 분명히 C 패밀리의 언어는 클래스로 정의 하지 않는 경향이 있습니다 (자바에는 특수한 기본 유형이 있고 C #은이를 불변의 구조체로 구현합니다).

기본 유형이 클래스로 구현 될 때 (통합 계층 구조가있는 유형 시스템에서) 매우 중요한 이점을 생각할 수 있습니다. 이러한 유형은 루트 유형의 적절한 Liskov 하위 유형이 될 수 있습니다. 따라서 우리는 언어를 boxing / unboxing (명시 적 또는 암시 적), 랩퍼 유형, 특수 분산 규칙, 특수 동작 등과 복잡하게 만드는 것을 피합니다.

물론 언어 디자이너가 자신의 방식을 결정하는 이유를 부분적으로 이해할 수 있습니다. 클래스 인스턴스는 메모리 레이아웃에 vtable 또는 기타 메타 데이터가 포함될 수 있기 때문에 약간의 공간 오버 헤드가있는 경향이 있습니다. (언어가 상속을 허용하지 않는 경우).

기본 유형이 종종 클래스 가 아닌 유일한 이유는 공간 효율성 (및 특히 큰 배열에서 향상된 공간 위치) 입니까?

필자는 일반적으로 대답이 ‘예’라고 가정했지만 컴파일러에는 이스케이프 분석 알고리즘이 있으므로 인스턴스 (기본 유형이 아닌 모든 인스턴스)가 엄격하게 입증되면 공간 오버 헤드를 생략 할 수 있는지 여부를 추론 할 수 있습니다 노동 조합 지부.

위의 내용이 잘못 되었습니까? 아니면 누락 된 것이 있습니까?



답변

그렇습니다, 그것은 효율성에 달려 있습니다. 그러나 영향을 과소 평가하거나 다양한 최적화가 얼마나 잘 작동하는지 과대 평가하는 것 같습니다.

첫째, 그것은 단지 “공간적 오버 헤드”가 아닙니다. 박스형 / 힙 할당 된 프리미티브를 만들면 성능 비용도 있습니다. GC에는 이러한 객체를 할당하고 수집해야하는 추가적인 압력이 있습니다. “원시 객체”가 불변 인 경우에는 두 배가됩니다. 그런 다음 간접적 인 캐시와 주어진 캐시 양에 적은 데이터가 들어가기 때문에 캐시 누락이 더 많습니다. “객체의 주소를로드 한 다음 해당 주소에서 실제 값을로드”한다는 사실은 “값을 직접로드”하는 것보다 더 많은 지시를받습니다.

둘째, 탈출 분석은 더 빠른 요정 먼지가 아닙니다. 이스케이프하지 않는 값에만 적용됩니다. 루프 계산 및 중간 계산 결과와 같은 로컬 계산을 최적화하는 것이 좋으며 측정 가능한 이점이 있습니다. 그러나 훨씬 더 많은 값이 객체와 배열의 필드에 존재합니다. 물론, 그것들은 이스케이프 분석 자체가 될 수 있지만, 일반적으로 변경 가능한 참조 유형이기 때문에, 그것들의 앨리어싱은 이스케이프 분석에 중대한 도전을 제시합니다. (2) 할당을 제거 할 목적으로 차이를 만들지 않습니다.

(게터 포함) 메소드를 호출하거나 인자로 객체를 전달 감안할 때 모든 객체의 탈출을 도울 수있는 다른 방법을하면, 당신은 모두 간 분석하지만, 대부분의 사소한 경우 필요합니다. 이것은 훨씬 비싸고 복잡합니다.

그리고 상황이 실제로 탈출하여 합리적으로 최적화 할 수없는 경우가 있습니다. 실제로 C 프로그래머가 힙 할당 문제를 얼마나 자주 겪고 있는지 고려한다면 상당히 많은 것들입니다. int가 포함 된 객체가 이스케이프하면 이스케이프 분석이 int에 적용되지 않습니다. 효율적인 프리미티브 필드에 작별 인사를하십시오 .

이는 또 다른 요점과 관련이 있습니다. 필요한 분석 및 최적화는 매우 복잡하고 활발한 연구 분야입니다. 어떤 언어 구현이 제안한 최적화 수준을 달성했는지 여부는 논쟁의 여지가 있지만, 그렇게해도 드물고 힘든 노력이었습니다. 이 거인들의 어깨에 서있는 것이 거인이되는 것보다 쉽지만 여전히 사소한 것은 아닙니다. 처음 몇 년 동안 언제라도 경쟁력있는 성능을 기대하지 마십시오.

그것은 그런 언어가 실용적이지 않다는 것은 아닙니다. 분명히 그들은 있습니다. 전용 프리미티브가있는 언어만큼 빠른 라인-라인이라고 가정하지 마십시오. 다시 말해, 충분히 똑똑한 컴파일러의 비전으로 자신을 회피하지 마십시오 .


답변

기본 유형이 종종 클래스가 아닌 유일한 이유는 공간 효율성 (및 특히 큰 배열에서 향상된 공간 위치)입니까?

아니.

다른 문제는 기본 유형이 기본 작업에 사용되는 경향이 있다는 것입니다. 컴파일러 int + int는 함수 호출로 컴파일되지 않고 일부 기본 CPU 명령어 (또는 동등한 바이트 코드)로 컴파일 될 것임을 알아야합니다 . 이 시점에서 int일반 객체로 사용하는 경우 어쨌든 효과적으로 물건을 개봉해야합니다.

이러한 종류의 작업은 실제로 서브 타이핑을 잘하지 않습니다. CPU 명령으로 디스패치 할 수 없습니다. CPU 명령어 에서 디스패치 할 수 없습니다 . 하위 유형의 전체 요점은 당신이 할 수있는 D곳을 사용할 수 있다는 것을 의미합니다 B. CPU 명령어는 다형성이 아닙니다. 프리미티브가이를 수행하려면 간단한 추가 (또는 기타)로 여러 배의 작업 비용이 소요되는 디스패치 로직으로 작업을 래핑해야합니다. int유형 계층 구조의 일부가 된다는 이점은 봉인 / 최종시 약간의 문제가됩니다. 그리고 그것은 바이너리 연산자에 대한 디스패치 로직으로 모든 두통을 무시하고 있습니다 …

기본적으로, 원시적 형은 주위에 특별한 규칙을 많이 가질 필요가 얼마나 컴파일러 핸들 그들, 사용자가 자신의 유형으로 무엇을 할 수 있는지 어쨌든 , 그냥 완전히 별개로 취급하는 것이 시간이 간단하므로.


답변

“기본 유형”이 전체 객체가되는 경우는 거의 없습니다 (여기서 객체는 디스패치 메커니즘에 대한 포인터를 포함하거나 디스패치 메커니즘에서 사용할 수있는 유형으로 태그가 지정된 데이터입니다).

  • 사용자 정의 유형이 기본 유형에서 상속 할 수 있기를 원합니다. 일반적으로 성능 및 보안 관련 두통을 유발하므로 바람직하지 않습니다. 컴파일은 int특정 고정 크기를 가지거나 메소드가 재정의 되지 않았다고 가정 할 수 없기 때문에 성능 문제 이며, ints의 의미 가 파괴 될 수 있기 때문에 보안 문제입니다 (임의의 정수 또는 정수를 고려하십시오). 불변이 아닌 값을 변경합니다).

  • 기본 유형에는 수퍼 유형이 있으며 기본 유형의 수퍼 유형 유형의 변수를 가지려고합니다. 예를 들어, 가정 int의가있다 Hashable, 당신은받는 함수 선언 할 Hashable일반 객체를받을뿐만 아니라 수있는 매개 변수 int들.

    이러한 유형은 불법으로 만들어 “해결”할 수 있습니다. 하위 유형을 제거하고 인터페이스가 유형이 아니라 유형 제약 조건이라고 결정하십시오. 분명히 이것은 타입 시스템의 표현력을 감소 시키며, 그러한 타입 시스템은 더 이상 객체 지향이라고 할 수 없습니다. 이 전략을 사용하는 언어에 대해서는 Haskell을 참조하십시오. 원시 형에는 수퍼 타입이 없기 때문에 C ++는 반쯤 있습니다.

    대안은 기본 유형의 전체 또는 부분 권투입니다. 권투 유형은 사용자가 볼 필요가 없습니다. 기본적으로 각 기본 유형에 대한 내부 박스 유형과 박스 유형과 기본 유형 사이의 암시 적 변환을 정의합니다. 박스형에 의미가 다른 경우에는 어색 할 수 있습니다. Java는 두 가지 문제점을 나타냅니다. 박스형 유형에는 동일성 개념이 있지만 기본 요소는 가치 동등성 개념 만 있으며 박스형은 널 입력 가능하지만 기본 요소는 항상 유효합니다. 이러한 문제는 값 유형에 대한 ID 개념을 제공하지 않고 연산자 오버로드를 제공하며 기본적으로 모든 객체를 nullable로 설정하지 않음으로써 완전히 피할 수 있습니다.

  • 정적 입력 기능이 없습니다. 변수는 기본 유형 또는 객체를 포함한 모든 값을 보유 할 수 있습니다. 따라서 강력한 타이핑을 보장하려면 모든 기본 유형을 항상 상자에 넣어야합니다.

정적 타이핑이있는 언어는 가능한 경우 기본 유형을 사용하는 것이 좋으며 최후의 수단으로 박스 유형으로 만 대체됩니다. 많은 프로그램이 성능에 크게 영향을 미치지는 않지만 기본 유형의 크기와 구성이 매우 관련이있는 경우가 있습니다. 수십억 개의 데이터 포인트를 메모리에 맞출 필요가있는 대규모 크 런칭을 생각해보십시오. 에서 전환doublefloatC에서 실행 가능한 공간 최적화 전략 일 수 있지만 모든 숫자 유형이 항상 상자에 들어 있으면 영향을 미치지 않습니다 (따라서 디스패치 메커니즘 포인터를 위해 메모리의 절반 이상을 낭비하십시오). 박스형 프리미티브 유형을 로컬로 사용하는 경우 컴파일러 내장 함수를 사용하여 복싱을 제거하는 것이 매우 간단하지만, 언어의 전체 성능을 “충분한 고급 컴파일러”에 베팅하는 것은 근시안적입니다.


답변

내가 알고있는 대부분의 구현은 컴파일러가 대부분의 시간에 기본 표현으로 기본 유형을 효율적으로 사용할 수 있도록 그러한 클래스에 세 가지 제한을 부과합니다. 이러한 제한 사항은 다음과 같습니다.

  • 불변성
  • 최종성 (파생 불가능)
  • 정적 타이핑

컴파일러 기본 표현에서 객체에 프리미티브를 상자에 넣어야 하는 상황 은 Object참조가이를 가리키는 경우와 같이 비교적 드 rare니다 .

이것은 컴파일러에서 약간의 특수한 경우 처리를 추가하지만 신화적인 고급 고급 컴파일러에만 국한되지는 않습니다. 이 최적화는 주요 언어의 실제 프로덕션 컴파일러에 있습니다. 스칼라는 자신 만의 가치 클래스를 정의 할 수도 있습니다.


답변

스몰 토크에서 그것들 (int, float 등)은 모두 일류 객체입니다. 특별한 경우 SmallIntegers이 성문화과 효율성을 위해 가상 머신에 의해 다르게 취급, 따라서 SmallInteger 클래스는 서브 클래스를 인정하지된다는 것입니다 (실제 제한하지 않은.)이 어떤 특별한 배려를 필요로하지 않습니다 코드 생성이나 가비지 수집과 같은 자동 루틴으로 구별되기 때문에 프로그래머의 입장에서.

스몰 토크 컴파일러 (소스 코드-> VM 바이트 코드)와 VM nativizer (바이트 코드-> 기계 코드)는 생성 된 코드 (JIT)를 최적화하여 이러한 기본 객체에 대한 기본 조작의 패널티를 줄입니다.


답변

나는 OO 언어와 런타임을 설계하고 있었다 (이것은 완전히 다른 이유로 실패했다).

int true 클래스와 같은 것을 만드는 데 본질적으로 잘못된 것은 없습니다. 사실 이제는 3 개 (클래스, 배열 및 프리미티브)가 아닌 2 가지 종류의 힙 헤더 (클래스 및 배열) 만 있으므로 GC를보다 쉽게 ​​설계 할 수 있습니다. ].

프리미티브 유형에는 대부분 최종 / 밀봉 된 메소드가 있어야합니다 (+ 정말 중요합니다. ToString은 그다지 중요하지 않습니다). 이를 통해 컴파일러는 거의 모든 함수 호출을 정적으로 해결하고 인라인 할 수 있습니다. 대부분의 경우 이것은 복사 동작으로 중요하지 않습니다. (언어 수준에서 포함을 사용하도록 선택했습니다. [.NET도 마찬가지였습니다.]) 경우에 따라 메서드가 봉인되지 않으면 컴파일러에서 다음을 호출해야합니다. int + int를 구현하는 데 사용되는 함수


답변