2009-03-05 4 views
7

Ich habe eine relativ teure Daten holen Operation, die ich die Ergebnisse von cachen wollen. Dieser Vorgang wird von const Methoden aufgerufen, in etwa wie folgt aus:Caching teure Daten in C++ - Funktionsbereich Statik vs veränderbare Member-Variablen

double AdjustData(double d, int key) const { 
    double factor = LongRunningOperationToFetchFactor(key); 
    return factor * d; 
} 

I AdjustDataconst bleiben möchte, aber ich möchte den Faktor zwischenzuspeichern, so das erste Mal hole ich es nur. Zur Zeit verwende ich eine mutable map<int, double>, um das Ergebnis zu speichern (die Karte ist von key bis factor), aber ich denke, mit einem Funktionsumfang statisch könnte eine bessere Lösung sein - dieser Faktor wird nur von dieser Funktion benötigt, und ist irrelevant für den Rest der Klasse.

Scheint das ein guter Weg zu gehen? Gibt es bessere Möglichkeiten? Was soll ich denken, besonders im Hinblick auf die Fadensicherheit?

Danke,

Dom

Antwort

5

Ich würde die Implementierung von LongRunningOperationToFetchFactor mit etwas wie diesem umhüllen. Ich benutze Boost Scoped Locks, aber Sie können so etwas mit anderen Sperrgerüsten ähnlich.

#include <boost/thread/thread.hpp> 
#include <boost/thread/mutex.hpp> 
#include <map> 

using namespace std; 

static boost::mutex myMutex; 
static map<int,double> results; 

double CachedLongRunningOperationToFetchFactor(int key) 
{ 

    { 
     boost::mutex::scoped_lock lock(myMutex); 

     map<int,double>::iterator iter = results.find(key); 
     if (iter != results.end()) 
     { 
      return (*iter).second; 
     } 
    } 
    // not in the Cache calculate it 
    result = LongRunningOperationToFetchFactor(key); 
    { 
     // we need to lock the map again 
     boost::mutex::scoped_lock lock(myMutex); 
     // it could be that another thread already calculated the result but 
     // map assignment does not care. 
     results[key] = result; 
    } 
    return result; 
} 

Wenn dies wirklich ein langwieriger Vorgang ist, sollten die Kosten für die Verriegelung des Mutex minimal sein.

Es war nicht ganz klar von Ihnen Frage, aber wenn die Funktion LongRunningOperationToFetchFactor eine Memberfunktion Ihrer Klasse ist, dann möchten Sie die Karte die veränderbare Karte in der gleichen Klasse sein. Ich single statischen Mutex für den Zugriff ist aber immer noch schnell genug.

+0

Es ist möglicherweise nicht notwendig, den lang andauernden Vorgang zu sperren, sondern nur Anrufe in der Karte zu suchen/einzufügen. –

+0

Ich denke du hast Recht. Lass es mich weiter tunen. –

+0

Das Erstellen der statischen Zuordnung, während keine Sperre gespeichert wird, ist nicht garantiert threadsicher, da Sie die Zuordnung möglicherweise doppelkonstruieren und doppelzerstören, wenn zwei Threads diese Funktion bei der ersten Verwendung gleichzeitig aufrufen. Siehe http://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx – bk1e

0

Es sei denn, ich verstehe nicht, es scheint mir offensichtlich, dass Sie dies eine statische machen wollen:

double AdjustData(double d) const { 
    static const double kAdjustFactor = LongRunningOperationToFetchFactor(); 
    return kAdjustFactor * d; 
} 

diese Weise können Sie nur holen den Faktor Einmal.

+0

Hallo Lyndsey - danke, das war, was ich dachte. Ich habe meine Frage ein wenig bearbeitet, um das eigentliche Problem zu konkretisieren. Ich kann in diesem Fall kein statisches const verwenden, also schätze ich, dass ich eine statische Karte verwende und irgendeine Art von Locking um Karteneinsätze mache. Scheint das richtig? –

+0

Wovon du sprichst, wird funktionieren, aber die Lösung hat einen etwas * schlechten * Geruch :) (Aus dem Refactoring Buch entnommen). Ist das etwas, das der Benutzer für viele verschiedene "Schlüssel" einleitet, die Sie in einem separaten Thread ausführen und das Ergebnis anzeigen können, wenn es fertig ist? –

+0

Wenn Sie es statisch machen, teilen alle Objekte der Klasse den Wert. Ist das was du willst? –

1

Sie können die singleton pattern (1) mit einer Klasse verwenden, die die lang andauernde Operation ausführt und das Ergebnis zwischenspeichert. Diese Instanz könnte dann in Konstantenfunktionen anderer Klassen verwendet werden. Ziehen Sie einen gegenseitigen Ausschluss in Betracht, um Einfügungen und Extraktionen aus der Kartendatenstruktur für die Threadsicherheit zu schützen. Wenn die Multithread-Leistung ein großes Problem darstellt, können Sie Schlüssel im Verlauf markieren, um zu verhindern, dass mehrere Threads denselben Schlüssel gleichzeitig berechnen.

#include <cstdlib> 
#include <iostream> 
#include <map> 

using namespace std; 

class FactorMaker { 
    map<int, double> cache; 

    double longRunningFetch(int key) 
    { 
     const double factor = static_cast<double> (rand())/RAND_MAX; 
     cout << "calculating factor for key " << key << endl; 
     // lock 
     cache.insert(make_pair(key, factor)); 
     // unlock 
     return factor; 
    } 

public: 
    double getFactor(int key) { 
     // lock 
     map<int, double>::iterator it = cache.find(key); 
     // unlock 
     return (cache.end() == it) ? longRunningFetch(key) : it->second; 
    } 
}; 

FactorMaker & getFactorMaker() 
{ 
    static FactorMaker instance; 
    return instance; 
} 

class UsesFactors { 
public: 
    UsesFactors() {} 

    void printFactor(int key) const 
    { 
     cout << getFactorMaker().getFactor(key) << endl; 
    } 
}; 

int main(int argc, char *argv[]) 
{ 
    const UsesFactors obj; 

    for (int i = 0; i < 10; ++i) 
     obj.printFactor(i); 

    for (int i = 0; i < 10; ++i) 
     obj.printFactor(i); 

    return EXIT_SUCCESS; 
} 

(1) Die Singletonmuster grob übersehen werden. Also, bitte unterlasse es nicht damit, wenn du es zum ersten Mal siehst.

3

Ich würde nicht machen diesen Cache eine lokale statische. Die veränderbare Map ist die Lösung für das Zwischenspeichern von Ergebnissen. Andernfalls wird Ihre Funktion unbrauchbar, da sich verschiedene Objekte Ihrer Klasse denselben Cache teilen, da der lokale statische Cache für alle Objekte gleich ist. Sie können die lokale Statistik verwenden, wenn das Ergebnis nicht vom Objekt abhängt. Aber dann würde ich mich fragen, warum die Funktion ein nicht-statisches Mitglied Ihres Objekts ist, wenn es nicht auf einen Zustand davon zugreifen muss.

Wie Sie sagen, sollte es threadsicher sein - wenn verschiedene Threads die Memberfunktion für dasselbe Objekt aufrufen können, möchten Sie wahrscheinlich einen Mutex verwenden.boost::thread ist eine gute Bibliothek zu verwenden.

+1

Die Funktion ist also ein nicht-statisches Element des Objekts, da es zuvor vom Zustand des Objekts (der veränderlichen Map) abhängig war. Mit einer lokalen Statik kann ich dann diese Funktion statisch machen. –

+0

in Ordnung, das ist eine andere Sache dann :) wollte Sie nur davor warnen, wenn die LongRunningOperation eine Mitgliedsfunktion ist, die von Ihrem Objekt abhängt :) man kann nicht vorsichtig genug sein :) –