Кэшируемые или предварительно вычисляемые неизменяемые функции в C # / C ++ - PullRequest
3 голосов
/ 07 мая 2009

Под «неизменяемой функцией» или «неизменяемым методом» я подразумеваю функцию, результат которой никогда не изменится, если вы дадите ей одинаковые аргументы.

Мне было бы интересно узнать, знает ли кто-нибудь более общее или менее подробное решение, когда вы хотите кэшировать предварительно вычисленные значения неизменяемой функции.

Позвольте мне объяснить, что я имею в виду, на простом примере:

//Let's assume that ComputeStuff() returns a widely used value 
//and that 
//1. It is immutable (it will always return the same result)
//2. its performance is critical, and it cannot be accepted to compute
//   the result at each call, because the computation is too slow
//I show here a way to solve the problem, based on a cached result.
//(this example works in a case of a method with no arguments. 
// A hash would be required in order to store multiple precomputed results 
//depending upon the arguments)
private string mComputeStuff_Cached = null;
public string ComputeStuff()
{
  if (mComputeStuff_Cached != null)
    return mComputeStuff_Cached ;

  string result;
  //
  // ...
  //Do lots of cpu intensive computation in order to compute "result" 
  //or whatever you want to compute
  //(for example the hash of a long file)
  //...
  //

  mComputeStuff_Cached  = result;
  return mComputeStuff_Cached ;
}

Примечания:
- Я добавил тег C ++, так как решение в C ++ меня также заинтересовало
- Понятие «неизменяемые функции» является общим для разработчиков баз данных, поскольку функцию можно определить как «неизменяемую» или «неизменяемую в транзакции» (это хороший способ повысить производительность запросов).

Заранее спасибо

Ответы [ 8 ]

2 голосов
/ 07 мая 2009

" Memoization " может быть полезным термином здесь. Есть несколько библиотек памятки (я могу поклясться, что была одна в boost , но я не могу найти ее в данный момент). Если вы выполните поиск в Интернете по запросу «memoize» или «memoization», и ваш выбранный язык покажет несколько совпадений.

Вот замечательная статья в Wikibooks: Оптимизация C ++ / Общие методы оптимизации / Заметки

1 голос
/ 06 января 2015

вы можете попробовать что-то вроде этого:

#include <functional>
#include <type_traits>
#include <map>
#include <tuple>

//requires c++14
auto add_function_cache = [](auto fun) {
    using fun_type = decltype(fun);
    return ([=](auto... run_args){
        using fun_return_type = std::result_of_t<fun_type(decltype(run_args)...)>;
        static std::map<
            std::tuple<decltype(run_args)...>,
            fun_return_type
        > result_cache;
        std::tuple<decltype(run_args)...> tuple(run_args...);
        if (result_cache.find(tuple) == result_cache.end()) {
            fun_return_type rv = fun(run_args...);
            result_cache[tuple] = rv; 
            return rv; 
        }   
        else {
            return result_cache[tuple];
        }   
    }); 
};

template <typename R, class... Args> auto
add_function_cache_old(std::function<R(Args...)> fun)
-> std::function<R(Args...)>
{
    std::map<std::tuple<Args...>, R> cache;
    return [=](Args... args) mutable  {
        std::tuple<Args...> t(args...);
        if (cache.find(t) == cache.end()) {
            R rv = fun(args...);
            cache[t] = rv; 
            return rv; 
        }   
        else {
            return cache[t];
        }   
    };  
};

А затем используйте его следующим образом:

//function_cache - usage
auto fib_cache = add_function_cache(&fib);

//function_cache_old - usage
function<decltype(fib)> fib_fn = &fib;
auto fib_cache_old = add_function_cache_old(fib_fn);

fib_cache(10);
fib_cache(10);

Идея состоит в том, чтобы создать функцию, которая принимает функцию (забаву) в качестве аргумента и возвращает другую функция. Возвращенная функция оборачивает забаву и предоставляет входные параметры формы карты (run_args) для результатов. Так что он имеет ту же сигнатуру, что и веселье, устает искать результат для заданных параметров (run_args) на карте (кеш). Затем он возвращает кэшированное значение или результат, вычисленный с помощью fun. Очевидно, что результат веселья должен быть добавлен в кэш в случае неудачного поиска.

Привет

Jan

1 голос
/ 07 мая 2009

Что ж, использование такого делегата, как Func<T>, может сделать его более пригодным для повторного использования, не требуя полиморфизма / наследования - но в C # нет ничего более "встроенного":

using System;
static class Program {
    static void Main() {
        var func = CachedFunc.Create(() => int.Parse(Console.ReadLine()));

        Console.WriteLine(func.Value);
        Console.WriteLine(func.Value);
    }
}
static class CachedFunc {
    public static CachedFunc<T> Create<T>(Func<T> func) {
        return new CachedFunc<T>(func);
    }
}
class CachedFunc<T> {
    T value;
    Func<T> func;
    public CachedFunc(Func<T> func){
        if (func == null) throw new ArgumentNullException("func");
        this.func = func;
    }
    public T Value {
        get {
            if (func != null) {
                value = func();
                func = null;
            }
            return value;
        }
    }
    public static explicit operator T(CachedFunc<T> func) {
        return func.Value; }
}
0 голосов
/ 07 мая 2009

Вы можете использовать ключевое слово static внутри функции. Он будет вычислен только один раз:

std::string GetWidelyUsedValue()
{
   static std::string value = ComputeStuff() ;
   return value ;
}

std::string ComputeStuff()
{
   // Compute the string here.
}
0 голосов
/ 07 мая 2009

Вы можете переписать этот код почти дословно в C ++

class CClass
{

  private:
     std::string* mComputeStuff_Cached;

  public:
     CClass()
       mComputeStuff_Cached(NULL)
     {

     }

     ~CClass()
     {
           delete mComputeStuff_Cached;
     }


     std::string ComputeStuff()
     {
         if (mComputeStuff_Cached != NULL)
         {
             return mComputeStuff_Cached
         }
         else
         {
             std::string calcedAnswer;
             ...
             // store away answer
             mComputeStuff_Cached = new std::string(calcedAnswer);
         }
     }
};

Я не уверен, достаточно ли проверки, чтобы увидеть, пуст ли mComputeStuff_Cached () Может быть так, что empty () является законно кэшированным результатом.

0 голосов
/ 07 мая 2009

Вы можете сделать переменную-член изменяемой (ключевое слово).

Это позволит функции-члену const изменить это значение. Я все время использую его для кеширования промежуточных результатов.

0 голосов
/ 07 мая 2009

Вы можете сделать его немного менее многословным:

private string mComputeStuff_Cached = null;
public string ComputeStuff()
{
  if (mComputeStuff_Cached == null) {
    string result;
    //
    // ...
    //Do lots of cpu intensive computation in order to compute "result" 
    //or whatever you want to compute
    //(for example the hash of a long file)
    //...
    //

    mComputeStuff_Cached  = result;
  }

  return mComputeStuff_Cached ;
}

Другие примечания по этому типу рисунка:

  • Если вы используете C ++ и такой метод const, то изменение членов будет невозможно, поскольку они также рассматриваются как const в контексте этого метода. Однако вы можете переопределить это, пометив элемент (ы), который необходимо изменить, как mutable.
  • Существует различие между "семантическим константным" и "побитовым константным". Когда автор помечает что-то как const, они обычно означают «семантическое константное» (что вы имеете в виду в данном случае), но компилятор может применять только «битовое константное».
  • const обычно создают для вызывающей стороны впечатление, что их можно вызывать из нескольких потоков одновременно, поскольку они не имеют побочных эффектов. В этом типе паттерна у вас есть побитовый побочный эффект, но нет семантического побочного эффекта. Поэтому, если возможно, что несколько потоков могут вызывать такой метод, вы должны внутренне защитить эту инициализацию.
0 голосов
/ 07 мая 2009

Решение C ++ будет очень похоже на то, что вы обрисовали в общих чертах, отличаясь только хитрым сочетанием const квалифицированных общедоступных интерфейсов и (некоторых) mutable членов:

class Computer {
    mutable string cache;
  public:
    // I wouldn't call it ComputeXXX
    // since I want to hide the implementation
    // details from my client: for the client
    // there is no change in state due to a call
    // to this function
    const string& StringVal() const {
         if (cache.empty()) {
                // compute cache
         }
         return cache;
    }
    // ...
};              
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...