Этот вопрос получил довольно много комментариев, но до сих пор все ответы пытаются перефразировать вопрос, чтобы решить проблемы с перегрузкой оператора или побочными эффектами установщика.
Если сеттер используется несколькими потоками, это действительно может изменить ситуацию.Проверка перед заданным шаблоном может (вам следует измерить) быть полезной, если вы перебираете одни и те же данные с несколькими потоками, которые изменяют данные.Название учебника для этого явления называется ложное разделение .Если вы прочитали данные и убедились, что они уже соответствуют целевому значению, вы можете пропустить запись.
Если вы пропустите запись, ЦПУ не нужно очищать строку кэша (64-байтовый блок на процессорах Intel), чтобы другие ядра увидели измененное значение.Если другое ядро собиралось прочитать некоторые другие данные из этого 64-байтового блока, то вы просто замедлили свое ядро и увеличили многоядерный трафик, чтобы синхронизировать содержимое памяти между кэшами ЦП.
Следующий пример приложения показывает этот эффект, который также содержит проверку перед записью:
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, если доступ к памяти только однопоточный.Если несколько потоков работают с одинаковыми или близкими данными, может произойти ложное совместное использование, которое может стоить до приблизительно.20% производительности доступа к памяти.
Обновление 1 Я провел больше тестов и создал диаграмму, чтобы показать чат с несколькими ядрами.Это показывает простой набор ( Безусловный набор ), как это было отмечено комментатором Фрэнком Хопкинсом. Условно не установлено содержит значение if, которое никогда не устанавливает значение.И последнее, но не менее важное: Условный набор установит значение в условии if.
![Performance vs Cores](https://i.stack.imgur.com/xF3vS.png)