繁体   English   中英

LRU缓存和多线程

[英]LRU Caching & Multithreading

前一段时间我已经发布了一个帖子,询问关于LRU缓存(在C ++中)的良好设计。 您可以在此处找到问题,答案和一些代码:

更好地了解LRU算法

现在,我尝试对这段代码进行多线程处理(使用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 ,该CacheLRUunordered_mapsshared_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM