省略 - c# 三項演算子




冗長比較&代入前の「if」 (5)

これがその例です。

if(value != ageValue) {
  ageValue = value;
}

つまり、ある変数の値を別の変数の値に代入した場合、それらが同じ値を持つかどうかを確認する必要があるのはなぜでしょうか。

それは私を混乱させる。 より広い文脈はここにあります:

private double ageValue;
public double Age {
  get {
    return ageValue;
  }

  set {
    if(value != ageValue) {
      ageValue = value;
    }
  }
}

winformsコントロールでは、BackgroundColorを特定の色に設定しました。

myControl.BackgroundColor = Color.White

特定の状況下では、これはタイトなループで起こり、フリーズしたUIにつながる可能性があります。 パフォーマンスを分析した結果、この呼び出しが凍結されたUIの理由であることがわかったため、単純に次のように変更しました。

if (myControl.BackgroundColor != Color.White)
    myControl.BackgroundColor = Color.White

そして私達のツールのパフォーマンスは順調に戻っていました(そしてそれから私達はタイトなループの理由を排除しました)。

そのため、このチェックは必ずしも冗長ではありません。 特に、ターゲットがセッター内でより効果的なプロパティの場合は、値をバッキングストアに適用するだけです。


この質問にはかなりのコメントが寄せられていますが、これまでのところすべての回答は、オペレータの過負荷やセッターの副作用に関する問題に対処するために質問をリフレームしようとしています。

セッターが複数のスレッドによって使用されている場合、それは本当に違いを生む可能性があります。 データを変更する複数のスレッドを使用して同じデータを反復処理する場合は、set patternの前のチェック(測定する必要があります)が役立ちます。 この現象の教科書名は、 偽共有 と呼ばれてい ます 。 データを読み、それがすでに目標値と一致していることを確認した場合は、書き込みを省略できます。

書き込みを省略した場合、CPUはキャッシュライン(Intel CPUの64バイトブロック)をフラッシュして他のコアが確実に変更された値を認識するようにする必要はありません。 もう一方のコアがその64バイトブロックから他のデータを読み込もうとしているのであれば、コアの速度を落としてコアキャッシュ間のトラフィックを増やし、CPUキャッシュ間でメモリの内容を同期させます。

次のサンプルアプリケーションは、書き込み前の確認条件も含まれているこの効果を示しています。

 if (tmp1 != checkValue)  // set only if not equal to checkvalue
 {
    values[i] = checkValue;
 }

これが完全なコードです。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        const int N = 500_000_000;
        int[] values = new int[N]; // 2 GB
        for (int nThreads = 1; nThreads < Environment.ProcessorCount; nThreads++)
        {
            SetArray(values, checkValue: 1, nTimes: 10, nThreads: nThreads);
            SetArray(values, checkValue: 2, nTimes: 10, nThreads: nThreads);
            SetArrayNoCheck(values, checkValue: 2, nTimes: 10, nThreads: nThreads);
        }
    }

    private static void SetArray(int[] values, int checkValue, int nTimes, int nThreads)
    {
        List<double> ms = new List<double>();

        for (int k = 0; k < nTimes; k++)  // set array values to 1
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = 1;
            }

            var sw = Stopwatch.StartNew();
            Action acc = () =>
            {
                int tmp1 = 0;
                for (int i = 0; i < values.Length; i++)
                {
                    tmp1 = values[i];
                    if (tmp1 != checkValue)  // set only if not equal to checkvalue
                    {
                        values[i] = checkValue;
                    }
                }
            };

            Parallel.Invoke(Enumerable.Repeat(acc, nThreads).ToArray());  // Let this run on 3 cores

            sw.Stop();
            ms.Add(sw.Elapsed.TotalMilliseconds);
            //  Console.WriteLine($"Set {values.Length * 4 / (1_000_000_000.0f):F1} GB of Memory in {sw.Elapsed.TotalMilliseconds:F0} ms. Initial Value 1. Set Value {checkValue}");
        }
        string descr = checkValue == 1 ? "Conditional Not Set" : "Conditional Set";
        Console.WriteLine($"{descr}, {ms.Average():F0}, ms, nThreads, {nThreads}");

    }

    private static void SetArrayNoCheck(int[] values, int checkValue, int nTimes, int nThreads)
    {
        List<double> ms = new List<double>();
        for (int k = 0; k < nTimes; k++)  // set array values to 1
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = 1;
            }

            var sw = Stopwatch.StartNew();
            Action acc = () =>
            {
                for (int i = 0; i < values.Length; i++)
                {
                        values[i] = checkValue;
                }
            };

            Parallel.Invoke(Enumerable.Repeat(acc, nThreads).ToArray());  // Let this run on 3 cores

            sw.Stop();
            ms.Add(sw.Elapsed.TotalMilliseconds);
            //Console.WriteLine($"Unconditional Set {values.Length * 4 / (1_000_000_000.0f):F1} GB of Memory in {sw.Elapsed.TotalMilliseconds:F0} ms. Initial Value 1. Set Value {checkValue}");
        }
        Console.WriteLine($"Unconditional Set, {ms.Average():F0}, ms, nThreads, {nThreads}");
    }
}

それを実行させると、次のような値になります。

// Value not set
Set 2.0 GB of Memory in 439 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 420 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 429 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 393 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 404 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 395 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 419 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 421 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 442 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 422 ms. Initial Value 1. Set Value 1
// Value written
Set 2.0 GB of Memory in 519 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 582 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 543 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 484 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 523 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 540 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 552 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 527 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 535 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 581 ms. Initial Value 1. Set Value 2

その結果、22%高速なパフォーマンスが得られます。これは、高性能の数値処理シナリオでは重要になる可能性があります。

書かれた質問に答えるには:

メモリへのアクセスがシングルスレッドのみの場合は、if文を削除できます。 複数のスレッドが同じデータまたは近くのデータを処理していると、誤った共有が発生する可能性があり、最大で約10分の費用がかかります。 メモリアクセス性能の20%

更新1 私はより多くのテストを実行し、クロスコアチットチャットを示すチャートを作成しました。 それがコメント投稿者Frank Hopkinsによって指摘されたように、これは単純なセット( 無条件セット )を示しています。 条件付き未設定 には、値を設定しないifが含まれます。 そして最後になりましたが、 条件セット はif条件に値を設定します。


チェックが 非常に便利な 場合のコードサンプルは次のとおり です

 public class MyClass {
    ...
    int ageValue = 0;

    public int AgeValue {
      get {
        return ageValue
      }
      protected set {
        ... // value validation here

        // your code starts
        if (value != ageValue) { 
          ageValue = value; 
        }
        // your code ends
        else
          return; // do nothing since value == ageValue

        // ageValue has been changed
        // Time (or / and memory) consuming process
        SaveToRDBMS();
        InvalidateCache(); 
        ...
      } 
    } 

 ... 

しかしながら、より自然な実装は、不必要な計算を避けるために最初にチェックインすることです。

    protected set {
      if (ageValue == value)
        return;

      ... // value validation here
      ageValue = value; 

      // ageValue has been changed
      // Time (or / and memory) consuming process
      SaveToRDBMS();
      InvalidateCache();  
      ...
    }

パフォーマンスはそれほど重要ではなく、ロジックのニーズによって異なります。


私は実際にこのようなものを数回コーディングしましたが、それはさまざまな理由からです。 それらは説明するのがちょっと難しいので、私と一緒にしてください。

主なことは、参照の値が前の参照の値と論理的に等しい場合は、 新しい参照を 設定しないことです。 上記のコメントでは、ユーザーはこのシナリオの厄介さを批判しています - そして対処する必要があるの 厄介です - しかし、場合によっては本質的に必要です。

私はこのようなユースケースを分割しようと思います:

  1. 値は抽象データ型です。ここでは、同じ論理値を表すさまざまな構成済みインスタンスがあります。

    • これはMathematicaのように、原始的な数値を使うことができないので、同じものを表すことを意図したさまざまなオブジェクトに終わってしまうような数学プログラムではよく起こります。
  2. value の参照はキャッシュロジックに役立ちます。

    • これは抽象数値を使うときにも現れることがあります。 たとえば、プログラムの他の部分で参照に関するデータがキャッシュされていると予想される場合は、他の場所で使用されているキャッシュを無効にするため、論理的に同等の参照に置き換えたくありません。
  3. あなたはリアクティブエバリュエーターを使用しています、そこで新しい値を設定することは更新の連鎖反応を強いるかもしれません。

    • 厳密には、これがどのようにそしてなぜそのことが重要なのかは文脈によって異なります。

大きな概念上のポイントは、場合によっては、同じ参照値を異なる参照に格納することができますが、次の2つの大きな理由で縮退参照の数を最小限に抑えたいということです。

  1. 同じ論理値を複数回格納すると、より多くのメモリを消費します。

  2. 実行時の多くは、たとえばキャッシュなど、ショートカットとして参照チェックを使用できます。これは、同じ論理値への重複した参照を伝播させないようにすると、より効率的になります。

別のランダムな例では、 .NETのガベージコレクタは " 世代別 "です 。つまり、値が新しいときに値を収集できるかどうかを確認することにより多くの労力を費やします。 そのため、古い参照を優先的に保持すると、ガベージコレクタが有利になる可能性があります。新しい参照のほうがガベージコレクションを早く実行できるようになります。

抽象データ型を使用したもう1つのユースケースは、遅延評価されたプロパティが追加されている可能性がある場合です。 たとえば、 .IsRational.IsEven などのプロパティを持つ abstract class Number があるとします。次に、それらをすぐに計算するのではなく、要求に応じて生成し、結果をキャッシュします。 このようなシナリオでは、論理的に == であっても、新しい value はそれに関連する情報が少なくなる可能性があるのに対して、古い論理値には同じ論理値の古い Number を保持することを好む傾向があります。 。

場合によってはこれが意味をなすことができるさまざまな理由をまとめる方法を考えるのはちょっと難しいですが、それを使用する理由がある場合は意味をなすことができる基本的に最適化です。 使用する理由がない場合は、動機が生じるまで心配しないでください。





if-statement