1

LRUキャッシング(C ++)の優れた設計について質問するために、私はすでにしばらく前に投稿しました。あなたはそこに質問、答えといくつかのコードを見つけることができます:

LRUアルゴリズムをよりよく理解する

私は今、このコードを(pthreadを使用して)マルチスレッド化しようとしましたが、いくつかの本当に予期しない結果が得られました。ロックを使用する前に、各スレッドが独自のキャッシュにアクセスするシステムを作成しました(コードを参照)。このコードは4コアプロセッサで実行します。1スレッドと4スレッドで実行してみました。1つのスレッドで実行する場合、キャッシュで100万回のルックアップを実行し、4つのスレッドで各スレッドが25万回のルックアップを実行します。私は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;
}
4

3 に答える 3

1

何千もの謝罪、コードのデバッグを続けることによって、そのコードを見ると、私は本当に悪い初心者の間違いを犯したことに気づきました。

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秒かかります。あなたの助けに感謝します、そして私があなたにあなたの時間を失わせたならば申し訳ありません;-)

于 2013-03-26T14:09:24.490 に答える
0

具体的な答えはまだありません。私はいくつかの可能性を考えることができます。1つは、testCache()を使用していることですrandom()。これは、ほぼ確実に単一のグローバルミューテックスで実装されています。(したがって、すべてのスレッドがミューテックスをめぐって競合しており、ミューテックスは現在、キャッシュ間でピンポンしています。)((これrandom()は、システム上で実際にスレッドセーフであると想定しています。))

次に、とで実装されているにtestCach()アクセスします。特に、は、すべてのスレッドがアクセスを競合する原因となる、ある種のグローバルミューテックスの下に実装されている可能性があります。CacheLRUunordered_mapsshared_ptrsunordered_maps

ここで何が起こっているのかを実際に診断するには、の内部でもっと簡単なことを行う必要がありますtestCache()。(最初に、入力変数のsqrt()を250K回(vs. 1M回)取得してみてください。次に、サイズ250K(または1M)のC配列に線形にアクセスしてみてください。現在実行している複雑なことをゆっくりと構築してください。)

別の可能性は。と関係がありpthread_joinます。 pthread_joinすべてのスレッドが完了するまで戻りません。したがって、1つが他よりも時間がかかっている場合は、最も遅いものを測定しています。ここでの計算はバランスが取れているように見えますが、おそらくOSが予期しないことをしているのでしょうか。(複数のスレッドを1つのコアにマッピングするようなものです(おそらくハイパースレッドプロセッサを使用しているためですか?または、実行中に1つのスレッドが1つのコアから別のコアに移動しているためです(OSがそうでないときにスマートであると見なしているため)。 )。

于 2013-03-26T11:38:06.187 に答える
0

これは少し「構築する」答えになります。4コアのAMDCPUと16GBのRAMを搭載したFedora16Linuxシステムでコードを実行しています。

同様の「スレッド数が多いほど遅くなる」動作が見られることを確認できます。ランダム関数を削除しましたが、まったく改善されません。

他にも小さな変更を加えます。

于 2013-03-26T13:22:31.520 に答える