Memory Cache .Net 4.0 性能测试:惊人的结果
Posted
技术标签:
【中文标题】Memory Cache .Net 4.0 性能测试:惊人的结果【英文标题】:Memory Cache .Net 4.0 performance test : astonishing result 【发布时间】:2012-07-28 13:33:45 【问题描述】:此性能测试有误,或者系统缓存的性能异常出色?
这是我的结果: [13] 交互次数 100000 : 63 毫秒 [14] 交互次数 100000 : 139 毫秒 [12] 交互次数 100000 : 47 毫秒 [15] 交互次数 100000 : 44 毫秒 测试结束。
硬件: x86 Family 6 Model 23 Stepping GenuineIntel ~2992 Mhz 3.327 MB, 5.1.2600 Service Pack 3
using System;
using System.Collections.Generic;
using System.Runtime.Caching;
using System.Diagnostics;
using System.Threading;
namespace CacheNet40
public class CacheTest
private ObjectCache cache;
public CacheTest()
cache = MemoryCache.Default;
public void AddItem(CacheItem item, double span)
CacheItemPolicy cp = new CacheItemPolicy();
cp.SlidingExpiration.Add(TimeSpan.FromMinutes(span));
cache.Add(item, cp);
public Object GetItem(string key)
return cache.Get(key);
class Program
private static CacheTest Cache = new CacheTest();
private static string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@$?_-";
private static int counter = 0;
private static readonly object locker = new object();
static string CreateRandomString(int passwordLength, int idx)
char[] chars = new char[passwordLength];
Random rd = new Random((int)DateTime.Now.Ticks + idx);
for (int i = 0; i < passwordLength; i++)
chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
return new string(chars);
private static void CacheAccessTes()
int span = 5;
string key;
string data;
int itens = 1000;
int interactions = 100000;
int cont = 0;
int index = 0;
List<string> keys = new List<string>();
lock (locker)
counter++;
cont = itens;
//populates it with data in the cache
do
key = CreateRandomString(127, Thread.CurrentThread.ManagedThreadId + cont);
keys.Add(key);
data = CreateRandomString(156000, Thread.CurrentThread.ManagedThreadId + cont + 1);
CacheItem ci = new CacheItem(key, data);
Cache.AddItem(ci, span);
cont--;
while (cont > 0);
cont = interactions;
index = 0;
//test readings
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
do
Object ci = Cache.GetItem(keys[index]);
ci = null;
index++;
if (index == itens)
index = 0;
cont--;
while (cont > 0);
stopWatch.Stop();
lock (locker)
counter--;
string outstring = String.Format("[0] number of interactions 1 : 2 milliseconds", Thread.CurrentThread.ManagedThreadId, interactions, stopWatch.ElapsedMilliseconds );
Console.WriteLine(outstring);
static void Main(string[] args)
for (int threads = 0; threads < 4; threads++)
Thread thread = new Thread(new ThreadStart(CacheAccessTes));
thread.Start();
Thread.Sleep(1000);
while (true)
lock (locker)
if (counter == 0) break;
Thread.Sleep(100);
Console.WriteLine("End of test.");
Console.ReadLine();
【问题讨论】:
对我来说看起来不错。是什么让你怀疑结果? 这里的实际问题是什么?究竟什么是惊人的? 我发现了非凡的表现,这让我很怀疑。 【参考方案1】:我刚刚在网上搜索了有关 MemoryCache
性能的信息,并偶然发现了这个 SO 问题。我问自己为什么没有使用合适的基准测试库,所以我最终通过非常懒惰来制作自己的基准测试(所有优秀的程序员都应该 :-) 并使用令人难以置信的 BenchmarkDotNet 库来检查效果如何(或不是)这个类的行为。
首先是结果
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
Frequency=1757813 Hz, Resolution=568.8887 ns, Timer=TSC
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0
DefaultJob : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0
| Method | N | Mean | Error | StdDev |
|------------------------------- |------ |----------:|----------:|----------:|
| FindDadosEmpInCache | 30000 | 231.40 ns | 0.4435 ns | 0.3703 ns |
| FindDataAtTheEnd | 30000 | 429.90 ns | 1.1490 ns | 1.0186 ns |
| FindDataInDictionary | 30000 | 24.09 ns | 0.2244 ns | 0.2099 ns |
| FindDataInConcurrentDictionary | 30000 | 29.66 ns | 0.0990 ns | 0.0926 ns |
| FindDataInHashset | 30000 | 16.25 ns | 0.0077 ns | 0.0065 ns |
现在解释一下……
我最感兴趣的是看看MemoryCache
与具有数千个条目的散列列表(Dictionary
、Hashset
...)相比有多快,以及对这种“长”列表的最坏情况线性搜索。所以我添加了一些额外的测试并意识到虽然MemoryCache
不如简单或并发列表快,但速度仍然在纳秒级。从 30,000 长的缓存项目列表中检索项目甚至不需要一毫秒。
公平地说,MemoryCache
比那些简单的列表做得更多,因为它必须控制并发、项目过期/驱逐等。我相信它对于各种工作负载来说已经足够快了,但如果你不需要它添加的功能,例如驱逐策略,您应该更好地坚持使用更简单的哈希列表实现。
另一方面,由于它比哈希查找“慢”一个数量级,因此可能还有改进的余地。我猜设计师认为它已经足够好了,我有什么资格不同意 DOTNET 工程师的意见? :-)
这是基准程序的源代码:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
namespace TestListPerformance
class Program
static void Main(string[] args)
var summary = BenchmarkRunner.Run<BenchmarkMemoryCache>();
public class BenchmarkMemoryCache
[Params(30000)]
public int N get; set;
public string FindStr;
private IList<DadosEmp> data;
private Dictionary<string, DadosEmp> dict;
private ConcurrentDictionary<string, DadosEmp> concurrentDict;
private HashSet<DadosEmp> hashset;
private DadosEmp last;
[GlobalSetup]
public void BuildData()
FindStr = N.ToString();
data = new List<DadosEmp>(N);
dict = new Dictionary<string, DadosEmp>(N);
concurrentDict = new ConcurrentDictionary<string, DadosEmp>();
hashset = new HashSet<DadosEmp>();
for (int i = 0; i <= N; i++)
DadosEmp d;
data.Add(d = new DadosEmp
Identificacao = i,
Pis = i * 100,
NumCartao = i * 1000,
Nome = "Nome " + i.ToString(),
);
MemoryCache.Default.Add(i.ToString(), d,
new CacheItemPolicy SlidingExpiration = TimeSpan.FromMinutes(30) );
dict.Add(i.ToString(), d);
concurrentDict.TryAdd(i.ToString(), d);
hashset.Add(d);
last = d;
[Benchmark]
public DadosEmp FindDadosEmpInCache()
var f = (DadosEmp)MemoryCache.Default.Get(FindStr);
return f;
[Benchmark]
public DadosEmp FindDataAtTheEnd()
var f = data.FirstOrDefault(e => e.NumCartao == N || e.Pis == N || e.Identificacao == N);
return f;
[Benchmark]
public DadosEmp FindDataInDictionary()
var f = dict[FindStr];
return f;
[Benchmark]
public DadosEmp FindDataInConcurrentDictionary()
var f = concurrentDict[FindStr];
return f;
[Benchmark]
public bool FindDataInHashset()
return hashset.Contains(last);
public class DadosEmp : IEquatable<DadosEmp>
public const string BIO_EXCLUSAO = "xbio";
public DadosEmp()
Biometrias = new List<string>();
public long Identificacao get; set;
public long Pis get; set;
public long NumCartao get; set;
public string Nome get; set;
public int VersaoBio get; set;
public string Unidade get; set;
public IList<string> Biometrias get; set;
public string Biometria get; set;
public bool ExcluirBiometria get return Biometria == BIO_EXCLUSAO;
public DateTime DataEnvioRep get; set;
public string SenhaTeclado get; set;
public bool ExigeAutorizacaoSaida get; set;
public bool BioRepPendente get; set;
public override bool Equals(object obj)
DadosEmp e = obj as DadosEmp;
if (ReferenceEquals(e, null))
return false;
return Equals(e);
public bool Equals(DadosEmp e)
if (ReferenceEquals(e, null))
return false;
return e.Pis == this.Pis;
public override int GetHashCode()
return Pis.GetHashCode();
public override string ToString()
return string.Format("0 (1 - 2)", Nome, Pis, Identificacao);
【讨论】:
【参考方案2】:看起来不错。虽然低于一秒的时间不是很可靠;您可能遇到了垃圾收集,您的 PC 可能会在短时间内执行其他操作,第一次 JIT 编译等。
所以增加计数。这也应该使每个线程的结果最终更接近。
我上周进行的一些测试使其达到每秒 800 万次迭代(不是很多,但仍然是)单线程。所以是的,这些天 PC 速度很快 ;-)
【讨论】:
【参考方案3】:问题是不能在多核机器上使用的 StopWatch 类! (我假设您有一个多核 CPU)与当线程从一个内核移动到另一个内核时 Bios 如何处理该计数器有关(即使是单线程应用程序也会跳转内核!)。
编辑: 查看 - http://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx - 特别是备注部分。 还有一个 *** 帖子 - Multicore and thread aware .Net stopwatch?。 结束编辑
我已经从高到低搜索了衡量应用程序性能的最佳方法,我想出的最可靠的是 DateTime.UtcNow。获取开始时间和结束时间,然后取它们之间的差异。您必须对代码进行足够的循环以克服低精度,但我遇到的其他方法都没有提供更可靠的准确性。
【讨论】:
@exacerbatedexpert - 在称事情为 FUD 之前,你为什么不研究一下这件事。看看 - msdn.microsoft.com/en-us/library/windows/desktop/… - 和备注部分。特别指出,由于 BIOS 错误,您可以获得不同的结果,这反过来意味着在多核机器上使用 StopWatch 毫无意义(不设置线程亲和性)。 @exacerbatedexpert -(我确实阅读了您的知识库文章)。 MSDN 文章备注部分没有指定每个 BIOS 都有该错误,但也没有指定只有少数 BIOS-s 有错误。所以现在这取决于你如何解释所写的内容。我认为备注部分中的文字暗示这些错误广泛存在,这就是为什么你不应该依赖秒表在多核 CPU 上给出准确的结果(不设置线程亲和性)。网上有很多关于秒表问题的帖子,这些帖子也都应该支持我的说法...... 我不知道在这种情况下 StopWatch 类是否有问题(处理器规格看起来像 Xeon 但不知道它是否是多核的)。但是,我在自己的代码中肯定遇到了 StopWatch 类的问题,所以我建议不要使用它。问题包括偶尔会出现负时间间隔。然而,更常见的是,连续的值显然是不正确的,后面的值小于前面的值。 ...你不喜欢人们只是删除他们被证明是错误的 cmets【参考方案4】:在我的机器上,每次调用 GetItem 大约需要 40 毫秒或 400 纳秒。
我在调试器下跟踪调用,在我的 I7 机器上每个 GetItem 大约有 2000 条指令。这超出了我的预期。
【讨论】:
以上是关于Memory Cache .Net 4.0 性能测试:惊人的结果的主要内容,如果未能解决你的问题,请参考以下文章
#computer architecture#memory1
200 from memory cache / from disk cache / 304 Not Modified 区别
SmartSql = Dapper + MyBatis + Cache(Memory | Redis) + ZooKeeper + R/W Splitting + ......
计算机的Cache和Memory访问时Write-back,Write-through及write allocate的区别。