论坛首页 综合技术论坛

LRU缓存的实现

浏览 15059 次
精华帖 (0) :: 良好帖 (3) :: 新手帖 (13) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-12-09   最后修改:2009-12-09
火星来客 写道
mikeandmore 写道
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-

于是
我觉得lz的实现有点问题。。。
readlock和readlock互溶

于是会同时发生get,这估计也是lz想要的。
但是看看hashedlinklist的代码
第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。

这个也是我用synchronized的原因,get操作会修改链表,用ReentrantLock没问题,但是要用同一把锁做get和put,读写锁用在这里不合适

嗯。对。
而且我觉得这个东西如果用spinlock会好一些。。这么短操作不要陷入内核...
0 请登录后投票
   发表时间:2009-12-09  
mikeandmore 写道
火星来客 写道
mikeandmore 写道
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-

于是
我觉得lz的实现有点问题。。。
readlock和readlock互溶

于是会同时发生get,这估计也是lz想要的。
但是看看hashedlinklist的代码
第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。

这个也是我用synchronized的原因,get操作会修改链表,用ReentrantLock没问题,但是要用同一把锁做get和put,读写锁用在这里不合适

嗯。对。
而且我觉得这个东西如果用spinlock会好一些。。这么短操作不要陷入内核...

250w/s的操作我想对于一个cache来说应该没有问题了,只是在我的测试中cpu上升到95%,应该是内核互斥命令造成的。不知道用spinlock会不会能降低cpu的消耗
0 请登录后投票
   发表时间:2009-12-09  
火星来客 写道
mikeandmore 写道
火星来客 写道
mikeandmore 写道
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-

于是
我觉得lz的实现有点问题。。。
readlock和readlock互溶

于是会同时发生get,这估计也是lz想要的。
但是看看hashedlinklist的代码
第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。

这个也是我用synchronized的原因,get操作会修改链表,用ReentrantLock没问题,但是要用同一把锁做get和put,读写锁用在这里不合适

嗯。对。
而且我觉得这个东西如果用spinlock会好一些。。这么短操作不要陷入内核...

250w/s的操作我想对于一个cache来说应该没有问题了,只是在我的测试中cpu上升到95%,应该是内核互斥命令造成的。不知道用spinlock会不会能降低cpu的消耗

理论上只会升高。不过cache这个东西本身就是越快越好的。
0 请登录后投票
   发表时间:2009-12-09   最后修改:2009-12-09
mikeandmore 写道

理论上只会升高。不过cache这个东西本身就是越快越好的。

我认为是够用就好,

比如说我做一个cache app,每秒可以接受30w次socket请求,那我这个cache再快也没有用,在这种场景下,我们可以考虑如何降低cpu的消耗,而不是一味的追求快。

总之看场景吧,在速度和cpu的负载之间取一个平衡点
0 请登录后投票
   发表时间:2009-12-09  
火星来客 写道
mikeandmore 写道

理论上只会升高。不过cache这个东西本身就是越快越好的。

我认为是够用就好,

比如说我做一个cache app,每秒可以接受30w次socket请求,那我这个cache再快也没有用,在这种场景下,我们可以考虑如何降低cpu的消耗,而不是一味的追求快。

总之看场景吧,在速度和cpu的负载之间取一个平衡点


嗯,我不是做这个场景的。嘿嘿。。。
要是数据库的话,这块东西必然是越快越好。。。因为io操作远远比request的数量多。

anyway, tuning is tricky.
0 请登录后投票
   发表时间:2009-12-09  
你们俩的回复速度惊人。
单纯从缓存角度,synchronized是最好的,但是考虑并发读写上的不同逻辑,当一个线程put时,另一个线程是无法get的。
同一个线程重入get的间隔基本可以保证链表重排完成,而且两次进入该锁的可能性并不高。
技术上存在你们说的问题,但实际上并不太会发生。因此,我不打算做改变。
0 请登录后投票
   发表时间:2009-12-09  
marshan 写道
你们俩的回复速度惊人。
单纯从缓存角度,synchronized是最好的,但是考虑并发读写上的不同逻辑,当一个线程put时,另一个线程是无法get的。
同一个线程重入get的间隔基本可以保证链表重排完成,而且两次进入该锁的可能性并不高。
技术上存在你们说的问题,但实际上并不太会发生。因此,我不打算做改变。


不会发生吗?事实上发生的几率相当大,特别是在高并发并且缓存数据急剧增加的情况下。我们的系统吃过苦头,因为这个问题OOM。这本身是不安全的代码,拿出来误导人是很不应该的。
0 请登录后投票
   发表时间:2009-12-09  
marshan 写道

OK,感谢二位!如果可能,请给出重入读锁的失败例子。


我想构建并发测试程序是一项基本技能,比如我随手写的一个并发测试:

package lru;

import java.util.concurrent.CyclicBarrier;

public class LRUMapConcurrentTest {
	static int limit = 1000;
	static int count = 100000;
	static int threadCount = 100;
	private final OjadbMap<Integer, Integer> map = new OjadbMap<Integer, Integer>(
			1000);

	class TestThread implements Runnable {

		private final CyclicBarrier barrier;
		private final int beginIndex;

		public TestThread(int beginIndex, CyclicBarrier barrier) {
			super();
			this.beginIndex = beginIndex;
			this.barrier = barrier;
		}

		public void run() {
			try {
				barrier.await();
				for (int i = beginIndex; i < beginIndex + count; i++)
					map.put(i, i);
				for (int i = beginIndex; i < beginIndex + count; i++)
					map.get(i);
				barrier.await();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}

		}
	}

	public void concurrentTest() {
		CyclicBarrier barrier = new CyclicBarrier(threadCount + 1);
		for (int i = 0; i < threadCount; i++) {
			new Thread(new TestThread(i, barrier)).start();
		}
		try {
			barrier.await();
			barrier.await();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		System.out.println("Current map size:" + map.size());
	}

	public static void main(String[] args) {
		new LRUMapConcurrentTest().concurrentTest();
	}
}
 

    尽管讲LRU的maxCapacity设置为1000,然而最后的结果却是maxCapacity的十倍,最大限制形同虚设,如果你将循环次数count设置为Integer.MAX_VALUE,那么很容易跑出个内存溢出。既然这样的代码是放在google code上,我想基于对用户负责的态度,也不应该让这样明显的并发隐患存在于你的系统中吧。可能口气重了点,我想表达的意思是对于并发,不要用“可能、我认为不会”这样的字眼,事实上看起来很小的错误在高并发下都将被放大并显现。

 

0 请登录后投票
   发表时间:2009-12-10  
marshan 写道
你们俩的回复速度惊人。
单纯从缓存角度,synchronized是最好的,但是考虑并发读写上的不同逻辑,当一个线程put时,另一个线程是无法get的。
同一个线程重入get的间隔基本可以保证链表重排完成,而且两次进入该锁的可能性并不高。
技术上存在你们说的问题,但实际上并不太会发生。因此,我不打算做改变。

1 这个东西很难测到的
2 这个东西还是可以测到的。。。见上面的case
0 请登录后投票
   发表时间:2009-12-10  
dennis_zane 写道

    尽管讲LRU的maxCapacity设置为1000,然而最后的结果却是maxCapacity的十倍,最大限制形同虚设,如果你将循环次数count设置为Integer.MAX_VALUE,那么很容易跑出个内存溢出。既然这样的代码是放在google code上,我想基于对用户负责的态度,也不应该让这样明显的并发隐患存在于你的系统中吧。可能口气重了点,我想表达的意思是对于并发,不要用“可能、我认为不会”这样的字眼,事实上看起来很小的错误在高并发下都将被放大并显现。

测试结果:

Current map size:1000
我没太懂你要测什么,起初我们在讨论get时,readLock的问题。

 

你的表达很中肯,我有的只有敬重和感谢!而且,我的项目或将考虑你们的建议,进行修改。

0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics