왜 우리가 왜 커스텀 구조체를 재정의 GetHashCode
하고 왜 기본 구현이 "해시 테이블의 키로 사용하기에 적합하지 않은지" 를 설명하는 답변을 찾을 수 Equals
없으므로이 블로그에 대한 링크를 남겨 두겠습니다. post 는 문제가 발생한 실제 사례를 설명합니다.
전체 게시물을 읽는 것이 좋지만 여기에 요약이 있습니다 (강조 및 설명 추가).
구조체의 기본 해시가 느리고 좋지 않은 이유 :
CLR이 설계된 방식으로 정의 된 멤버 System.ValueType
또는 System.Enum
유형 에 대한 모든 호출은 [...] 권투 할당을 유발할 수 있습니다 .
해시 함수의 구현자는 딜레마에 직면합니다. 해시 함수를 적절하게 분배하거나 빠르게 만듭니다. 경우에 따라 두 가지를 모두 달성하는 것이 가능하지만 에서 일반적으로 수행 하기는 어렵 습니다 ValueType.GetHashCode
.
구조체의 표준 해시 함수는 모든 필드의 해시 코드를 "결합"합니다. 그러나 ValueType
메소드 에서 필드의 해시 코드를 얻는 유일한 방법은 리플렉션 을 사용하는 것 입니다. 따라서 CLR 작성자는 배포판을 통해 속도를 교환하기로 결정했으며 기본 GetHashCode
버전 은 null이 아닌 첫 번째 필드의 해시 코드를 반환 하고 유형 ID로 "조정"합니다. [...] 그렇지 않은 경우 합당한 동작입니다 . 예를 들어, 운이 좋지 않고 구조체의 첫 번째 필드가 대부분의 인스턴스에 대해 동일한 값을 갖는 경우 해시 함수는 항상 동일한 결과를 제공합니다 . 그리고 상상할 수 있듯이 이러한 인스턴스가 해시 세트 또는 해시 테이블에 저장된 경우 성능이 크게 저하됩니다.
[...] 리플렉션 기반 구현이 느립니다 . 아주 느린.
[...] 모두 ValueType.Equals
와 ValueType.GetHashCode
특별한 최적화를 가지고있다. 유형에 "포인터"가없고 적절하게 압축 된 [...] 인 경우보다 최적의 버전이 사용됩니다. GetHashCode
인스턴스를 반복하고 4 바이트의 XOR 블록을 Equals
사용 하고 메소드 는를 사용하여 두 인스턴스를 비교합니다 memcmp
. [...] 그러나 최적화는 매우 까다 롭습니다. 첫째, 최적화가 언제 활성화되는지 알기가 어렵습니다 [...] 둘째, 메모리 비교가 반드시 올바른 결과를 제공하지는 않습니다 . 다음은 간단한 예입니다. [...] -0.0
이며 +0.0
같지만 이진 표현이 다릅니다.
게시물에 설명 된 실제 문제 :
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
우리는 기본 평등 구현을 가진 커스텀 구조체를 포함하는 튜플을 사용했습니다. 그리고 불행하게도, 구조체는 거의 언제나 [빈 문자열]에 동일 옵션 첫 번째 필드를했다 . 세트의 요소 수가 크게 증가하여 실제 성능 문제가 발생하여 수만 개의 항목으로 콜렉션을 초기화하는 데 몇 분이 소요될 때까지 성능은 정상이었습니다.
따라서 structs 의 경우에는 "어떤 경우에 내 자신의 패키지를 작성하고 어떤 경우에는 기본 구현에 안전하게 의존 할 수 있는지"라는 질문에 대답하려면 structs 를 재정의 Equals
하고 GetHashCode
사용자 정의 struct가 a로 사용될 때마다 해시 테이블의 키 또는 Dictionary
.
또한 IEquatable<T>
이 경우 권투를 피하기 위해 구현 하는 것이 좋습니다 .
다른 답변에서 언급했듯이 클래스를 작성하는 경우 참조 동등성을 사용하는 기본 해시가 일반적으로 좋으므로이 경우 재정의가 필요 하지 않은 한이 경우 귀찮게하지 않습니다 Equals
( GetHashCode
따라서 적절 하게 재정의해야 함 ).