仅测量并发读取具有误导性,您的缓存将为您提供比实际用例更好的结果数量级。所以我在 Marc 的例子中添加了 SetValue:
using System;
using System.Diagnostics;
using System.Threading;
abstract class Experiment
{
public abstract double GetValue();
public abstract void SetValue(double value);
}
class Example1 : Experiment
{
private readonly object _sync = new object();
private double _value = 3;
public override double GetValue()
{
lock (_sync)
{
return _value;
}
}
public override void SetValue(double value)
{
lock (_sync)
{
_value = value;
}
}
}
class Example2 : Experiment
{
private readonly object _sync = new object();
private double _value = 3;
public override double GetValue()
{
lock (_sync)
{
return _value;
}
}
public override void SetValue(double value)
{
lock (_sync)
{
_value = value;
}
}
}
class Example3 : Experiment
{
private readonly object _sync = new object();
private double _value = 3;
public override double GetValue()
{
double result = 0;
lock (_sync)
{
result = _value;
}
return result;
}
public override void SetValue(double value)
{
lock (_sync)
{
_value = value;
}
}
}
class CompareExchange : Experiment
{
private double _value = 3;
public override double GetValue()
{
return Interlocked.CompareExchange(ref _value, 0, 0);
}
public override void SetValue(double value)
{
Interlocked.Exchange(ref _value, value);
}
}
class ReadUnsafe : Experiment
{
private long _value = DoubleToInt64(3);
static unsafe long DoubleToInt64(double val)
{ // I'm mainly including this for the field initializer
// in real use this would be manually inlined
return *(long*)(&val);
}
public override unsafe double GetValue()
{
long val = Interlocked.Read(ref _value);
return *(double*)(&val);
}
public override void SetValue(double value)
{
long intValue = DoubleToInt64(value);
Interlocked.Exchange(ref _value, intValue);
}
}
class UntypedBox : Experiment
{
// references are always atomic
private volatile object _value = 3.0;
public override double GetValue()
{
return (double)_value;
}
public override void SetValue(double value)
{
object valueObject = value;
_value = valueObject;
}
}
class TypedBox : Experiment
{
private sealed class Box
{
public readonly double Value;
public Box(double value) { Value = value; }
}
// references are always atomic
private volatile Box _value = new Box(3);
public override double GetValue()
{
Box value = _value;
return value.Value;
}
public override void SetValue(double value)
{
Box boxValue = new Box(value);
_value = boxValue;
}
}
static class Program
{
static void Main()
{
// once for JIT
RunExperiments(1);
// three times for real
RunExperiments(5000000);
RunExperiments(5000000);
RunExperiments(5000000);
}
static void RunExperiments(int loop)
{
Console.WriteLine("x{0}", loop);
RunExperiment(new Example1(), loop);
RunExperiment(new Example2(), loop);
RunExperiment(new Example3(), loop);
RunExperiment(new CompareExchange(), loop);
RunExperiment(new ReadUnsafe(), loop);
RunExperiment(new UntypedBox(), loop);
RunExperiment(new TypedBox(), loop);
Console.WriteLine();
}
static void RunExperiment(Experiment test, int loop)
{
// avoid any GC interruptions
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
int threads = Environment.ProcessorCount;
ManualResetEvent done = new ManualResetEvent(false);
// Since we use threads, divide the original workload
//
int workerLoop = Math.Max(1, loop / Environment.ProcessorCount);
int writeRatio = 1000;
int writes = Math.Max(workerLoop / writeRatio, 1);
int reads = workerLoop / writes;
var watch = Stopwatch.StartNew();
for (int t = 0; t < Environment.ProcessorCount; ++t)
{
ThreadPool.QueueUserWorkItem((state) =>
{
try
{
double val = 0;
// Two loops to avoid comparison for % in the inner loop
//
for (int j = 0; j < writes; ++j)
{
test.SetValue(j);
for (int i = 0; i < reads; i++)
{
val = test.GetValue();
}
}
}
finally
{
if (0 == Interlocked.Decrement(ref threads))
{
done.Set();
}
}
});
}
done.WaitOne();
watch.Stop();
Console.WriteLine("{0}\t{1}ms", test.GetType().Name,
watch.ElapsedMilliseconds);
}
}
结果是,以 1000:1 的读:写比率:
x5000000
Example1 353ms
Example2 395ms
Example3 369ms
CompareExchange 150ms
ReadUnsafe 161ms
UntypedBox 11ms
TypedBox 9ms
100:1(读:写)
x5000000
Example1 356ms
Example2 360ms
Example3 356ms
CompareExchange 161ms
ReadUnsafe 172ms
UntypedBox 14ms
TypedBox 13ms
10:1(读:写)
x5000000
Example1 383ms
Example2 394ms
Example3 414ms
CompareExchange 169ms
ReadUnsafe 176ms
UntypedBox 41ms
TypedBox 43ms
2:1(读:写)
x5000000
Example1 550ms
Example2 581ms
Example3 560ms
CompareExchange 257ms
ReadUnsafe 292ms
UntypedBox 101ms
TypedBox 122ms
1:1(读:写)
x5000000
Example1 718ms
Example2 745ms
Example3 730ms
CompareExchange 381ms
ReadUnsafe 376ms
UntypedBox 161ms
TypedBox 200ms
*更新了代码以删除写入时不必要的 ICX 操作,因为该值总是被覆盖。还修复了计算除以线程的读取数的公式(同样的工作)。