[英]LRU Caching & Multithreading
前一段时间我已经发布了一个帖子,询问关于LRU缓存(在C ++中)的良好设计。 您可以在此处找到问题,答案和一些代码:
现在,我尝试对这段代码进行多线程处理(使用pthread),并带来了一些非常出乎意料的结果。 在甚至尝试使用锁定之前,我已经创建了一个系统,其中每个线程都访问其自己的缓存(请参见代码)。 我在4核处理器上运行此代码。 我试图用1个线程和4个线程来运行它。 当它在1个线程上运行时,我在高速缓存中进行1百万次查找,在4个线程上,每个线程进行250K查找。 我原本希望减少4个线程的时间,但结果却相反。 1个线程在2.2秒内运行,4个线程在6秒以上运行?? 我只是无法理解这个结果。
我的代码有问题吗? 可以用某种方式解释这个问题(线程管理需要时间)。 得到专家的反馈将是很棒的。 非常感谢 -
我用以下代码编译此代码:c ++ -o cache 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 %d\n", (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.%ld\n", 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()
,几乎可以肯定它是通过单个全局互斥量实现的。 (因此,您的所有线程都在争夺互斥量,而互斥量现在正在缓存之间进行ping-poning。)((假设random()
在您的系统上实际上是线程安全的。))
接下来, testCach()
访问CacheLRU
,该CacheLRU
由unordered_maps
和shared_ptrs
。 特别是unordered_maps
可能在下面使用某种全局互斥量实现,这导致您的所有线程竞争访问权限。
要真正诊断出这里发生了什么,您应该在testCache()
内部做一些简单的testCache()
。 (首先尝试仅将输入变量的sqrt()进行250K次(相对于1M次)。然后尝试线性访问大小为250K(或1M)的C数组。慢慢地构建当前正在执行的复杂操作。)
另一种可能性与pthread_join
。 在所有线程完成之前, pthread_join
不会返回。 因此,如果一个人花费的时间比其他人长,那么您正在衡量的是最慢的人。 您的计算在这里看起来很均衡,但是也许您的OS正在做一些意外的事情? (例如,将多个线程映射到一个内核(可能是因为您拥有超线程处理器?或者一个线程正在运行的过程中从一个内核转移到另一个内核(也许是因为操作系统认为它在不是内核时很聪明)。 )
这将是一个“建立它”的答案。 我正在具有4核AMD cpu和16GB RAM的Fedora 16 Linux系统上运行您的代码。
我可以确认我看到了类似的“慢于更多线程”的行为。 我删除了随机函数,该函数根本无法改善。
我将进行其他一些小的更改。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.