mparlak / Flee

Fast Lightweight Expression Evaluator
607 stars 119 forks source link

Caching of compiled rules. #63

Open balajiforgithub opened 5 years ago

balajiforgithub commented 5 years ago

Hi, I am using Flee.Net45 library for evaluating expressions in my application.

As rule compilation is taking time, can we cache the compiled rules to bypass the rule compilation process?

Sample code : _expressionContext.CompileDynamic("expression")

Appreciate your help.

Thanks, Balaji.

PeterKnealeBizCover commented 5 years ago

I'm just experimenting with how to solve the same issue and this seems to work nicely

    public class Rule
    {
        public string Id {get;set;}
        public string Expression {get;set;}
    }

    public interface IExpressionCache
    {
        IGenericExpression<bool> GetExpression(Rule rule, Context context);
    }

    public interface IExpressionCompiler
    {
        IGenericExpression<bool> Compiler(string expression, Context context);
        bool Execute(IGenericExpression<bool> compiled, Context context);
    }

    public class ExpressionCache : IExpressionCache
    {
        private readonly IExpressionCompiler _engine;
        private readonly ICache _cache;

        private static object locker = new object();

        public ExpressionCache(IExpressionCompiler engine, ICache cache)
        {
            _engine = engine;
            _cache = cache;
        }

        public IGenericExpression<bool> GetExpression(Rule rule, Context context)
        {
            var key = $"rule_{rule.Id}";

            // https://en.wikipedia.org/wiki/Double-checked_locking
            var compiled = _cache.Get<IGenericExpression<bool>>(key);
            if (compiled == null) // 1st check
            {
                lock (locker) // Enter critical section
                {
                    compiled = _cache.Get<IGenericExpression<bool>>(key);
                    if (compiled == null) // 2nd (double) check
                    {
                        // Create a new context and cache it
                        compiled = _engine.Compiler(rule.Expression, context);
                        _cache.Set(key, compiled);
                    }
                }
            }
            return compiled;
        }
    }

    public class ExpressionCompiler : IExpressionCompiler
    {
        private const string _contextVariableName = "Context";

        public IGenericExpression<bool> Compiler(string expression, Context context)
        {
            var ec = new ExpressionContext();
            ec.Variables.Add(_contextVariableName, context);
            return ec.CompileGeneric<bool>(expression);
        }

        public bool Execute(IGenericExpression<bool> compiled, Context context)
        {
            compiled.Context.Variables[_contextVariableName] = context;
            return compiled.Evaluate();
        }
    }

    public interface ICache
    {
        T Get<T>(string key);
        void Set<T>(string key, T value);
    }

    /// <summary>
    /// Naive cache
    /// TODO: configure 
    /// </summary>
    public class Cache : ICache
    {
        private readonly IMemoryCache _cache;

        public Cache(IMemoryCache cache)
        {
            _cache = cache;
        }

        public T Get<T>(string key)
        {
            return _cache.Get<T>(key);
        }

        public void Set<T>(string key, T value)
        {
            _cache.Set(key, value, TimeSpan.FromSeconds(5));
        }
    }
hunkydoryrepair commented 2 years ago

I've also implemented a cache with a simple Dictionary. I think the application would best know how to do the caching