[c#] 구조체 대 클래스

코드에서 100,000 개의 개체를 만들려고합니다. 2 개 또는 3 개의 속성 만있는 작은 것입니다. 나는 그것들을 일반 목록에 넣고 그것들이있을 때 그것들을 반복하고 value를 확인 a하고 아마도 value를 업데이트 할 것이다 b.

이러한 객체를 클래스 또는 구조체로 만드는 것이 더 빠르거나 더 낫습니까?

편집하다

ㅏ. 속성은 값 유형입니다 (내가 생각하는 문자열 제외?)

비. (아직 확실하지 않음) 유효성 검사 방법이있을 수 있습니다.

2 편집

나는 궁금해했다 : 힙과 스택의 개체가 가비지 수집기에 의해 동일하게 처리됩니까, 아니면 다르게 작동합니까?



답변

이러한 객체를 클래스 또는 구조체로 만드는 것이 빠릅니까?

당신은 그 질문에 대한 답을 결정할 수있는 유일한 사람입니다. 두 가지 방법을 시도 측정 의미, 사용자 중심, 관련 성능 메트릭을, 그리고 당신은 변화 관련 시나리오에서 실제 사용자에 의미있는 효과가 있는지 여부를 알 수 있습니다.

구조체는 힙 메모리를 덜 소비합니다 ( “스택에”있기 때문이 아니라 더 작고 더 쉽게 압축되기 때문입니다). 그러나 참조 사본보다 복사하는 데 시간이 더 걸립니다. 메모리 사용량이나 속도에 대한 성능 메트릭이 무엇인지 모르겠습니다. 여기에는 트레이드 오프가 있으며 그것이 무엇인지 아는 사람입니다.

이러한 객체를 클래스 또는 구조체로 만드는 것이 더 낫 습니까?

아마도 클래스 일 수도 있고 구조체 일 수도 있습니다. 경험상 : 객체가 다음과 같은 경우 :
1. Small
2. 논리적으로 변경 불가능한 값
3. 많은 것들이 있습니다.
그럼 나는 그것을 구조체로 만드는 것을 고려할 것입니다. 그렇지 않으면 참조 유형을 고수 할 것입니다.

구조체의 일부 필드를 변경해야하는 경우 일반적으로 필드가 올바르게 설정된 전체 새 구조체를 반환하는 생성자를 빌드하는 것이 좋습니다. 아마도 약간 느리지 만 (측정하십시오!) 논리적으로 추론하기가 훨씬 쉽습니다.

힙과 스택의 개체가 가비지 수집기에 의해 동일하게 처리됩니까?

아니요 , 스택의 개체가 컬렉션의 루트 이기 때문에 동일하지 않습니다 . 가비지 수집기는 “이것이 스택에 있는가?”라고 물을 필요가 없습니다. 그 질문에 대한 답은 항상 “예, 스택에 있습니다”입니다. (지금, 당신은 그것을 유지 하는 데 의존 할 수 없습니다 스택은 구현 세부 사항이기 때문에 객체를 활성 하는 . 지터는 일반적으로 스택 값이 될 항목을 등록한 다음 스택에 있지 않는 최적화를 도입 할 수 있습니다. 그래서 GC는 그것이 아직 살아 있다는 것을 알지 못합니다. 등록 된 객체는 그것을 보유하고있는 레지스터가 다시 읽히지 않는 즉시 그 자손들을 공격적으로 수집 할 수 있습니다.)

그러나 가비지 수집기 살아있는 것으로 알려진 모든 객체를 살아있는 것으로 처리하는 것과 같은 방식으로 스택의 객체를 살아있는 것으로 처리해야합니다. 스택의 객체는 유지되어야하는 힙 할당 객체를 참조 할 수 있으므로 GC는 라이브 세트를 결정하기 위해 스택 객체를 살아있는 힙 할당 객체처럼 처리해야합니다. 그러나 분명히 힙을 압축 할 목적으로 “라이브 객체”로 취급 되지 않습니다 . 왜냐하면 처음에는 힙에 있지 않기 때문입니다.

분명합니까?


답변

때로는 structnew () 생성자를 호출 할 필요가없고 필드를 직접 할당하여 평소보다 훨씬 빠르게 할 수 있습니다.

예:

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i].id = i;
    list[i].isValid = true;
}

보다 약 2 ~ 3 배 빠릅니다.

Value[] list = new Value[N];
for (int i = 0; i < N; i++)
{
    list[i] = new Value(i, true);
}

두 개의 필드 ( 및 ) Value가있는 a는 어디에 있습니까 ?structidisValid

struct Value
{
    int id;
    bool isValid;

    public Value(int i, bool isValid)
    {
        this.i = i;
        this.isValid = isValid;
    }
}

반면에 항목을 이동하거나 값 유형을 선택해야 복사가 속도를 늦출 수 있습니다. 정확한 답을 얻으려면 코드를 프로파일 링하고 테스트해야한다고 생각합니다.


답변

구조체는 클래스와 비슷해 보일 수 있지만 알아야 할 중요한 차이점이 있습니다. 우선, 클래스는 참조 유형이고 구조체는 값 유형입니다. 구조체를 사용하면 기본 제공 형식처럼 동작하는 개체를 만들고 그 이점도 누릴 수 있습니다.

클래스에서 New 연산자를 호출하면 힙에 할당됩니다. 그러나 구조체를 인스턴스화하면 스택에 생성됩니다. 이렇게하면 성능이 향상됩니다. 또한 클래스 에서처럼 구조체의 인스턴스에 대한 참조를 다루지 않을 것입니다. 구조체 인스턴스로 직접 작업하게됩니다. 이 때문에 구조체를 메서드에 전달할 때 참조 대신 값으로 전달됩니다.

여기 더 :

http://msdn.microsoft.com/en-us/library/aa288471(VS.71).aspx


답변

구조체의 배열은 연속적인 메모리 블록의 힙에 표시되는 반면, 개체의 배열은 힙의 다른 위치에있는 실제 개체 자체와 함께 연속적인 참조 블록으로 표시되므로 개체와 해당 배열 참조 모두에 메모리가 필요합니다. .

이 경우, 그것들을 배치 할 때 List<>(그리고 a List<>는 배열로 백업 됨) 구조체를 사용하는 것이 더 효율적이고 메모리면에서 좋습니다.

(하지만 큰 배열은 수명이 길면 프로세스의 메모리 관리에 악영향을 미칠 수있는 큰 개체 힙에서 길을 찾을 수 있습니다. 또한 메모리가 유일한 고려 사항은 아닙니다.)


답변

값 의미론이 있으면 구조체를 사용해야합니다. 참조 시맨틱이있는 경우 클래스를 사용해야합니다. 값 의미가있는 경우에도 클래스를 만드는 데 주로 기울이는 예외가 있지만 거기서부터 시작합니다.

두 번째 편집의 경우 GC는 힙만 처리하지만 스택 공간보다 힙 공간이 훨씬 많으므로 스택에 물건을 넣는 것이 항상이기는 ​​것은 아닙니다. 게다가 구조체 유형 목록과 클래스 유형 목록이 어느 쪽이든 힙에 있으므로이 경우에는 관련이 없습니다.

편집하다:

나는 이라는 용어 를 해로운 것으로 생각하기 시작했습니다 . 결국 클래스를 변경 가능하게 만드는 것은 적극적으로 필요하지 않은 경우 나쁜 생각이며 변경 가능한 구조체를 사용하는 것을 배제하지 않습니다. 그래도 거의 항상 나쁜 생각이 될 정도로 좋지 않은 생각이지만 대부분 값 의미론과 일치하지 않으므로 주어진 경우에 구조체를 사용하는 것은 의미가 없습니다.

전용 중첩 구조체에는 합리적인 예외가있을 수 있으며,이 경우 해당 구조체의 모든 사용은 매우 제한된 범위로 제한됩니다. 하지만 여기에는 적용되지 않습니다.

정말로, 나는 “그것은 나쁜 stuct이기 때문에 변형된다”는 것이 힙과 스택에 대해 진행하는 것보다 훨씬 낫지 않다고 생각한다 (자주 잘못 표현 되더라도 적어도 성능에 약간의 영향을 미친다). “변이하기 때문에 가치 의미론을 갖는 것으로 간주하는 것은 이해가되지 않을 가능성 으므로 잘못된 구조체입니다.”는 약간만 다르지만 중요하게 생각합니다.


답변

가장 좋은 해결책은 측정하고 다시 측정 한 다음 더 측정하는 것입니다. “구조 사용”또는 “클래스 사용”과 같은 간단하고 쉬운 대답을 어렵게 만드는 작업에 대한 세부 정보가있을 수 있습니다.


답변

구조체는 본질적으로 필드 집합에 불과합니다. .NET에서는 구조가 객체 인 것처럼 “가장”하는 것이 가능하며, 각 구조 유형에 대해 .NET은 힙 객체가되는 동일한 필드 및 메서드를 사용하여 객체처럼 동작하는 힙 객체 유형을 암시 적으로 정의합니다. . 이러한 힙 객체 ( “박스형”구조)에 대한 참조를 보유하는 변수는 참조 의미론을 표시하지만 구조를 직접 보유하는 변수는 단순히 변수의 집합입니다.

struct-versus-class 혼란의 대부분은 구조가 매우 다른 두 가지 사용 사례를 가지고 있다는 사실에서 비롯된다고 생각합니다. 이것은 매우 다른 디자인 지침을 가져야하지만 MS 지침은 이들을 구분하지 않습니다. 때때로 객체처럼 행동하는 무언가가 필요합니다. 이 경우 MS 지침은 상당히 합리적이지만 “16 바이트 제한”은 아마도 24-32와 비슷할 것입니다. 그러나 때때로 필요한 것은 변수의 집계입니다. 이러한 목적으로 사용되는 구조체는 단순히 여러 개의 공개 필드로 구성되어야하며Equals 오버라이드 .ToString 재정의 및IEquatable(itsType).Equals이행. 필드의 집계로 사용되는 구조는 개체가 아니므로 가장 하여서는 안됩니다. 구조적 관점에서 필드의 의미는 “이 필드에 마지막으로 기록 된 것”에 불과합니다. 추가 의미는 클라이언트 코드에 의해 결정되어야합니다.

예를 들어, 변수 집계 구조체에 Minimum및 멤버가 Maximum있는 경우 구조체 자체는 Minimum <= Maximum. 이 별도의 통과있는 것처럼 행동한다 파라미터 바와 같은 구조 수신 코드 MinimumMaximum값. 요구 사항 Minimum보다 클 수 없습니다 Maximum것을 요구처럼 간주되어야 Minimum매개 변수가 별도로 전달보다 클 수 없습니다 Maximum하나.

때때로 고려해야 할 유용한 패턴은 ExposedHolder<T>다음과 같은 클래스를 정의하는 것입니다.

class ExposedHolder<T>
{
  public T Value;
  ExposedHolder() { }
  ExposedHolder(T val) { Value = T; }
}

가있는 경우 List<ExposedHolder<someStruct>>, where someStructis a variable-aggregating struct, 하나는 같은 일을 할 수 myList[3].Value.someField += 7;있지만 myList[3].Value다른 코드에 제공하면 Value변경 수단을 제공하지 않고 내용을 제공합니다. 반대로 List<someStruct>를 사용했다면 var temp=myList[3]; temp.someField += 7; myList[3] = temp;. 변경 가능한 클래스 유형을 사용하는 경우의 내용 myList[3]을 외부 코드에 노출하려면 모든 필드를 다른 개체에 복사해야합니다. 변경 불가능한 클래스 유형 또는 “객체 스타일”구조체 myList[3]를 사용 someField하는 경우, 다른 점을 제외하고 는 유사한 새 인스턴스를 생성 한 다음 해당 새 인스턴스를 목록에 저장해야합니다.

한 가지 추가 참고 사항 : 유사한 항목을 많이 저장하는 경우 중첩 된 구조 배열에 저장하는 것이 좋을 수 있습니다. 각 배열의 크기를 1K에서 64K 정도 사이로 유지하는 것이 좋습니다. 구조의 배열은 특별합니다. 인덱싱은 구조에 대한 직접 참조를 생성하므로 “a [12] .x = 5;”라고 말할 수 있습니다. 배열과 유사한 개체를 정의 할 수 있지만 C #에서는 이러한 구문을 배열과 공유 할 수 없습니다.