C# 正在查字典。折衷是,在应用更新后(在后台线程上完成),需要复制和交换字典,但如果您不经常写入或在初始化过程中只写入一次,那么折衷绝对是值得的
是什么使得单线程环境中的C# 正在查字典。折衷是,在应用更新后(在后台线程上完成),需要复制和交换字典,但如果您不经常写入或在初始化过程中只写入一次,那么折衷绝对是值得的,c#,.net-4.0,task-parallel-library,concurrentdictionary,C#,.net 4.0,Task Parallel Library,Concurrentdictionary,是什么使得单线程环境中的ConcurrentDictionary慢得多 使其在多线程环境中更快所需的机器开销 我的第一直觉是,lock(){}的速度总是比较慢。但显然不是 当没有竞争对手时,锁非常便宜。你可以每秒锁定一百万次,你的CPU甚至不会注意到,只要你是从一个线程执行的。多线程程序中影响性能的是锁的争用。当多个线程激烈竞争同一个锁时,几乎所有线程都必须等待持有锁的幸运线程释放锁。这就是带有细粒度锁定实现的ConcurrentDictionary的亮点所在。您拥有的并发性越多(处理器/内核越
ConcurrentDictionary
慢得多
使其在多线程环境中更快所需的机器开销
我的第一直觉是,lock(){}
的速度总是比较慢。但显然不是
当没有竞争对手时,
锁
非常便宜。你可以每秒锁定一百万次,你的CPU甚至不会注意到,只要你是从一个线程执行的。多线程程序中影响性能的是锁的争用。当多个线程激烈竞争同一个锁时,几乎所有线程都必须等待持有锁的幸运线程释放锁。这就是带有细粒度锁定实现的ConcurrentDictionary
的亮点所在。您拥有的并发性越多(处理器/内核越多),它的光芒就越耀眼。在一个线程中使用ConcurrentDictionary或在一个线程中同步访问都没有意义。当然,这本字典将击败ConcrurrentDictionary
这在很大程度上取决于使用模式和线程数。下面是一个测试,它表明ConcurrentDictionary在线程数增加时优于dictionary和lock
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Run(1, 100000, 10);
Run(10, 100000, 10);
Run(100, 100000, 10);
Run(1000, 100000, 10);
Console.ReadKey();
}
static void Run(int threads, int count, int cycles)
{
Console.WriteLine("");
Console.WriteLine($"Threads: {threads}, items: {count}, cycles:{cycles}");
var semaphore = new SemaphoreSlim(0, threads);
var concurrentDictionary = new ConcurrentDictionary<int, string>();
for (int i = 0; i < threads; i++)
{
Thread t = new Thread(() => Run(concurrentDictionary, count, cycles, semaphore));
t.Start();
}
Thread.Sleep(1000);
var w = Stopwatch.StartNew();
semaphore.Release(threads);
for (int i = 0; i < threads; i++)
semaphore.Wait();
Console.WriteLine($"ConcurrentDictionary: {w.Elapsed}");
var dictionary = new Dictionary<int, string>();
for (int i = 0; i < threads; i++)
{
Thread t = new Thread(() => Run(dictionary, count, cycles, semaphore));
t.Start();
}
Thread.Sleep(1000);
w.Restart();
semaphore.Release(threads);
for (int i = 0; i < threads; i++)
semaphore.Wait();
Console.WriteLine($"Dictionary: {w.Elapsed}");
}
static void Run(ConcurrentDictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
{
semaphore.Wait();
try
{
for (int i = 0; i < cycles; i++)
for (int j = 0; j < elements; j++)
{
var x = dic.GetOrAdd(i, x => x.ToString());
}
}
finally
{
semaphore.Release();
}
}
static void Run(Dictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
{
semaphore.Wait();
try
{
for (int i = 0; i < cycles; i++)
for (int j = 0; j < elements; j++)
lock (dic)
{
if (!dic.TryGetValue(i, out string value))
dic[i] = i.ToString();
}
}
finally
{
semaphore.Release();
}
}
}
}
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用系统诊断;
使用系统线程;
名称空间控制台
{
班级计划
{
静态void Main(字符串[]参数)
{
运行(1,100000,10);
运行(10,100000,10);
运行(100、100000、10);
运行(1000、100000、10);
Console.ReadKey();
}
静态无效运行(int线程、int计数、int周期)
{
控制台。写线(“”);
WriteLine($“Threads:{Threads},items:{count},cycles:{cycles}”);
var信号量=新信号量lim(0,线程);
var concurrentDictionary=新建concurrentDictionary();
对于(int i=0;iRun(concurrentDictionary、count、cycles、信号量));
t、 Start();
}
睡眠(1000);
var w=秒表.StartNew();
信号量释放(线程);
对于(int i=0;iRun(字典、计数、循环、信号量));
t、 Start();
}
睡眠(1000);
w、 重启();
信号量释放(线程);
对于(int i=0;ix.ToString());
}
}
最后
{
semaphore.Release();
}
}
静态无效运行(字典dic、int元素、int循环、信号量LIM信号量)
{
semaphore.Wait();
尝试
{
对于(int i=0;i
线程数:1,项目数:100000,周期数:10
ConcurrentDictionary:00:00.0000499
字典:00:00:00.0000137
线程数:10,项目数:100000,周期数:10
ConcurrentDictionary:00:00.0497413
字典:00:00:00.2638265
线程数:100,项目数:100000,周期数:10
ConcurrentDictionary:00:00.2408781
字典:00:00:02.2257736
线程:1000个,项目:100000个,周期:10个
ConcurrentDictionary:00:00.8196668
字典:00:00:25.5717232首先,您是否在未连接调试器的情况下以发布模式运行测试?(即“无调试运行”)。其次,“每个线程负责自己的链表”是不正确的。如果您想了解这些东西是如何实现的,请从下载库源代码。确保所有操作都是原子操作会带来开销。当操作本身非常简单时(在字典
的情况下,它只设置一个数组值,几乎没有其他内容),相比之下,开销可能会很大。与非平凡操作相比,开销更可能可以忽略不计。@JimMischel这只适用于ConcurrentBag
,这与您提到的其他集合完全不同。例如,ConcurrentQueue
,不能以相同的方式实现,因为这样就不是FIFO了。而ConcurrentStack
也不是后进先出。我想你的推理是,“我可以自己用锁来做这件事,也可以用专家编写的ConcurrentDictionary来做。因为专家们也可以选择使用锁,当然他们的实现
baseline = 00:00:01.2604656
baseline = 00:00:00.3229741
Stopwatch sw = new Stopwatch();
sw.Start();
var d = new ConcurrentDictionary<int, int>();
for (int i = 0; i < 1000000; i++) d[i] = 123;
for (int i = 1000000; i < 2000000; i++) d[i] = 123;
for (int i = 2000000; i < 3000000; i++) d[i] = 123;
sw.Stop();
Console.WriteLine("baseline = " + sw.Elapsed);
sw.Start();
var d2 = new Dictionary<int, int>();
for (int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123;
for (int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123;
for (int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123;
sw.Stop();
Console.WriteLine("baseline = " + sw.Elapsed);
sw.Stop();
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------- |------------:|----------:|----------:|---------:|---------:|---------:|----------:|
| ConcurrentWrite | 1,372.32 us | 12.752 us | 11.304 us | 226.5625 | 89.8438 | 44.9219 | 1398736 B |
| COWWrite | 1,077.39 us | 21.435 us | 31.419 us | 56.6406 | 19.5313 | 11.7188 | 868629 B |
| DictWrite | 347.19 us | 5.875 us | 5.208 us | 124.5117 | 124.5117 | 124.5117 | 673064 B |
| ConcurrentRead | 63.53 us | 0.486 us | 0.431 us | - | - | - | - |
| COWRead | 81.55 us | 0.908 us | 0.805 us | - | - | - | - |
| DictRead | 70.71 us | 0.471 us | 0.393 us | - | - | - | - |
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Run(1, 100000, 10);
Run(10, 100000, 10);
Run(100, 100000, 10);
Run(1000, 100000, 10);
Console.ReadKey();
}
static void Run(int threads, int count, int cycles)
{
Console.WriteLine("");
Console.WriteLine($"Threads: {threads}, items: {count}, cycles:{cycles}");
var semaphore = new SemaphoreSlim(0, threads);
var concurrentDictionary = new ConcurrentDictionary<int, string>();
for (int i = 0; i < threads; i++)
{
Thread t = new Thread(() => Run(concurrentDictionary, count, cycles, semaphore));
t.Start();
}
Thread.Sleep(1000);
var w = Stopwatch.StartNew();
semaphore.Release(threads);
for (int i = 0; i < threads; i++)
semaphore.Wait();
Console.WriteLine($"ConcurrentDictionary: {w.Elapsed}");
var dictionary = new Dictionary<int, string>();
for (int i = 0; i < threads; i++)
{
Thread t = new Thread(() => Run(dictionary, count, cycles, semaphore));
t.Start();
}
Thread.Sleep(1000);
w.Restart();
semaphore.Release(threads);
for (int i = 0; i < threads; i++)
semaphore.Wait();
Console.WriteLine($"Dictionary: {w.Elapsed}");
}
static void Run(ConcurrentDictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
{
semaphore.Wait();
try
{
for (int i = 0; i < cycles; i++)
for (int j = 0; j < elements; j++)
{
var x = dic.GetOrAdd(i, x => x.ToString());
}
}
finally
{
semaphore.Release();
}
}
static void Run(Dictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
{
semaphore.Wait();
try
{
for (int i = 0; i < cycles; i++)
for (int j = 0; j < elements; j++)
lock (dic)
{
if (!dic.TryGetValue(i, out string value))
dic[i] = i.ToString();
}
}
finally
{
semaphore.Release();
}
}
}
}