구조체 대 클래스


93

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

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

편집하다

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

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

2 편집

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


2
그들은 공공 분야 만 가질 것인가, 아니면 방법도 가질 것인가? 유형은 정수와 같은 기본 유형입니까? 그것들은 배열에 포함됩니까, 아니면 List <T>와 같은 것입니까?
JeffFerguson

14
변경 가능한 구조체 목록? 벨로시 랩터를 조심하세요.
Anthony Pegram

1
@Anthony : 나는 벨로시 랩터의 농담을 누락 두려워는 : -s
미셸

5
벨로시 랩터 농담은 XKCD에서 나온 것입니다. 그러나 '값 유형은 스택에 할당됩니다'오해 / 구현 세부 정보 (해당되는 경우 삭제)를 던지고있을 때주의해야 할 것은 Eric Lippert입니다 ...
Greg Beech

답변:


137

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

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

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

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

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

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

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

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

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

분명합니까?


Eric, 컴파일러 또는 지터가 readonly최적화를 허용하기 위해 불변성을 사용하는지 (아마도를 사용하여 강제하는 경우 ) 알고 있습니까 ? 나는 그것이 가변성에 대한 선택에 영향을주지 않도록하겠습니다 (저는 이론상 효율성에 대한 세부 사항에 대한 열광 자이지만 실제로 효율성을 향한 첫 번째 움직임은 항상 가능한 한 간단하게 정확성을 보장하기 위해 노력하는 것입니다. 체크와 엣지 케이스에서 CPU주기와 두뇌주기를 낭비하고 적절하게 변경 가능하거나 불변하면 도움이됩니다.)하지만 불변성이 더 느릴 수 있다는 말에 대한 무자비한 반응에 대응할 수 있습니다.
Jon Hanna

@Jon : C # 컴파일러는 const 데이터를 최적화 하지만 읽기 전용 데이터는 최적화 하지 않습니다 . jit 컴파일러가 읽기 전용 필드에서 캐싱 최적화를 수행하는지 여부를 모르겠습니다.
Eric Lippert

불변성에 대한 지식은 일부 최적화를 허용하지만 그 시점에서 이론적 지식의 한계에 도달했지만 그 한계는 제가 늘고 싶은 한계입니다. 그 동안 "두 가지 방법 모두 더 빠를 수 있습니다. 이제이 경우에 어떤 것이 적용되는지 테스트하고 찾아보십시오"는 다음과 같이 말할 수 있습니다.
Jon Hanna

다이빙을 시작하려면 simple-talk.com/dotnet/.net-framework/… 및 자신의 기사 (@Eric) 를 읽는 것이 좋습니다 . blogs.msdn.com/b/ericlippert/archive/2010/09/30/… 세부 사항으로. 주변에 좋은 기사가 많이 있습니다. BTW, 100,000 개의 작은 메모리 내 개체를 처리 할 때의 차이는 클래스에 대한 약간의 메모리 오버 헤드 (~ 2.3MB)를 통해 거의 눈에 띄지 않습니다. 간단한 테스트로 쉽게 확인할 수 있습니다.
Nick Martyshchenko

네, 분명합니다. 포괄적 인 답변을 해주셔서 대단히 감사합니다 (광범위한 것이 더 낫습니까? Google 번역은 2 개의 번역을 제공했습니다. 짧은 답변을 쓰지 않고 시간을내어 모든 세부 사항을 작성하는 데 시간이 걸렸다 고 말하려합니다) 답변.
Michel

23

때로는 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;
    }
}

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


물론 네이티브 경계를 넘어 값을 마샬링하면 상황이 훨씬 빨라집니다.
leppie

내가 아닌 다른 이름을 사용하는 것이 좋습니다 것입니다 list표시된 코드가 작동하지 않습니다 주어진을 List<Value>.
supercat

7

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

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

여기 더 :

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


4
나는 그것이 MSDN에서 그것을 말하는 것을 알고 있지만 MSDN은 전체 이야기를 말하지 않습니다. 스택 대 힙은 구현 세부 사항이며 구조체가 항상 스택에있는 것은 아닙니다 . 이에 대한 최근 블로그 하나만 보려면 blogs.msdn.com/b/ericlippert/archive/2010/09/30/…을
Anthony Pegram

"... 값으로 전달됩니다 ..."참조와 구조체 모두 값으로 전달됩니다 ( 'ref'를 사용하지 않는 한). 값 또는 참조가 서로 다른 전달되는지 여부입니다. 즉, 값별로 전달됩니다. , 클래스 객체는 값으로 참조로 전달되고 참조 표시 매개 변수는 참조로 참조로 전달됩니다.
Paul Ruane

10
이 기사는 몇 가지 핵심 사항에 대해 오해의 소지가 있으며 MSDN 팀에 수정 또는 삭제를 요청했습니다.
Eric Lippert

2
@supercat : 첫 번째 요점을 해결하려면 더 큰 요점은 값 또는 값에 대한 참조가 저장된 관리 코드 에서 크게 관련이 없다는 것 입니다. 우리는 대부분의 경우 개발자가 런타임이 자신을 대신하여 현명한 스토리지 결정을 내릴 수 있도록 허용하는 메모리 모델을 만들기 위해 열심히 노력했습니다. 이러한 구분을 이해하지 못하면 C 에서처럼 충돌하는 결과가 발생할 때 매우 중요합니다. C #에서는 그리 많지 않습니다.
Eric Lippert

1
@supercat : 두 번째 요점을 해결하기 위해 변경 가능한 구조체는 대부분 악합니다. 예를 들어, void M () {S s = new S (); s.Blah (); N (s); }. 리팩터링 : void DoBlah (S s) {s.Blah (); } void M (S s = new S (); DoBlah (s); N (s);}. S가 가변 구조체이기 때문에 방금 버그가 발생했습니다. 버그 를 즉시 보셨나요 ? 아니면 S가 변경 가능한 구조체 가 버그를 숨기 나요?
Eric Lippert

6

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

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

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


ref이를 처리 하기 위해 키워드 를 사용할 수 있습니다 .
leppie

"하지만 대형 어레이는 수명이 길면 프로세스의 메모리 관리에 악영향을 미칠 수있는 대형 개체 힙에서 길을 찾을 수 있습니다." -왜 그렇게 생각 하시는지 모르겠어요? LOH에 할당 되어도 (아마도) 수명이 짧은 개체가 아니고 Gen 2 컬렉션을 기다리지 않고 메모리를 빠르게 회수하려는 경우가 아니면 메모리 관리에 악영향을 미치지 않습니다.
Jon Artus

@Jon Artus : LOH는 압축되지 않습니다. 수명이 긴 개체는 LOH를 이전과 이후의 여유 메모리 영역으로 나눕니다. 할당에는 연속 메모리가 필요하며 이러한 영역이 할당에 충분하지 않으면 LOH에 더 많은 메모리가 할당됩니다 (즉, LOH 조각화가 발생 함).
Paul Ruane 2010

4

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

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

편집하다:

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

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

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


3

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


측정 부분에 동의하지만, 제 생각에는 이것은 간단하고 명확한 예라고 생각합니다. 나는 그것에 대해 몇 가지 일반적인 것을 말할 수 있다고 생각했습니다. 결과적으로 어떤 사람들은 그렇게했습니다.
Michel

3

구조체는 본질적으로 필드 집합에 불과합니다. .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 #에서는 이러한 구문을 배열과 공유 할 수 없습니다.


1

수업을 사용하십시오.

일반적으로. 값 b를 생성 할 때 업데이트하지 않는 이유는 무엇입니까?


1

C ++ 관점에서 나는 클래스에 비해 구조체 속성을 수정하는 것이 더 느리다는 데 동의합니다. 그러나 나는 구조체가 힙 대신 스택에 할당되어 있기 때문에 읽는 것이 더 빠를 것이라고 생각합니다. 힙에서 데이터를 읽으려면 스택보다 더 많은 검사가 필요합니다.


1

글쎄, 당신이 struct로 가면 문자열을 제거하고 고정 크기의 char 또는 byte 버퍼를 사용하십시오.

그게 바로 성능입니다.

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