게임 개발에 C # 속성을 사용하도록 Microsoft 권장 사항이 있습니까?


26

때로는 다음과 같은 속성이 필요하다는 것을 알았습니다.

public int[] Transitions { get; set; }

또는:

[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }

그러나 제 인상은 게임 개발에서 달리 할 이유가 없다면 모든 것이 가능한 한 빨리 이루어져야한다는 것입니다. 내가 읽은 것들에 대한 인상은 Unity 3D가 속성을 통해 액세스를 인라인 할 수 없기 때문에 속성을 사용하면 추가 함수 호출이 발생한다는 것입니다.

공개 필드를 사용하지 않는 Visual Studio의 제안을 지속적으로 무시할 권리가 있습니까? (우리는 int Foo { get; private set; }의도 된 사용법을 보여줄 수있는 이점이있는 곳에서 사용합니다.)

나는 조기 최적화가 나쁘다는 것을 알고 있지만, 내가 말했듯이 게임 개발에서 비슷한 노력으로 빠르게 할 수있을 때 무언가를 느리게 만들지 않습니다.


34
무언가가 느리다고 믿기 전에 항상 프로파일 러로 확인하십시오. 나는 무언가가 느리다는 말을 들었고 프로파일 러는 0.5 초를 잃기 위해서는 500,000,000 번 실행해야한다고 말했다. 프레임에서 수백 번 이상 실행하지 않기 때문에 관련이 없다고 결정했습니다.
Almo

5
여기에 약간의 추상화가 있으며 저수준 최적화를보다 광범위하게 적용하는 데 도움이됩니다. 예를 들어, 다양한 종류의 연결 목록을 사용하는 수많은 일반 C 프로젝트를 보았습니다.이 목록은 연속 배열 (예 :의 백업 ArrayList)에 비해 엄청나게 느립니다 . C에는 제네릭이 없기 때문에이 C 프로그래머는 ArrayLists크기 조정 알고리즘에 대해 동일한 것보다 링크 목록을 반복적으로 다시 구현하는 것이 더 쉽다는 것을 알았습니다 . 좋은 저수준 최적화가 나오면 캡슐화하십시오!
hegel5000

3
@Almo 10,000 개의 다른 장소에서 발생하는 작업에 동일한 논리를 적용합니까? 그것이 클래스 멤버 액세스이기 때문입니다. 논리적으로 최적화 클래스가 중요하지 않다고 결정하기 전에 성능 비용에 10K를 곱해야합니다. 0.5 초가 아니라 게임 성능이 망가질 때 가장 좋은 방법입니다. 당신의 요점은 여전히 ​​옳습니다. 그러나 올바른 행동이 당신이 그것을 만드는 것만 큼 명확하지 않다고 생각합니다.
피오 조

5
당신은 2 마리의 말이 있습니다. 그들을 경주하십시오.
마스트

@piojo하지 않습니다. 어떤 생각은 항상 이런 것들에 들어가야합니다. 필자의 경우 UI 코드의 루프입니다. 하나의 UI 인스턴스, 적은 루프, 적은 반복. 기록을 위해, 나는 당신의 의견을 찬성했습니다. :)
Almo

답변:


44

그러나 제 인상은 게임 개발에서 달리 할 이유가 없다면 모든 것이 가능한 한 빨리 이루어져야한다는 것입니다.

반드시 그런 것은 아닙니다. 응용 프로그램 소프트웨어와 마찬가지로 게임에는 성능에 중요한 코드와 그렇지 않은 코드가 있습니다.

코드가 프레임 당 수천 번 실행되면 이러한 낮은 수준의 최적화가 의미가있을 수 있습니다. (실제로 자주 호출해야하는 경우 먼저 확인해야 할 수도 있지만 가장 빠른 코드는 실행하지 않는 코드입니다)

코드가 몇 초마다 한 번 실행되면 가독성과 유지 관리 성이 성능보다 훨씬 중요합니다.

내가 읽은 것은 Unity 3D가 속성을 통해 액세스를 인라인하지는 않으므로 속성을 사용하면 추가 함수 호출이 발생한다는 것입니다.

스타일에 간단한 속성을 사용 int Foo { get; private set; }하면 컴파일러가 속성 래퍼를 최적화 할 수 있습니다. 그러나:

  • 실제로 구현이 있다면 더 이상 확신 할 수 없습니다. 구현이 복잡할수록 컴파일러가 인라인 할 가능성이 줄어 듭니다.
  • 속성은 복잡성을 숨길 수 있습니다. 양날의 칼입니다. 한편으로는 코드를 더 읽기 쉽고 변경 가능하게 만듭니다. 그러나 반면에 클래스의 속성에 액세스하는 다른 개발자는 단일 변수에만 액세스한다고 생각할 수 있으며 실제로 해당 속성 뒤에 숨겨진 코드의 양을 알지 못할 수 있습니다. 속성이 계산적으로 비싸기 시작하면 명시 적 GetFoo()/ SetFoo(value)메소드 로 리팩토링하는 경향 이 있습니다. 씬 뒤에 더 많은 일이 있음을 암시합니다. (또는 다른 사람들이 힌트를 얻도록 더 확신 해야하는 경우 : CalculateFoo()/ SwitchFoo(value))

최상위 레벨 오브젝트가 읽기 전용이 아닌 클래스 인 경우, 구조 유형의 비 읽기 전용 공용 필드 내의 필드에 액세스하는 것은 해당 필드가 구조 등의 구조 내에있는 경우에도 빠릅니다. 필드 또는 읽기 전용이 아닌 독립 변수입니다. 이러한 조건이 적용되는 경우 성능에 부정적인 영향을 미치지 않으면 서 구조가 상당히 클 수 있습니다. 이 패턴을 깨면 구조 크기에 비례하여 속도가 느려집니다.
슈퍼 캣

5
"그런 다음 명시적인 GetFoo () / SetFoo (value) 메소드로 리팩토링하는 경향이 있습니다."-좋은 점은 무거운 작업을 속성에서 메소드로 옮기는 것입니다.
마크 로저스

Unity 3D 컴파일러에서 언급 한 최적화를 확인하는 방법이 있습니까 (IL2CPP 및 Mono for Android 빌드)?
피오 조

9
@piojo 대부분의 성능 관련 질문과 마찬가지로 가장 간단한 대답은 "그냥 해"입니다. 코드가 준비되었습니다-필드와 속성을 갖는 것의 차이점을 테스트하십시오. 구현 세부 사항은 실제로 두 접근 방식 간의 중요한 차이를 측정 할 수있는 경우에만 조사 할 가치가 있습니다. 내 경험에 따르면 가능한 한 빨리 모든 것을 쓰면 사용 가능한 높은 수준의 최적화를 제한하고 낮은 수준의 부분을 경주하려고합니다 ( "나무의 숲을 볼 수 없음"). 예를 들어 Update완전히 건너 뛰는 방법을 찾으면 Update빨리 만드는 것보다 더 많은 시간을 절약 할 수 있습니다 .
루안

9

확실하지 않은 경우 모범 사례를 사용하십시오.

당신은 의심합니다.


그것은 쉬운 대답이었습니다. 물론 현실은 더 복잡합니다.

첫째, 게임 프로그래밍이 초 고성능 프로그래밍이며 모든 것이 가능한 한 빠르다는 신화가 있습니다. 게임 프로그래밍을 성능 인식 프로그래밍 으로 분류 합니다 . 개발자는 성능 제약을 알고 그 안에서 작업해야합니다.

둘째로, 모든 것을 가능한 한 빨리 만드는 것이 프로그램을 빨리 실행시키는 것이라는 신화가 있습니다. 실제로 병목 현상을 식별하고 최적화하는 것은 프로그램을 빠르게 만드는 것이며 코드를 쉽게 유지 관리하고 수정할 수있는 경우 훨씬 쉽고 빠르며 빠릅니다. 매우 최적화 된 코드는 유지 관리 및 수정이 어렵습니다. 그 다음 매우 최적화 된 프로그램을 작성하기 위해, 당신은 종종만큼 거의 가능한 필요하지와 같은 극단적 인 코드 최적화를 사용해야합니다.

셋째, 속성 및 접근 자의 이점은 변경하기에 강력하므로 코드를보다 쉽게 ​​유지 관리하고 수정할 수 있다는 것입니다. 이는 빠른 프로그램을 작성하는 데 필요한 것입니다. 누군가가 당신의 가치를 null로 설정하려고 할 때마다 예외를 던지 길 원하십니까? 세터를 사용하십시오. 값이 변경 될 때마다 알림을 보내시겠습니까? 세터를 사용하십시오. 새로운 가치를 기록하고 싶습니까? 세터를 사용하십시오. 게으른 초기화를 원하십니까? 게터를 사용하십시오.

넷째, 개인적으로 저는이 사례에서 Microsoft의 모범 사례를 따르지 않습니다. 사소하고 공개적인 게터와 세터 가있는 속성이 필요한 경우 필드를 사용하고 필요할 때 간단히 속성으로 변경하십시오. 그러나 그런 사소한 속성이 거의 필요하지 않습니다. 경계 조건을 확인하거나 세터를 비공개로 설정하려는 것이 훨씬 일반적입니다.


2

.net 런타임이 간단한 속성을 인라인하는 것은 매우 쉽고 종종 수행합니다. 그러나 이러한 인라이닝은 일반적으로 프로파일 러에 의해 비활성화되므로 오용하기 쉽고 공공 재산을 공공 분야로 변경하면 소프트웨어 속도가 빨라질 것이라고 생각합니다.

코드를 다시 컴파일 할 때 다시 컴파일되지 않는 다른 코드에 의해 호출되는 코드에서는 필드에서 속성으로 변경하는 것이 주요 변경 사항이므로 속성을 사용하는 것이 좋습니다. 그러나 대부분의 코드에서 get / set에 중단 점을 설정할 수있는 것 외에는 공용 필드와 비교하여 간단한 속성을 사용하는 이점이 없었습니다.

전문 C # 프로그래머로 10 년 동안 속성을 사용했던 주된 이유는 다른 프로그래머가 코딩 표준을 지키지 않는다고 말하지 못하게하는 것이 었습니다. 재산을 사용하지 않는 정당한 이유가 거의 없었기 때문에 이것은 좋은 상충 관계였습니다.


2

구조 및 나체 필드는 관리되지 않는 일부 API와의 상호 운용성을 용이하게합니다. 종종 저수준 API가 참조로 값에 액세스하려고합니다. 이는 불필요한 사본을 피하기 때문에 성능에 좋습니다. 속성을 사용하는 것은 그에 대한 장애물이며 종종 래퍼 라이브러리가 사용하기 쉽도록 그리고 때로는 보안을 위해 복사를하는 경우가 있습니다.

이 때문에 속성이 아닌 네이 키드 필드가있는 벡터 및 행렬 유형의 성능이 향상되는 경우가 많습니다.


모범 사례는 진공 상태에서 생성되지 않습니다. 일부화물 컬트에도 불구하고 일반적으로 모범 사례가 좋은 이유가 있습니다.

이 경우 몇 가지가 있습니다.

  • 속성을 사용하면 클라이언트 코드를 변경하지 않고 구현을 변경할 수 있습니다 (이진 수준에서는 소스 수준에서 클라이언트 코드를 변경하지 않고 필드를 속성으로 변경할 수 있지만 변경 후 다른 것으로 컴파일 됨) . 즉, 처음부터 속성을 사용하면 속성을 내부적으로 변경하기 위해 사용자를 참조하는 코드를 다시 컴파일 할 필요가 없습니다.

  • 유형 필드의 가능한 모든 값이 유효한 상태가 아닌 경우 코드를 수정하는 클라이언트 코드에 노출하지 않으려 고합니다. 따라서 일부 값 조합이 유효하지 않은 경우 필드를 개인용 (또는 내부 용)으로 유지하려고합니다.

나는 클라이언트 코드를 말하고있다. 그것은 당신을 부르는 코드를 의미합니다. 도서관을 만들지 않는 경우 (또는 공공 장소 대신 내부를 사용하여 도서관을 만드는 경우) 일반적으로 도서관과 좋은 훈련을받지 않아도됩니다. 이 상황에서 속성을 사용하는 가장 좋은 방법은 발로 자신을 쏘지 못하게하는 것입니다. 또한 필드가 다른 곳에서 수정되는지 여부에 대해 걱정할 필요없이 단일 파일에서 필드가 변경 될 수있는 모든 위치를 볼 수 있으면 코드에 대해 추론하기가 훨씬 쉽습니다. 실제로 속성은 잘못된 점을 파악할 때 중단 점을 두는 것도 좋습니다.


그렇습니다. int 업계에서 무엇을하고 있는지 볼 가치가 있습니다. 그러나 모범 사례를 따르려는 동기가 있습니까? 아니면 다른 누군가가 그렇게했기 때문에 코드를 추론하기가 어려워지는 모범 사례에 반대합니까? 아, 그건 그렇고, "다른 사람들이하는 것"은화물 컬트를 시작하는 방법입니다.


게임이 느리게 진행되고 있습니까? 병목 현상을 파악하고 해결하는 데 시간을 투자하는 것이 더 좋을 수 있습니다. 컴파일러는 많은 최적화를 수행 할 것이므로 안심할 수 있습니다. 그 때문에 잘못된 문제를보고있을 가능성이 있습니다.

반대로, 무엇을 시작해야할지 결정하는 경우 필드 대 속성과 같은 작은 세부 정보에 대해 걱정하는 대신 먼저 알고리즘 및 데이터 구조에 대해 걱정해야합니다.


마지막으로 모범 사례에 반하여 무언가를 얻습니까?

당신의 경우 (Unity 및 Mono for Android)의 경우 Unity는 참조로 값을 가져 옵니까? 그렇지 않으면 어쨌든 값을 복사하여 성능 향상을 얻지 못합니다.

그렇다면이 데이터를 참조하는 API로이 데이터를 전달하는 경우입니다. 필드를 공개하는 것이 합리적입니까, 아니면 유형이 API를 직접 호출 할 수있게 할 수 있습니까?

물론, 네이 키드 필드가있는 구조체를 사용하여 수행 할 수있는 최적화가있을 수 있습니다. 예를 들어, 포인터 Span<T> 등으로 액세스 할 수 있습니다 . 또한 메모리에 컴팩트하여 네트워크를 통해 전송하거나 영구 저장 장치에 넣을 수 있도록 쉽게 직렬화 할 수 있습니다 (예 : 사본).

이제 올바른 알고리즘과 구조를 골랐습니다. 병목 현상으로 판명되면 문제를 해결하는 가장 좋은 방법이 무엇인지 결정합니다. 언제 그리고 언제 발생하는지 걱정할 수 있습니다. 한편 당신은 좋은 또는 재미있는 게임을 가치있는 게임을 만드는 것과 같은 더 중요한 문제에 대해 걱정할 수 있습니다.


1

... 게임 개발에있어 다른 이유가없는 한 모든 것이 가능한 한 빨리 이루어져야한다는 인상이 있습니다.

:

나는 조기 최적화가 나쁘다는 것을 알고 있지만, 내가 말했듯이 게임 개발에서 비슷한 노력으로 빠르게 할 수있을 때 무언가를 느리게 만들지 않습니다.

조기 최적화는 나쁘지만 이는 절반에 불과하다는 것을 알고있을 것입니다 (아래 전체 인용문 참조). 게임 개발에서도 모든 것이 가능한 한 빠를 필요는 없습니다 .

먼저 작동 시키십시오. 그런 다음에 만 필요한 비트를 최적화하십시오. 즉, 첫 번째 초안이 지저분하거나 불필요하게 비효율적 일 수 있지만이 단계에서는 최적화가 우선 순위가 아닙니다.

" 실제 문제는 프로그래머가 잘못된 장소와 잘못된 시간에 효율성을 걱정하는 데 너무 많은 시간을 소비 했다는 것입니다 . 조기 최적화는 프로그래밍의 모든 악 (또는 적어도 대부분)의 근원입니다." 도널드 크 누스, 1974.


1

그런 기본적인 것들을 위해 C # 옵티마이 저는 어쨌든 그것을 얻을 것입니다. 당신이 그것에 대해 걱정한다면 (그리고 코드의 1 % 이상에 대해 걱정한다면, 잘못하고 있습니다 ) 속성이 만들어졌습니다. 이 속성 [MethodImpl(MethodImplOptions.AggressiveInlining)]은 메소드가 인라인 될 가능성을 크게 증가시킵니다 (속성 getter 및 setter는 메소드).


0

구조 유형 멤버를 필드가 아닌 속성으로 노출하면 특히 구조가 "기발한 객체"가 아닌 구조로 처리되는 경우 성능과 의미가 떨어질 수 있습니다.

.NET의 구조는 함께 덕트 탭된 필드의 모음이며 일부 목적을 위해 하나의 단위로 취급 될 수 있습니다. .NET Framework는 클래스 객체와 거의 같은 방식으로 구조를 사용할 수 있도록 설계되었으며, 편리한 경우가 있습니다.

구조를 둘러싼 많은 조언은 프로그래머가 테이프로 묶은 필드 모음이 아닌 객체로 구조를 사용하기를 원한다는 개념에 근거합니다. 반면에 테이프로 묶인 필드 무리로 동작하는 것이 유용한 경우가 많이 있습니다. 이것이 필요한 경우 .NET 조언은 프로그래머와 컴파일러 모두에게 불필요한 작업을 추가하여 단순히 구조를 직접 사용하는 것보다 성능과 의미가 떨어집니다.

구조를 사용할 때 명심해야 할 가장 큰 원칙은 다음과 같습니다.

  1. 값을 기준으로 구조체를 전달하거나 반환하거나 불필요하게 복사하지 마십시오.

  2. 원칙 # 1을 준수하면 작은 구조물뿐만 아니라 큰 구조물도 작동합니다.

경우 Alphablob26 개 대중 포함하는 구조 intAZ라는 이름의 필드 및 속성 게터 ab의의 합계를 반환 ab필드, 다음 주어진 Alphablob[] arr; List<Alphablob> list;코드는 int foo = arr[0].ab + list[0].ab;필드 읽을 필요가 ab의를 arr[0]하지만, 모든 26 개 필드 읽을해야합니다 list[0]심지어 것이지만를 둘을 제외한 모든 것을 무시하십시오. 와 같은 구조에서 효율적으로 작동 할 수있는 일반적인 목록과 같은 컬렉션을 원한다면 alphaBlob인덱싱 된 getter를 메소드로 바꿔야합니다.

delegate ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
actOnItem<TParam>(int index, ActByRef<T, TParam> proc, ref TParam param);

그런 다음 호출 proc(ref backingArray[index], ref param);합니다. 만약 하나의 대체 등의 컬렉션을 감안할 sum = myCollection[0].ab;

int result;
myCollection.actOnItem<int>(0, 
  ref (ref alphaBlob item, ref int dest)=>dest = item,
  ref result);

alphaBlob속성 getter에서 사용되지 않는 부분을 ​​복사 할 필요 가 없습니다. 전달 된 대리자는 ref매개 변수 이외의 다른 것에 액세스하지 않으므로 컴파일러는 정적 대리자를 전달할 수 있습니다.

불행히도 .NET Framework는이 작업을 훌륭하게 수행하는 데 필요한 종류의 대리자를 정의하지 않으며 구문은 엉망이됩니다. 다른 한편으로,이 접근 방식은 구조를 불필요하게 복사하는 것을 피할 수있게하여 스토리지에 액세스하는 가장 효율적인 방법 인 어레이 내에 저장된 대형 구조에 대해 적절한 조치를 수행하는 것이 실용적입니다.


안녕하세요, 잘못된 페이지에 답변을 게시하셨습니까?
piojo

@pojo : 아마도 대답의 목적은 필드 대신 속성을 사용하는 것이 나쁜 상황을 식별하고 액세스를 캡슐화하면서 필드의 성능 및 의미 론적 이점을 얻는 방법을 식별하는 것입니다.
supercat

@piojo : 편집 내용이 더 명확 해 집니까?
supercat

"두 개를 제외한 나머지는 무시해도 list [0]의 26 개 필드를 모두 읽어야합니다." 뭐? 확실합니까? MSIL을 확인 했습니까? C #은 거의 항상 참조로 클래스 유형을 전달합니다.
pjc50

@ pjc50 : 속성을 읽으면 값을 기준으로 결과를 얻습니다. 에 대한 인덱스 게터 두 경우 list와에 대한 속성 게터는 ab모두에 줄 지어 있으며, 지터가 만 회원을 인식하는 것이 가능할 수 ab필요,하지만 난 지터 버전이 실제로 그런 일을 모르고입니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.