C # 8 : 변수 구조의 읽기 전용 인스턴스 멤버

이전 기사에서 우리는 C # 읽기 전용 구조 (읽기 전용 구조) 1 및 그에 가까운 상대 in매개 변수 2를 소개했습니다 .
오늘은 C # 8에서 도입 된 기능에 대해 설명하겠습니다. 변경 가능한 구조의 읽기 전용 인스턴스 멤버 (구조가 변경 가능한 경우 구조의 상태를 변경하지 않는 인스턴스 멤버는로 선언 됨 readonly).

읽기 전용 인스턴스 멤버를 도입하는 이유

간단히 말해 성능향상시키는 것입니다 .
우리는 이미 읽기 전용 구조 ( readonly struct)를 알고 있으며 in매개 변수는 코드 실행 성능을 향상시키기 위해 축소하여 복사본을 만들 수 있습니다. 읽기 전용 구조 유형을 만들 때 컴파일러는 모든 멤버가 읽기 전용이되도록 강제합니다 (즉, 인스턴스 멤버가 상태를 수정하지 않음). 그러나 예를 들어 일부 시나리오에서는 공개적으로 액세스 가능한 필드 또는 변경 가능한 멤버와 변경 불가능한 멤버가 모두있는 기존 API가 있습니다. 이 경우 유형을로 표시 할 수 없습니다 readonly(모든 인스턴스 멤버와 관련되기 때문).

일반적으로 이것은 큰 영향을 미치지 않지만 in예외 에 대한 사용 사례 매개 변수입니다. 읽기 전용이 아닌 구조 in매개 변수의 경우 컴파일러는이 호출이 내부 상태를 수정하지 않는다는 것을 보장 할 수 없기 때문에 각 인스턴스 멤버를 호출하는 매개 변수의 방어 복사본을 만듭니다. 이로 인해 많은 수의 복사본이 생성 될 수 있으며 전체 성능은 값으로 직접 구조를 전달할 때보 다 나빠집니다 (값으로 전달하면 매개 변수를 전달할 때 한 번만 복사본이 생성되기 때문입니다).

이해하기 위해 예제를보고 이러한 일반적인 구조를 정의한 다음 in인수 로 사용합니다 .

public struct Rect
{
    
    
    public float w;
    public float h;

    public float Area
    {
    
    
        get
        {
    
    
            return w * h;
        }
    }
}
public class SampleClass
{
    
    
    public float M(in Rect value)
    {
    
    
        return value.Area + value.Area;
    }
}

컴파일 후 로직을 실행 하는 클래스 SampleClass메소드 M코드는 실제로 다음과 같습니다.

public float M([In] [IsReadOnly] ref Rect value)
{
    
    
    Rect rect = value;  //防御性副本
    float area = rect.Area;
    rect = value;       //防御性副本
    return area + rect.Area;
}

변수 구조의 읽기 전용 인스턴스 멤버

우리 Rect는 약간 변경된 변수 구조 위에 다음과 같이 readonly메소드를 추가 GetAreaReadOnly합니다.

public struct Rect
{
    
    
    public float w;
    public float h;

    public float Area
    {
    
    
        get
        {
    
    
            return w * h;
        }
    }

    public readonly float GetAreaReadOnly()
    {
    
    
        return Area; //警告 CS8656 从 "readonly" 成员调用非 readonly 成员 "Rect.Area.get" 将产生 "this" 的隐式副本。
    }
}

이 시점에서 코드를 컴파일 할 수 있지만 다음과 같은 경고가 표시됩니다. "readonly"멤버에서 읽기 전용이 아닌 멤버 "Rect.Area.get"을 호출하면 "this"의 암시 적 복사본이 생성됩니다.
즉, National Cheng Kung University 언어 번역 즉, 읽기 전용 방법에서는 읽기 전용 GetAreaReadOnly이 아닌 Area속성을 "이"방어 적 사본 이라고 부릅니다 . 코드를 컴파일 한 후 GetAreaReadOnly메서드 본문이 실제로 논리를 운영하는 방법은 다음과 같습니다.

[IsReadOnly]
public float GetAreaReadOnly()
{
    
    
    Rect rect = this; //防御性副本
    return rect.Area;
}

따라서 불필요한 방어 복사본을 생성하지 않고 성능에 영향을 미치려면 메서드 본문에 추가 readonly수정 자 (이 경우 속성 Area추가 readonly수정 자) 있는 읽기 전용 속성 또는 메서드 호출을 제공해야합니다 .

변수 구조에서 읽기 전용 인스턴스 멤버 호출

위의 예를 다시 수정 해 보겠습니다.

public struct Rect
{
    
    
    public float w;
    public float h;

    public readonly float Area
    {
    
    
        get
        {
    
    
            return w * h;
        }
    }

    public readonly float GetAreaReadOnly()
    {
    
    
        return Area;
    }

    public float GetArea()
    {
    
    
        return Area;
    }
}

public class SampleClass
{
    
    
    public float CallGetArea(Rect vector)
    {
    
    
        return vector.GetArea();
    }

    public float CallGetAreaIn(in Rect vector)
    {
    
    
        return vector.GetArea();
    }

    public float CallGetAreaReadOnly(in Rect vector)
    {
    
    
        //调用可变结构体中的只读实例成员
        return vector.GetAreaReadOnly();
    }
}

클래스 SampleClass는 세 가지 방법으로 정의됩니다.

  • 첫 번째 방법은 이전의 일반적인 호출 방법입니다.
  • in변수 구조에 대한 두 번째 인수에서 읽기 전용이 아닌 (메소드가 구조의 상태를 수정할 수 있음) 호출;
  • 세 번째 in매개 변수에서 변수 구조로, 읽기 전용 메소드 호출.

두 번째와 세 번째 방법의 차이점에 중점을 두거나 아래 표시된 것처럼 IL 코드 로직을 이해하기 쉬운 실행 로직으로 변환 해 보겠습니다 .

public float CallGetAreaIn([In] [IsReadOnly] ref Rect vector)
{
    
    
    Rect rect = vector; //防御性副本
    return rect.GetArea();
}

public float CallGetAreaReadOnly([In] [IsReadOnly] ref Rect vector)
{
    
    
    return vector.GetAreaReadOnly();
}

그것은 것을 알 수 CallGetAreaReadOnly의 제 (판독 전용) 멤버 메소드 호출시 CallGetAreaIn구조를 지역 방어 복사 미만 생성된다 (구조의 비판 독 - 전용 멤버 메소드가 호출된다)이므로있는 장점이 있어야 실행 성능.

읽기 전용 인스턴스 멤버의 성능 분석

구조의 성능 향상이 클 때 분명하므로 3 번 방법의 성능 차이를 강조하기 위해 테스트 할 때 Rect10 진수 속성 유형 구조체를 30 개 추가 한 다음 SampleClass3 개의 클래스 를 추가 한 테스트 방법은 다음과 같습니다. 다음과 같습니다.

public struct Rect
{
    
    
    public float w;
    public float h;

    public readonly float Area
    {
    
    
        get
        {
    
    
            return w * h;
        }
    }

    public readonly float GetAreaReadOnly()
    {
    
    
        return Area;
    }

    public float GetArea()
    {
    
    
        return Area;
    }

    public decimal Number1 {
    
     get; set; }
    public decimal Number2 {
    
     get; set; }
    //...
    public decimal Number30 {
    
     get; set; }
}

public class SampleClass
{
    
    
    const int loops = 50000000;
    Rect rectInstance;

    public SampleClass()
    {
    
    
        rectInstance = new Rect();
    }

    [Benchmark(Baseline = true)]
    public float DoNormalLoop()
    {
    
    
        float result = 0F;
        for (int i = 0; i < loops; i++)
        {
    
    
            result = CallGetArea(rectInstance);
        }
        return result;
    }

    [Benchmark]
    public float DoNormalLoopByIn()
    {
    
    
        float result = 0F;
        for (int i = 0; i < loops; i++)
        {
    
    
            result = CallGetAreaIn(in rectInstance);
        }
        return result;
    }

    [Benchmark]
    public float DoReadOnlyLoopByIn()
    {
    
    
        float result = 0F;
        for (int i = 0; i < loops; i++)
        {
    
    
            result = CallGetAreaReadOnly(in rectInstance);
        }
        return result;
    }

    public float CallGetArea(Rect vector)
    {
    
    
        return vector.GetArea();
    }

    public float CallGetAreaIn(in Rect vector)
    {
    
    
        return vector.GetArea();
    }

    public float CallGetAreaReadOnly(in Rect vector)
    {
    
    
        return vector.GetAreaReadOnly();
    }
}

in메소드 매개 변수를 사용하지 않으면 수신되는 각 호출이 변수의 새 사본이고 in수정 자 메소드를 사용 하면 변수의 각 새 사본이 전달되지 않고 동일한 읽기 전용 참조 사본을 전달합니다.

  • DoNormalLoop 메서드, 수정자가없는 매개 변수, 일반 구조를 전달하고 변수 구조의 읽기 전용이 아닌 메서드를 호출합니다. 이는 과거의 일반적인 관행입니다.
  • DoNormalLoopByIn메서드, 매개 변수 및 in수정 자, 들어오는 일반 구조 , 변수 구조에 대한 읽기 전용 호출 메서드가 아닙니다.
  • DoReadOnlyLoopByIn메소드, 매개 변수 및 in수정 자, 수신 호출의 일반 구조는 변수 구조에 대한 읽기 전용 메소드입니다.

BenchmarkDotNet 도구를 사용하여 세 가지 방법의 실행 시간을 테스트하면 결과는 다음과 같습니다.

방법 평균 오류 StdDev 비율 RatioSD
DoNormalLoop 2.034 초 0.0392 초 0.0348 초 1.00 0.00
DoNormalLoopByIn 3.490 초 0.0667 초 0.0557 초 1.71 0.03
DoReadOnlyLoopByIn 1.041 초 0.0189 초 0.0202 초 0.51 0.01

그 결과, 변수 구조가 in읽기 전용 매개 변수 메소드 호출 구조를 사용하면 다른 두 개보다 성능이 우수합니다. in비 매개 변수 메소드를 사용하면 읽기 전용 변수 구조를 호출하여 실행 시간이 가장 길며 심각한 영향을 미칩니다. 성능을 향상 시키려면 이러한 호출을 피해야합니다.

요약하자면

  • 구조가 변경 가능한 유형 인 경우 변경을 일으키지 않는 (즉, 구조의 상태가 변경되지 않는) 멤버는 readonly.
  • in매개 변수를 사용하여 구조의 멤버가 효과적으로 성능을 향상시킬 수 있는 경우에만 호출되는 읽기 전용의 예입니다 .
  • readonly읽기 전용 속성에는 수정자가 필요합니다. 컴파일러는 getter 방문자가 상태를 수정하지 않는다고 가정하지 않습니다. 따라서 속성에 명시 적으로 선언해야합니다.
  • 자동 속성은 readonly수정자를 생략 할 수 있습니다. 수정자가 readonly존재하더라도 컴파일러는 모든 getter를 읽기 전용으로 자동으로 구현합니다.
  • in읽기 전용이 아닌 인스턴스 멤버는 성능에 부정적인 영향을 미치므로 구조 매개 변수를 호출 하지 마십시오 .

저자 : Technical Zemin
출판사 : Technical Translation Station

공개 번호 : 기술 번역 스테이션


  1. https://ittranslator.cn/dotnet/csharp/2020/10/26/c-7-2-talk-about-readonly-struct.html C #의 읽기 전용 구조 ↩︎

  2. https://ittranslator.cn/dotnet/csharp/2020/11/02/understanding-in-modifier-csharp.html in parameter and performance analysis in C # ↩︎

추천

출처blog.csdn.net/weixin_47498376/article/details/109676815