这个问题已经获得了相当多的 cmets,但到目前为止,所有答案都试图重新构建问题以解决运算符重载或 setter 副作用的问题。
如果 setter 被多个线程使用,它真的会产生影响。如果您使用多个更改数据的线程迭代相同的数据,则设置模式之前的检查可能(您应该测量)很有用。这种现象的教科书名称称为false sharing。如果您读取数据并确认它已经与目标值匹配,则可以省略写入。
如果省略写入,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 语句。如果多个线程正在处理相同或附近的数据,则可能会发生错误共享,这可能会导致您高达 ca. 20% 的内存访问性能。
更新 1
我进行了更多测试并创建了一个图表来显示跨核心聊天。这显示了一个简单的集合(Unconditional Set),正如评论者 Frank Hopkins 所指出的那样。 Conditional Not Set 包含从不设置值的 if。最后但并非最不重要的一点是 Conditional Set 将在 if 条件中设置值。