Almacenamiento en caché de datos caros en C ++: estadísticas de ámbito de función frente a variables de miembro mutable

StackOverflow https://stackoverflow.com/questions/614875

Pregunta

Tengo una operación de recuperación de datos relativamente costosa de la que quiero almacenar en caché los resultados. Esta operación se llama desde const métodos, más o menos así:

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

Me gustaría que AdjustData permanezca mutable map<int, double>, pero quiero almacenar en caché el factor, así que solo lo obtengo la primera vez. En este momento estoy usando un key para almacenar el resultado (el mapa es de factor a <=>), pero creo que usar una estática con ámbito de función podría ser una mejor solución: este factor solo es necesario por esta función, y es irrelevante para el resto de la clase.

¿Parece eso un buen camino a seguir? ¿Hay mejores opciones? En qué cosas podría pensar, particularmente con respecto a la seguridad de los hilos.

Gracias,

Dom

¿Fue útil?

Solución

Yo envolvería la implementación de LongRunningOperationToFetchFactor con algo como esto. Estoy usando bloqueos con alcance 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 debería ser mínimo.

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

Otros consejos

no haría que esta caché sea una estática local. El mapa mutable es la solución para el almacenamiento en caché de resultados. De lo contrario, hará que su función sea inútil, ya que diferentes objetos de su clase compartirán la misma caché, ya que la caché estática local es la misma para todos los objetos. Sin embargo, puede usar la estática local si el resultado no depende del objeto. Pero luego 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 del mismo.

Como usted dice, debe ser seguro para subprocesos: si diferentes subprocesos pueden llamar a la función miembro en el mismo objeto, probablemente desee usar un mutex. boost::thread es una buena biblioteca para usar.

Puede usar el patrón singleton (1) a con una clase que realiza el largo -ejecuta la operación y almacena en caché el resultado. Esta instancia se podría usar en funciones miembro const de otras clases. Considere la exclusión mutua para proteger las inserciones y extracciones de la estructura de datos del mapa para la seguridad del subproceso. Si el rendimiento de subprocesos múltiples es un gran problema, puede marcar las teclas como en progreso para evitar que varios subprocesos calculen 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) Se puede pasar por alto el patrón singleton. Por lo tanto, evite volverse loco con él si lo está viendo por primera vez.

A menos que no entienda, me parece obvio que desea hacer esto estático:

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

De esa forma solo obtiene el factor una vez.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top