C# 高性能缓存
下面的代码应该缓存最后一次读取。C# 高性能缓存,c#,.net,multithreading,C#,.net,Multithreading,下面的代码应该缓存最后一次读取。LastValueCache是一个可以被多个线程访问的缓存(这就是我使用共享内存的原因)。对我来说,有竞争条件是可以的,但我希望其他线程能够看到更改的LastValueCache class Repository { public Item LastValueCache { get { Thread.MemoryBarrier(); SomeType result =
LastValueCache
是一个可以被多个线程访问的缓存(这就是我使用共享内存的原因)。对我来说,有竞争条件是可以的,但我希望其他线程能够看到更改的LastValueCache
class Repository
{
public Item LastValueCache
{
get
{
Thread.MemoryBarrier();
SomeType result = field;
Thread.MemoryBarrier();
return result;
}
set
{
Thread.MemoryBarrier();
field = value;
Thread.MemoryBarrier();
}
}
public void Save(Item item)
{
SaveToDatabase(item);
Item cached = LastValueCache;
if (cached == null || item.Stamp > cached.Stamp)
{
LastValueCache = item;
}
}
public void Remove(Timestamp stamp)
{
RemoveFromDatabase(item);
Item cached = LastValueCache;
if (cached != null && cached.Stamp == item.Stamp)
{
LastValueCache = null;
}
}
public Item Get(Timestamp stamp)
{
Item cached = LastValueCache;
if (cached != null && cached.Stamp == stamp)
{
return cached;
}
return GetFromDatabase(stamp);
}
}
Repository
对象被许多线程使用。我不想使用锁定,因为它会影响性能,在我的情况下,性能比数据一致性更重要。问题是哪种最小的同步机制可以满足我的需要?可能volatile
或者get
和set
中的单个内存载体
就足够了?volatile
应该足够了,并且对于您的情况来说性能最好。在一些潜在的情况下,volatile
本身不足以确保读取获得最新的值,但在这种情况下,我认为这不是一个重要因素。有关详细信息,请参阅的“volatile关键字”部分。替代用途可能是ReaderWriterLockSlim
类,以便为您这样的读写器场景启用线程同步
ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
get
{
_rw.EnterReadLock();
SomeType result = field;
_rw.ExitReadLock();
return result;
}
set
{
_rw.EnterWriteLock();
field = value;
_rw.ExitWriteLock();
}
这是读写器同步的有效实现。Eric Lippert的这张照片可能会让你感兴趣
另一个有趣的选项可能是ConcurrentExclusiveSchedulerPair
类,该类为任务创建两个TaskScheduler,可将其拆分为“具有读访问权限的任务”(并发)和“具有写访问权限的任务”(独占)
有关这方面的更多信息可以找到。如果这是愚蠢的,你不需要投票否决我。
只要告诉我,我就会删除。
但我并没有遵循这个逻辑
public void Save(Item item)
{
SaveToDatabase(item);
Item cached = LastValueCache;
if (cached == null || item.Stamp > cached.Stamp)
{
LastValueCache = item;
}
}
您担心的是内存毫秒,但在更新缓存之前,您正在等待对数据库的写入。基于公共项的Get stamp是一个密钥 假设db写入为20毫秒
db读数为10毫秒
缓存获取和缓存设置各为2毫秒 公共作废保存(项目)
SaveToDatabase(项目);20毫秒
项目缓存=LastValueCache;2毫秒
如果(cached==null | | item.Stamp>cached.Stamp)1毫秒
LastValueCache=项目;2毫秒 在LastValueCache=项目之前的23毫秒内;对公共项Get(Timestamp stamp)的任何调用都将命中数据库而不是缓存 在LastValueCache=项目之前的23毫秒内;对公共项LastValueCache的任何调用 get将获得一个过期时间长达23毫秒的值。 声明的目标是让其他线程看到LastValueCache,但它们看到的是过时的LastValueCache “删除”也一样。
您将对本可以避免的数据库进行多次访问 你想达到什么目标?
你分析过这个吗 我打赌瓶颈在于对数据库的调用。
数据库调用比锁和内存载体之间的差异长1000倍
public void Save(Item item)
{
// add logic that the prior asynchonous call to SaveToDatabase is complete
// if not wait for it to complete
// LastValueCache will possible be replaced so you need last item in the database
// the time for a lock is not really a factor as it will be faster than the prior update
Item cached = LastValueCache;
if (cached == null || item.Stamp > cached.Stamp)
{
LastValueCache = item;
}
// make the next a task or background so it does not block
SaveToDatabase(item);
}
如果设置LastValueCache=item,甚至可以将逻辑更改为仅等待先前的调用但是您需要以某种方式限制数据库 下一步是缓存最后一个X,并在Item Get(Timestamp stamp)中使用它
数据库是您需要优化的调用
同样,您需要配置文件
之后,逻辑会变得更复杂,但会将数据库调用提供给BlockingCollection。需要确保最后一个X缓存大于BlockingCollections大小。如果不阻塞,等待BC清除。您需要将相同的BC用于insert和delete,以便按顺序处理它们。可以变得足够聪明,只需不插入包含删除的记录。不要一次插入或删除一条记录 我实现了一个为并发工作负载设计的线程安全伪LRU:ConcurrentLru。性能非常接近ConcurrentDictionary,比MemoryCache快约10倍,命中率比传统LRU高。下面的github链接提供了完整的分析 用法如下所示:
int capacity = 666;
var lru = new ConcurrentLru<int, SomeItem>(capacity);
var value = lru.GetOrAdd(1, (k) => new SomeItem(k));
注意,如果没有竞争,锁是一个非常便宜的原语。你的关键部分很小(一打左右的说明)。这将花费大约2个联锁操作。;除此之外,我认为volatile应该起作用。但我不是这方面的专家。你现在使用的双重记忆屏障也应该有效。我不理解你的要求。如果竞争条件正常,则LastValueCache返回的值很容易无效。例如,有人对当前值调用remove,remove看到比当前值旧的缓存值,没有将缓存设置为null,然后boom,LastCacheValue引用的是已删除的项。这对我来说是可以的。但我不希望这种状态永远持续下去。也就是说,当我最终设置
LastValueCache
时,我希望其他线程可以刷新其引用。@Pellared:但这将是永远的(至少,直到下一次使用较新的时间戳进行保存,假设您的时间戳一直在增加)。@Cameron很遗憾,现在我不理解您的意思。时间如下:线程1-RemoveFromDatabase
,线程2-Get
(从缓存中获取删除的值),线程1-LastValueCache=null
;线程2-Get
(现在它从数据库中获取值)。这会慢得多。分配引用是原子的,因此锁定属性只会给我们带来开销。此外,正如我在问题中已经提到的,我不想要任何锁定。以前的实现在方法(ReadLock用于Get,WriteLock用于Save和Delete)周围使用了readerwriterlocksim
,但是速度太慢了。你读过Eric Lippert的文章吗?但如果你认为你的代码是线程安全的,那就没问题了……是的,我是这么认为的。此外,他还回答了我关于它的另一个问题:我更喜欢锁,但是如果你真的需要锁,我会给你锁上
Install-Package BitFaster.Caching