[performance] .Net 4.0의 새로운 튜플 유형이 값 유형 (구조체)이 아닌 참조 유형 (클래스) 인 이유

누구든지 대답을 알고 있거나 이에 대한 의견이 있습니까?

튜플은 일반적으로 그다지 크지 않기 때문에 클래스보다 구조체를 사용하는 것이 더 합리적이라고 가정합니다. 뭐라고?



답변

Microsoft는 단순성을 위해 모든 튜플 형식 참조 형식을 만들었습니다.

개인적으로 이것이 실수라고 생각합니다. 필드가 4 개 이상인 튜플은 매우 드문 경우이며 어쨌든 더 형식적인 대안 (예 : F #의 레코드 유형)으로 대체해야하므로 작은 튜플 만 실제적으로 유용합니다. 내 벤치 마크에 따르면 최대 512 바이트의 unboxed 튜플이 boxed tuple보다 더 빠를 수 있습니다.

메모리 효율성이 한 가지 관심사이지만 가장 큰 문제는 .NET 가비지 수집기의 오버 헤드라고 생각합니다. .NET에서 할당 및 수집은 가비지 수집기가 매우 최적화되지 않았기 때문에 (예 : JVM에 비해) 매우 비쌉니다 . 또한 기본 .NET GC (워크 스테이션)는 아직 병렬화되지 않았습니다. 결과적으로 튜플을 사용하는 병렬 프로그램은 모든 코어가 공유 가비지 수집기를 위해 경쟁하면서 중단되어 확장 성을 파괴합니다. 이것은 주된 관심사 일뿐만 아니라 AFAIK가이 문제를 조사 할 때 Microsoft에 의해 완전히 무시되었습니다.

또 다른 문제는 가상 디스패치입니다. 참조 유형은 하위 유형을 지원하므로 해당 멤버는 일반적으로 가상 디스패치를 ​​통해 호출됩니다. 반대로 값 유형은 하위 유형을 지원할 수 없으므로 멤버 호출이 완전히 모호하지 않고 항상 직접 함수 호출로 수행 될 수 있습니다. CPU가 프로그램 카운터가 끝나는 위치를 예측할 수 없기 때문에 가상 디스패치는 최신 하드웨어에서 엄청나게 비쌉니다. JVM은 가상 디스패치를 ​​최적화하기 위해 많은 노력을 기울이지 만 .NET은 그렇지 않습니다. 그러나 .NET은 값 형식의 형태로 가상 디스패치로부터의 이스케이프를 제공합니다. 따라서 튜플을 값 유형으로 표현하면 여기서도 성능이 크게 향상 될 수 있습니다. 예를 들어,GetHashCode 2- 튜플에서 백만 번은 0.17 초가 걸리지 만 동등한 구조체에서 호출하는 데는 0.008 초 밖에 걸리지 않습니다. 즉, 값 유형이 참조 유형보다 20 배 빠릅니다.

튜플에서 이러한 성능 문제가 일반적으로 발생하는 실제 상황은 튜플을 사전의 키로 사용하는 것입니다. 실제로 Stack Overflow 질문 F # 의 링크를 따라이 스레드를 우연히 발견했습니다. 내 알고리즘은 Python보다 느리게 실행됩니다! 저자의 F # 프로그램은 박스형 튜플을 사용했기 때문에 Python보다 느리다는 것이 밝혀졌습니다. 손으로 쓴 struct형식을 사용하여 수동으로 unboxing 하면 F # 프로그램이 Python보다 몇 배 더 빠르고 빠릅니다. 튜플이 시작될 참조 유형이 아니라 값 유형으로 표현된다면 이러한 문제는 발생하지 않았을 것입니다.


답변

그 이유는 작은 튜플 만이 작은 메모리 풋 프린트를 가지기 때문에 값 유형으로 의미가 있기 때문입니다. 더 큰 튜플 (즉, 속성이 더 많은 튜플)은 16 바이트보다 크므로 실제로 성능이 저하됩니다.

일부 튜플은 값 유형이고 다른 튜플은 참조 유형이되어 개발자가 Microsoft 직원이 모든 참조 유형을 만드는 것이 더 간단하다고 생각하는 것을 알도록 강요하는 대신.

아, 의심이 확인되었습니다! 튜플 작성을 참조하십시오 .

첫 번째 주요 결정은 튜플을 참조 또는 값 유형으로 처리할지 여부였습니다. 튜플의 값을 변경하고 싶을 때마다 변경이 불가능하므로 새 값을 만들어야합니다. 참조 유형 인 경우 타이트 루프에서 튜플의 요소를 변경하면 많은 가비지가 생성 될 수 있습니다. F # 튜플은 참조 형식 이었지만 2 개 또는 3 개 요소 튜플이 대신 값 형식이면 성능 향상을 실현할 수 있다는 느낌이 들었습니다. 내부 튜플을 만든 일부 팀은 시나리오가 관리되는 개체를 많이 만드는 데 매우 민감했기 때문에 참조 형식 대신 값을 사용했습니다. 그들은 값 유형을 사용하면 더 나은 성능을 제공한다는 것을 발견했습니다. 튜플 사양의 첫 번째 초안에서 우리는 2, 3, 4 요소 튜플을 값 유형으로 유지하고 나머지는 참조 유형입니다. 그러나 다른 언어의 대표자를 포함하는 디자인 회의에서 두 유형 간의 의미가 약간 다르기 때문에이 “분할”디자인이 혼란 스러울 것이라고 결정했습니다. 행동과 디자인의 일관성이 잠재적 인 성능 향상보다 우선 순위가 높은 것으로 결정되었습니다. 이 입력을 기반으로 모든 튜플이 참조 유형이되도록 설계를 변경했지만 일부 크기의 튜플에 대해 값 유형을 사용할 때 속도가 향상되었는지 확인하기 위해 F # 팀에 성능 조사를 요청했습니다. 컴파일러가 F #으로 작성 되었기 때문에이를 테스트하는 좋은 방법이있었습니다. 다양한 시나리오에서 튜플을 사용한 대규모 프로그램의 좋은 예입니다. 결국 F # 팀은 일부 튜플이 참조 형식이 아닌 값 형식 인 경우 성능이 향상되지 않는다는 것을 발견했습니다. 이로 인해 튜플에 참조 유형을 사용하기로 한 결정에 대해 기분이 좋아졌습니다.


답변

.NET System.Tuple <…> 유형이 구조체로 정의 된 경우 확장 가능하지 않습니다. 예를 들어, long 정수의 삼항 튜플은 현재 다음과 같이 확장됩니다.

type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4

삼항 튜플이 구조체로 정의 된 경우 결과는 다음과 같습니다 (내가 구현 한 테스트 예제를 기반으로 함).

sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104

튜플은 F #에서 기본 제공 구문을 지원하고이 언어에서 매우 자주 사용되기 때문에 “struct”튜플은 F # 프로그래머가 인식하지 못한 채 비효율적 인 프로그램을 작성할 위험이 있습니다. 아주 쉽게 일어날 것입니다.

let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3

제 생각에 “struct”튜플은 일상적인 프로그래밍에서 상당한 비 효율성을 만들 가능성이 높습니다. 반면, 현재 존재하는 “클래스”튜플은 @Jon이 언급 한 것처럼 특정 비 효율성을 유발합니다. 그러나 나는 “발생 확률”과 “잠재적 손상”의 곱이 현재 클래스보다 구조체에서 훨씬 더 높을 것이라고 생각합니다. 따라서 현재 구현은 덜 악합니다.

이상적으로는 “class”튜플과 “struct”튜플이 모두 존재하며 둘 다 F #에서 구문을 지원합니다!

편집 (2017-10-07)

이제 구조체 튜플은 다음과 같이 완전히 지원됩니다.


답변

2- 튜플의 경우 이전 버전의 공통 유형 시스템에서 항상 KeyValuePair <TKey, TValue>를 사용할 수 있습니다. 가치 유형입니다.

Matt Ellis 기사에 대한 사소한 설명은 참조 유형과 값 유형 간의 사용 의미의 차이가 불변성이 유효 할 때 “미미”하다는 것입니다 (물론 여기에 해당됨). 그럼에도 불구하고 튜플이 일부 임계 값에서 참조 유형으로 교차하는 혼란을 야기하지 않는 것이 BCL 디자인에서 가장 좋았을 것이라고 생각합니다.


답변

모르겠지만 F # 튜플을 사용한 적이 있다면 언어의 일부입니다. .dll을 만들고 튜플 유형을 반환 한 경우이를 넣을 유형을 갖는 것이 좋습니다. 이제 F #이 언어 (.Net 4)의 일부인 것으로 의심됩니다. CLR에 일부 공통 구조를 수용하기 위해 일부 수정이 이루어졌습니다. F #에서

에서 http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Records

let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;

val scalarMultiply : float -> float * float * float -> float * float * float

scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)


답변