이 장에서는 책의 다른 부분에서 다루지 않은 몇 가지 코딩 및 디자인 원칙을 소개합니다. 일부 .NET 애플리케이션 시나리오가 포함되어 있으며 일부는 큰 해를 끼치지 않으며 일부는 명백한 문제를 야기합니다. 나머지는 어떻게 사용하느냐에 따라 다른 효과를 가지게 됩니다. 이 장에서 제시된 원칙을 요약하면 다음과 같습니다.
과도한 최적화는 코드 추상화에 영향을 미칩니다.
이는 더 높은 최적화 성능을 원할 때 각 수준의 구현을 이해해야 함을 의미합니다. 코드 세부 사항. 이 장에서는 관련된 소개가 많이 있을 것입니다.
클래스의 인스턴스는 힙에 할당되고 포인터 참조를 통해 액세스됩니다. 이러한 객체를 전달하는 것은 단지 포인터의 복사본(4 또는 8 직접)이기 때문에 비용이 저렴합니다. 그러나 개체에는 8바이트 또는 16바이트(32비트 또는 64비트 시스템)라는 고정된 오버헤드도 있습니다. 이 오버헤드에는 다른 목적으로 사용되는 메소드 테이블 및 동기화 필드에 대한 포인터가 포함됩니다. 하지만 디버깅 도구를 통해 빈 개체가 차지하는 메모리를 살펴보면 13~24바이트(32비트 또는 64비트 시스템) 더 큰 것을 알 수 있습니다. 이는 .NET의 메모리 정렬 메커니즘으로 인해 발생합니다.
구조에는 위의 오버헤드가 없으며 메모리 사용량은 필드 크기의 조합입니다. 구조체가 메서드(함수) 내에서 선언된 지역 변수인 경우 스택에 제어를 할당합니다. 구조가 클래스의 일부로 선언된 경우 구조에서 사용하는 메모리는 클래스의 메모리 레이아웃의 일부입니다(따라서 힙에 할당됨). 그러나 구조를 메서드(함수)에 전달하면 바이트 데이터가 복사됩니다. 힙에 있지 않기 때문에 구조로 인해 가비지 수집이 발생하지 않습니다.
그래서 여기에 타협이 있습니다. 구조체 크기에 대한 다양한 제안을 찾을 수 있지만 여기서는 정확한 숫자를 제공하지 않습니다. 대부분의 경우 구조체의 크기를 작게 유지해야 하며, 특히 구조체를 자주 전달해야 하는 경우 구조체의 크기가 너무 큰 문제를 일으키지 않는지 확인해야 합니다. 확실한 것은 자신의 애플리케이션 시나리오에 따라 분석해야 한다는 것입니다.
어떤 경우에는 효율성의 차이가 상당히 큽니다. 객체의 오버헤드가 크지 않아 보이지만, 객체의 배열과 구조체의 배열을 비교해 보면 차이를 알 수 있습니다. 32비트 시스템에서 데이터 구조에 16바이트의 데이터가 포함되어 있고 배열 길이가 100w라고 가정합니다.
객체 배열이 차지하는 공간
8바이트 배열 오버헤드 +
(4바이트 포인터 주소 구조체 배열이 차지하는 공간
8바이트 배열 오버헤드 +
(16바이트 데이터)
공간 외에도 CPU 효율성 문제도 있습니다. CPU에는 여러 수준의 캐시가 있습니다. CPU에 가까울수록 캐시는 작지만 액세스 속도는 빨라지고 순차적으로 저장되는 데이터에 대한 최적화가 더 쉬워집니다.
구조체 배열의 경우 모두 메모리 내 연속 값입니다. 구조 배열의 데이터에 액세스하는 것은 매우 간단합니다. 올바른 위치를 찾으면 해당 값을 얻을 수 있습니다. 이는 대규모 데이터 배열을 반복할 때 큰 차이가 있음을 의미합니다. 값이 이미 CPU 캐시에 있는 경우 액세스 속도는 RAM에 액세스하는 것보다 훨씬 빠릅니다.
객체 배열의 항목에 액세스하려면 먼저 객체의 포인터 참조를 얻은 다음 힙에서 해당 항목에 액세스해야 합니다. 객체 배열을 반복하면 데이터 포인터가 힙에서 점프하게 되어 CPU 캐시가 자주 업데이트되므로 CPU 캐시 데이터에 액세스할 수 있는 기회가 많이 낭비됩니다.
많은 경우, 메모리에 저장된 데이터의 위치를 개선하여 메모리에 대한 CPU 액세스 비용을 줄이는 것은 성능을 크게 향상시킬 수 있는 구조를 사용하는 주요 이유 중 하나입니다.
구조는 사용될 때 항상 복사되므로 코딩할 때 주의하세요. 그렇지 않으면 흥미로운 버그가 발생할 수 있습니다. 예를 들어 다음 밤나무는 컴파일할 수 없습니다.
struct Point { public int x; public int y; } public static void Main() { List<Point> points = new List<Point>(); points.Add(new Point() {x = 1, y = 2}); points[0].x = 3; }문제는 마지막 줄에 있습니다. 목록에 있는 Point 요소의 특정 값을 수정하려고 하면 points[0]가 원본을 반환하기 때문에 이 작업이 불가능합니다. 가치. 값을 수정하는 올바른 방법은
Point p = points[0]; p.x = 3; points[0] = p;입니다. 그러나 더 엄격한 코딩 전략을 채택할 수 있습니다. 구조를 수정하지 마세요. 일단 구조가 생성되면 그 값은 절대 변경되어서는 안 됩니다. 이는 위의 컴파일 문제를 제거하고 구조 사용 규칙을 단순화합니다.
복사하는 데 많은 시간을 소비하지 않도록 구조를 작게 유지해야 한다고 앞서 언급했지만 때로는 일부 큰 구조가 사용되기도 합니다. 예를 들어, 최종 비즈니스 프로세스 세부 정보의 개체는 많은 수의 타임스탬프를 저장해야 합니다.
class Order { public DateTime ReceivedTime { get; set; } public DateTime AcknowledgeTime { get; set; } public DateTime ProcessBeginTime { get; set; } public DateTime WarehouseReceiveTime { get; set; } public DateTime WarehouseRunnerReceiveTime { get; set; } public DateTime WarehouseRunnerCompletionTime { get; set; } public DateTime PackingBeginTime { get; set; } public DateTime PackingEndTime { get; set; } public DateTime LabelPrintTime { get; set; } public DateTime CarrierNotifyTime { get; set; } public DateTime ProcessEndTime { get; set; } public DateTime EmailSentToCustomerTime { get; set; } public DateTime CarrerPickupTime { get; set; } // lots of other data ... }
코드를 단순화하기 위해 시간 데이터를 자체 하위 구조로 나누어 이 코드에서 Order 개체에 액세스할 수 있습니다. 방법:
Order order = new Order(); Order.Times.ReceivedTime = DateTime.UtcNow;
모든 데이터를 자체 클래스에 넣을 수 있습니다:
class OrderTimes { public DateTime ReceivedTime { get; set; } public DateTime AcknowledgeTime { get; set; } public DateTime ProcessBeginTime { get; set; } public DateTime WarehouseReceiveTime { get; set; } public DateTime WarehouseRunnerReceiveTime { get; set; } public DateTime WarehouseRunnerCompletionTime { get; set; } public DateTime PackingBeginTime { get; set; } public DateTime PackingEndTime { get; set; } public DateTime LabelPrintTime { get; set; } public DateTime CarrierNotifyTime { get; set; } public DateTime ProcessEndTime { get; set; } public DateTime EmailSentToCustomerTime { get; set; } public DateTime CarrerPickupTime { get; set; } } class Order { public OrderTimes Times; }
但是,这样会为每个Order对象引入额外的12或者24字节的开销。如果你需要将OrderTimes对象作为一个整体传入各种方法函数里,这也许是有一定道理的,但为什么不把Order对象传入方法里呢?如果你同时有数千个Order对象,则可能会导致更多的垃圾回收,这是额外的对象增加的引用导致的。
相反,将OrderTime更改为结构体,通过Order上的属性(例如:Order.Times.ReceivedTime)访问OrderTImes结构体的各个属性,不会导致结构体的副本(.NET会对这个访问做优化)。这样OrderTimes结构体基本上成为Order类的内存布局的一部分,几乎和没有子结构体一样了,你拥有了更加漂亮的代码。
这种技术确实违反了不可变的结构体原理,但这里的技巧就是将OrderTimes结构的字段视为Order对象的字段。你不需要将OrderTimes结构体作为一个实体进行传递,它只是一个代码组织方式。
위 내용은 코딩 및 디자인 원칙의 몇 가지 예를 요약합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!