LRU 缓存和多线程
LRU Caching & Multithreading
我之前已经发了一篇帖子,询问LRU缓存的良好设计(在c++中)。你可以在这里找到问题、答案和一些代码:
更好地理解LRU算法
我现在已经尝试多线程这段代码(使用pthread),并得到了一些真正意想不到的结果。在尝试使用锁之前,我已经创建了一个系统,其中每个线程访问自己的缓存(参见代码)。我在4核处理器上运行这段代码。我试着用1个线程和4个线程运行它。当它在1个线程上运行时,我在缓存中进行100万次查找,在4个线程上,每个线程进行250K次查找。我期望得到一个时间减少与4线程,但得到相反的。1个线程运行2.2秒,4个线程运行超过6秒??我就是搞不懂这个结果。
我的代码有问题吗?这可以解释一下吗(线程管理需要时间)。如果能得到专家的反馈就太好了。谢谢-
我用以下命令编译这段代码:c++ -o cache.cpp -std=c++0x -O3 -lpthread
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <sys/time.h>
#include <list>
#include <cstdlib>
#include <cstdio>
#include <memory>
#include <list>
#include <unordered_map>
#include <stdint.h>
#include <iostream>
typedef uint32_t data_key_t;
using namespace std;
//using namespace std::tr1;
class TileData
{
public:
data_key_t theKey;
float *data;
static const uint32_t tileSize = 32;
static const uint32_t tileDataBlockSize;
TileData(const data_key_t &key) : theKey(key), data(NULL)
{
float *data = new float [tileSize * tileSize * tileSize];
}
~TileData()
{
/* std::cerr << "delete " << theKey << std::endl; */
if (data) delete [] data;
}
};
typedef shared_ptr<TileData> TileDataPtr; // automatic memory management!
TileDataPtr loadDataFromDisk(const data_key_t &theKey)
{
return shared_ptr<TileData>(new TileData(theKey));
}
class CacheLRU
{
public:
list<TileDataPtr> linkedList;
unordered_map<data_key_t, TileDataPtr> hashMap;
CacheLRU() : cacheHit(0), cacheMiss(0) {}
TileDataPtr getData(data_key_t theKey)
{
unordered_map<data_key_t, TileDataPtr>::const_iterator iter = hashMap.find(theKey);
if (iter != hashMap.end()) {
TileDataPtr ret = iter->second;
linkedList.remove(ret);
linkedList.push_front(ret);
++cacheHit;
return ret;
}
else {
++cacheMiss;
TileDataPtr ret = loadDataFromDisk(theKey);
linkedList.push_front(ret);
hashMap.insert(make_pair<data_key_t, TileDataPtr>(theKey, ret));
if (linkedList.size() > MAX_LRU_CACHE_SIZE) {
const TileDataPtr dropMe = linkedList.back();
hashMap.erase(dropMe->theKey);
linkedList.remove(dropMe);
}
return ret;
}
}
static const uint32_t MAX_LRU_CACHE_SIZE = 100;
uint32_t cacheMiss, cacheHit;
};
int numThreads = 1;
void *testCache(void *data)
{
struct timeval tv1, tv2;
// Measuring time before starting the threads...
double t = clock();
printf("Starting thread, lookups %dn", (int)(1000000.f / numThreads));
CacheLRU *cache = new CacheLRU;
for (uint32_t i = 0; i < (int)(1000000.f / numThreads); ++i) {
int key = random() % 300;
TileDataPtr tileDataPtr = cache->getData(key);
}
std::cerr << "Time (sec): " << (clock() - t) / CLOCKS_PER_SEC << std::endl;
delete cache;
}
int main()
{
int i;
pthread_t thr[numThreads];
struct timeval tv1, tv2;
// Measuring time before starting the threads...
gettimeofday(&tv1, NULL);
#if 0
CacheLRU *c1 = new CacheLRU;
(*testCache)(c1);
#else
for (int i = 0; i < numThreads; ++i) {
pthread_create(&thr[i], NULL, testCache, (void*)NULL);
//pthread_detach(thr[i]);
}
for (int i = 0; i < numThreads; ++i) {
pthread_join(thr[i], NULL);
//pthread_detach(thr[i]);
}
#endif
// Measuring time after threads finished...
gettimeofday(&tv2, NULL);
if (tv1.tv_usec > tv2.tv_usec)
{
tv2.tv_sec--;
tv2.tv_usec += 1000000;
}
printf("Result - %ld.%ldn", tv2.tv_sec - tv1.tv_sec,
tv2.tv_usec - tv1.tv_usec);
return 0;
}
非常抱歉,通过不断调试代码,我意识到我犯了一个非常严重的初学者错误,如果你看一下代码:
TileData(const data_key_t &key) : theKey(key), data(NULL)
{
float *data = new float [tileSize * tileSize * tileSize];
}
来自TikeData类,其中数据实际上应该是类的成员变量…所以正确的代码应该是:
class TileData
{
public:
float *data;
TileData(const data_key_t &key) : theKey(key), data(NULL)
{
data = new float [tileSize * tileSize * tileSize];
numAlloc++;
}
};
我很抱歉!这是我过去犯过的一个错误,我认为原型设计是很棒的,但有时它会导致一些愚蠢的错误。我用1和4个线程运行代码,现在确实看到了加速。1个线程耗时约2.3秒,4个线程耗时0.92秒。谢谢你的帮助,很抱歉耽误了你的时间;-)
我还没有一个具体的答案。我能想到几种可能性。一个是testCache()
使用random()
,这几乎肯定是用单个全局互斥量实现的。(因此,所有的线程都在争夺互斥锁,现在在缓存之间进行乒乓比赛。)((假设random()
在您的系统上实际上是线程安全的。))
接下来,testCach()
正在访问unordered_maps
和shared_ptrs
实现的CacheLRU
。特别是unordered_maps
,可能会在下面使用某种全局互斥锁来实现,这会导致所有线程竞争访问权。
要真正诊断这里发生了什么,你应该在testCache()
中做一些更简单的事情。(首先尝试将输入变量的平方根()取250K次(vs. 1M次)。然后尝试线性访问大小为250K(或1M)的C数组。慢慢积累你正在做的复杂的事情。)
另一种可能与pthread_join
有关。pthread_join
直到所有线程完成才返回。所以如果一个比其他的花的时间长,你测量的是最慢的一个。您的计算似乎是平衡的,但也许您的操作系统做了一些意想不到的事情?(比如将多个线程映射到一个核心(可能是因为您有一个超线程处理器?,或者一个线程在运行过程中从一个内核移动到另一个内核(可能是因为操作系统认为它是聪明的,当它不是。)
这将是一个有点"建立它"的答案。我在Fedora 16 Linux系统上运行您的代码,该系统具有4核AMD cpu和16GB RAM。
我可以确认我看到了类似的"线程越多越慢"的行为。我去掉了随机函数,这一点也没有改善。
- 在C++中使用cURL和多线程
- 多线程双缓冲区
- 为什么我的多线程作业队列崩溃
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 试图创建一个多线程程序来查找0-100000000之间的总素数
- 为什么一个向量上的多线程操作很慢
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 全局变量 多读取器 一个写入器多线程安全?
- boost::文件系统::recursive_directory_iterator多线程安全
- 如何阻止TensorFlow的多线程
- 如何在多线程中正确使用unique_ptr进行多态性?
- 并发/多线程:是否可以以这种方式生成相同的输出?
- sigwait() 在多线程程序中不起作用
- 多线程减慢程序速度:无错误共享,无互斥锁,无缓存未命中,无小工作量
- 多线程C :从内存中读取力,绕过缓存
- 缓存高效的多线程合并排序
- 在多线程代码中缓存友好的数组迭代模式
- c++使用' .reserve() '填充' std::vector '作为防止多线程缓存无效和错误共享的一种方
- LRU 缓存和多线程
- 在多线程应用程序中缓存JNI环境