ホームページ  >  記事  >  バックエンド開発  >  コーディングと設計原則の例をいくつか要約する

コーディングと設計原則の例をいくつか要約する

零下一度
零下一度オリジナル
2017-06-24 10:36:271542ブラウズ

この章では、本の他の部分では説明されていないコーディングと設計の原則をいくつか紹介します。いくつかの .NET アプリケーション シナリオが含まれています。大きな害を及ぼさないものもあれば、明らかな問題を引き起こすものもあります。残りは使い方次第で様々な効果が得られます。この章で説明する原則を要約すると、次のようになります:

過度の最適化はコードの抽象化に影響します
これは、より高い最適化パフォーマンスが必要な場合、各レベルの実装を理解する必要があることを意味します。コードの詳細。この章では、関連する多くの紹介が行われます。

クラス vs 構造

クラスのインスタンスはヒープ上に割り当てられ、ポインター参照を通じてアクセスされます。これらのオブジェクトを渡すことは、ポインターの単なるコピー (4 または 8 を直接) であるため、コストがかかりません。ただし、オブジェクトには一定のオーバーヘッドもあります: 8 バイトまたは 16 バイト (32 または 64 ビット システム)。このオーバーヘッドには、他の目的に使用されるメソッド テーブルおよび同期フィールドへのポインターが含まれます。ただし、デバッグ ツールを使用して空のオブジェクトが占有するメモリを確認すると、13 バイトまたは 24 バイト大きいことがわかります (32 ビットまたは 64 ビット システム)。これは、.NET のメモリ アライメント メカニズムが原因で発生します。

この構造体には上記のオーバーヘッドはなく、そのメモリ使用量はフィールド サイズの組み合わせになります。構造体がメソッド (関数) 内で宣言されたローカル変数の場合、スタック上に制御を割り当てます。構造体がクラスの一部として宣言されている場合、その構造体によって使用されるメモリはクラスのメモリ レイアウトの一部です (したがって、ヒープ上に割り当てられます)。ただし、構造体をメソッド (関数) に渡すと、バイト データがコピーされます。ヒープ上にないため、ガベージコレクションが発生しない構造となっています。

つまり、ここには妥協点があります。構造体のサイズについてはさまざまな提案がありますが、ここでは正確な数値は示しません。ほとんどの場合、構造体を頻繁に渡す必要がある場合は特に、構造体のサイズが大きな問題を引き起こさないようにする必要があります。唯一確かなことは、独自のアプリケーション シナリオに基づいて分析する必要があるということです。

場合によっては、効率の差が非常に大きくなることがあります。オブジェクトのオーバーヘッドはそれほど大きくないようですが、オブジェクトの配列と構造体の配列を比較すると、その違いがわかります。 32 ビット システムでは、データ構造に 16 バイトのデータが含まれており、配列の長さが 100w であると仮定します。
オブジェクト配列が占有する空間
8バイトの配列オーバーヘッド+
(4バイトのポインタアドレス 構造体配列が占有する空間
8バイトの配列オーバーヘッド+
(16バイトのデータ)

構造体配列では、同じサイズのデータ​​が占めるメモリの量が少ないことがわかります。オブジェクト配列内のオブジェクトの数が増加すると、GC への負荷も増加します。

スペースに加えて、CPU 効率の問題もあります。 CPU には複数レベルのキャッシュがあります。 CPU に近いほどキャッシュは小さくなりますが、アクセス速度が速くなり、順次保存されるデータの最適化が容易になります。
構造体配列の場合、それらはすべてメモリ内の連続値です。構造体配列内のデータへのアクセスは非常に簡単で、正しい位置が見つかっていれば、対応する値を取得できます。これは、大規模なデータ配列を反復処理する場合に大きな違いが生じることを意味します。値がすでに CPU のキャッシュにある場合、そのアクセス速度は RAM にアクセスするよりも 1 桁速くなります。

オブジェクト配列内の項目にアクセスしたい場合は、まずオブジェクトのポインタ参照を取得してから、ヒープ内のそれにアクセスする必要があります。オブジェクト配列を反復処理すると、データ ポインターがヒープ内でジャンプし、CPU キャッシュが頻繁に更新されるため、CPU キャッシュ データにアクセスする多くの機会が無駄になります。

多くの場合、メモリに格納されるデータの場所を改善することで CPU のメモリ アクセスのコストを削減することが構造体を使用する主な理由の 1 つであり、これによりパフォーマンスが大幅に向上します。

構造体は使用時に常にコピーされるため、コーディング時には注意してください。そうしないと、興味深いバグが発生します。たとえば、次の栗はコンパイルできません:

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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。