2009-03-05 11 views
7

Tengo una operación de búsqueda de datos relativamente cara en la que quiero guardar los resultados. Esta operación se llama desde const métodos, más o menos así:Almacenamiento en caché de datos costosos en C++: estadísticas estáticas con función variable vs variables de miembros mutables

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

me gustaría seguir siendo AdjustDataconst, pero yo quiero para almacenar en caché a cabo el factor por lo que sólo lo trae por primera vez. En este momento estoy usando un mutable map<int, double> para almacenar el resultado (el mapa es de key a factor), pero estoy pensando que usar una estática de ámbito funcional podría ser una mejor solución; este factor solo es necesario para esta función, y es irrelevante para el resto de la clase.

¿Le parece una buena forma de hacerlo? ¿Hay mejores opciones? En qué cosas podría pensar, particularmente con respecto a la seguridad de hilos.

Gracias,

Dom

Respuesta

5

Me gustaría envolver la implementación de LongRunningOperationToFetchFactor con algo como esto. Estoy usando cerraduras con el objetivo Boost pero puedes hacer algo similar con otros marcos de bloqueo.

#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; 
} 

Si esta es realmente una operación de larga duración, entonces el costo de bloquear el Mutex debe ser mínimo.

No estaba del todo claro por su pregunta, pero si la función LongRunningOperationToFetchFactor es una función miembro de su clase, entonces quiere que el mapa sea el mapa mutable en esa misma clase. Sin embargo, el único mutex estático para el acceso sigue siendo lo suficientemente rápido.

+0

Puede que no sea necesario bloquear la operación de larga ejecución, solo busque/inserte llamadas en el mapa. –

+0

Creo que tienes razón. Déjame afinarlo más. –

+0

No se garantiza la seguridad de la ejecución de subprocesos al crear un mapa estático, ya que puede duplicar y duplicar el mapa si dos subprocesos llaman a esta función simultáneamente la primera vez que se utiliza. Ver http://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx – bk1e

0

A menos que no entiendo, parece obvio para mí que desea hacer de esto una estática:

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

De esa manera buscar solamente los factores una vez.

+0

Hola Lyndsey - gracias, eso era lo que estaba pensando. He editado mi pregunta un poco para dar forma al problema real. No puedo usar una constante estática en este caso, así que supongo que utilizo un mapa estático y hago algún tipo de bloqueo alrededor de las inserciones del mapa. ¿Eso parece correcto? –

+0

Lo que está diciendo funcionará, pero la solución tiene un olor algo * malo * :) (Tomado del libro Refactoring). ¿Es esto algo que el usuario inicia para muchas 'claves' diferentes que puede ejecutar en un hilo separado y mostrar el resultado cuando está terminado? –

+0

Si lo convierte en estático, todos los objetos de la clase compartirán el valor. ¿Es esto lo que quieres? –

1

Puede usar el singleton pattern (1) con una clase que realiza la operación de larga ejecución y almacena el resultado en caché. Esta instancia podría ser utilizada en las funciones miembro de otras clases. Considere la exclusión mutua para proteger los insertos y las extracciones de la estructura de datos del mapa para la seguridad del hilo. Si el rendimiento de subprocesos múltiples es un gran problema, puede marcar teclas como en progreso para evitar que varios subprocesos puedan calcular la misma clave simultáneamente.

#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) El patrón singleton se puede perder groseramente. Por lo tanto, evite volverse loco si lo está viendo por primera vez.

3

Yo no hacen de este caché un local estático. El mapa mutable es la solución para los resultados del almacenamiento en caché. De lo contrario, hará que su función sea inútil, ya que diferentes objetos de su clase compartirán el mismo caché, ya que el caché estático local es el mismo para todos los objetos. Puede usar la estática local si el resultado no depende del objeto. Pero entonces me preguntaría por qué la función es un miembro no estático de su objeto, si no necesita acceder a ningún estado.

Como dices, debe ser seguro para subprocesos: si diferentes subprocesos pueden llamar a la función de miembro en el mismo objeto, es probable que desees utilizar un mutex.boost::thread es una buena biblioteca para usar.

+1

Entonces la función es un miembro no estático del objeto ya que anteriormente dependía del estado del objeto (el mapa mutable). Con un estático local, puedo hacer que esta función sea estática. –

+0

bien eso es algo diferente :) simplemente quería advertirle sobre esto si LongRunningOperation es una función miembro que depende de su objeto :) uno no puede ser lo suficientemente cuidadoso :) –

Cuestiones relacionadas