C# Memory Cache .Net 4.0 性能测试:惊人的结果
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11729023/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Memory Cache .Net 4.0 performance test : astonishing result
提问by lsalamon
This performance test is wrong or the system cache is working with exceptional performance?
这个性能测试是错误的还是系统缓存工作性能异常?
This is my result :
[13] number of interactions 100000 : 63 milliseconds
[14] number of interactions 100000 : 139 milliseconds
[12] number of interactions 100000 : 47 milliseconds
[15] number of interactions 100000 : 44 milliseconds
End of test.
这是我的结果:
[13] 交互次数 100000:63 毫秒
[14] 交互次数 100000:139 毫秒
[12] 交互次数 100000:47 毫秒
[15] 交互次数 100000:44 毫秒
结束
Hardware :
x86 Family 6 Model 23 Stepping GenuineIntel ~2992 Mhz 3.327 MB, 5.1.2600 Service Pack 3
硬件: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();
}
}
}
回答by IvoTops
Looks good. Although timings below a second are not very reliable; you might have run into a garbage collect, your PC might do something else for a short while, first time JIT compile etc.
看起来挺好的。虽然低于一秒的时间不是很可靠;您可能遇到了垃圾收集,您的 PC 可能会在短时间内做其他事情,第一次 JIT 编译等。
So increase the count. Which should also make the results for each thread end up closer together.
所以增加计数。这也应该使每个线程的结果更接近。
Some test I did last week made it to eight million iterations per second (doing not a lot, but still) singlethreaded. So yes, PC's are fast these days ;-)
我上周做的一些测试使它达到每秒 800 万次迭代(做的不多,但仍然是)单线程。所以是的,这些天 PC 的速度很快 ;-)
回答by Marko
The problem is the StopWatch class which can't be used on multi-core machines! (I'm assuming you have a multi-core CPU) Something to do with how the BIOS handles that counter when a thread moves from one core to another (even a single threaded application jumps cores!).
问题是不能在多核机器上使用的 StopWatch 类!(我假设你有一个多核 CPU)当一个线程从一个内核移动到另一个内核时 BIOS 如何处理该计数器(即使是单线程应用程序跳转内核!)。
Edit:
Check out - http://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx- specifically the remarks section.
There is a stackoverflow post as well - Multicore and thread aware .Net stopwatch?.
End Edit
编辑:
查看 - http://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx- 特别是备注部分。还有一个 stackoverflow 帖子 -多核和线程感知 .Net 秒表?.
结束编辑
I have searched high and low for the best method to measure app performance and the most reliable I have come up with is DateTime.UtcNow. Get the start and end time and then take the difference between them. You have to loop your code enough to get past the low precision, but no other method I have come across gives more reliableaccuracy.
我一直在寻找衡量应用程序性能的最佳方法,我想出的最可靠的方法是 DateTime.UtcNow。获取开始时间和结束时间,然后计算它们之间的差异。你必须循环你的代码以克服低精度,但我遇到的其他方法都没有提供更可靠的准确性。
回答by Feng Yuan
On my machine, it's about 40 ms, or 400 ns per GetItem call.
在我的机器上,每次 GetItem 调用大约需要 40 毫秒或 400 纳秒。
I traced the calls under debugger, it's about 2000 instructions per GetItem on my I7 machine. That is more than I would expect.
我在调试器下跟踪了调用,在我的 I7 机器上,每个 GetItem 大约有 2000 条指令。这超出了我的预期。
回答by Loudenvier
I've just searched the net for information on MemoryCacheperformance and stumbled upon this SO question. I asked myself why a proper benchmark library wasn't used, so I've ended up cooking my own benchmark by being very lazy (as all good programmers should :-) and used the incredible BenchmarkDotNetlibrary to check how well (or not) this class behaves.
我刚刚在网上搜索了有关MemoryCache性能的信息,并偶然发现了这个 SO 问题。我问自己为什么没有使用合适的基准测试库,所以我非常懒惰(所有优秀的程序员都应该这样做:-)并使用了令人难以置信的BenchmarkDotNet库来检查性能如何(或没有)这个类的行为。
First the results
先上结果
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 |
Now some explaining...
现在一些解释...
I was mostly interested in seeing how fast MemoryCachewould compare to hashed lists (Dictionary, Hashset...) with thousands of entries and also to a worst case linear search over such "long" list. So I've added some additional tests and realized that while MemoryCacheis not as fast as the simple or concurrent lists, the speed still lies at the nanosecond scale. Not even a single millisecond is taken to retrieve an item in a 30,000 long list of cached items.
我最感兴趣的是看看与具有数千个条目的MemoryCache散列列表 ( Dictionary, Hashset...)相比有多快,以及在如此“长”的列表上进行最坏情况的线性搜索。所以我添加了一些额外的测试,并意识到虽然MemoryCache没有简单列表或并发列表那么快,但速度仍然处于纳秒级。在 30,000 长的缓存项目列表中检索项目甚至不需要一毫秒。
To be fair MemoryCachedoes a LOT more than those simple lists as it must control concurrency, item expiration/eviction, etc. I believe it is fast enough for all kinds of workloads, but if you don't need its added features, like eviction policies, you should better stick with the simpler hashed lists implementations.
公平地说,MemoryCache它比那些简单的列表要多得多,因为它必须控制并发性、项目过期/驱逐等。我相信它对于各种工作负载来说都足够快,但是如果您不需要它的附加功能,例如驱逐策略,你最好坚持使用更简单的哈希列表实现。
On the other hand, since it's an order a magnitude "slower" than a hash lookup, there may be room for improvement. I guess the designers thought it is just good enough as it is, and who am I to disagree with the DOTNET engineers? :-)
另一方面,由于它比哈希查找“慢”一个数量级,因此可能有改进的空间。我猜设计师认为它已经足够好了,我有什么资格反对 DOTNET 工程师?:-)
Here is the source code for the benchmark program:
这是基准程序的源代码:
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);
}
}
}

