Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions README-EN-source.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,94 @@ Typical application scenarios for custom ClassSupplier:

Through the `cache` option, you can enable expression caching, so the same expressions won't be recompiled, greatly improving performance.

Note that this cache has no size limit and is only suitable for use when expressions are in limited quantities:
==== Basic Cache Usage

The basic cache has no size limit and is only suitable for use when expressions are in limited quantities:

[source,java,indent=0]
----
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cacheSwitch]
----

However, when scripts are executed for the first time, they're still relatively slow because there's no cache.
==== Advanced Cache Configuration

QLExpress4 provides configurable cache implementations supporting the following features:

* **LRU Eviction**: Automatically evicts least recently used entries when cache reaches maximum capacity
* **TTL Expiration**: Supports setting expiration time for cache entries
* **Custom Implementation**: Can provide custom cache implementation through interface

===== LRU Cache (Limit Maximum Entries)

[source,java,indent=0]
----
// Create an LRU cache with max capacity of 100
CacheConfig config = CacheConfig.lruConfig(100);
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

When the cache reaches maximum capacity, the least recently used entries are automatically evicted.

===== TTL Cache (Set Expiration Time)

[source,java,indent=0]
----
// Create a cache with 5 minutes TTL
CacheConfig config = CacheConfig.ttlConfig(TimeUnit.MINUTES.toMillis(5));
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

Expired cache entries are automatically cleaned up on access, with a background thread for periodic cleanup.

===== LRU + TTL Combined Cache

[source,java,indent=0]
----
// Create a cache with max capacity of 100 and 5 minutes TTL
CacheConfig config = CacheConfig.lruAndTtlConfig(100, TimeUnit.MINUTES.toMillis(5));
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

Or use Builder for more fine-grained configuration:

[source,java,indent=0]
----
CacheConfig config = CacheConfig.builder()
.maxSize(100)
.ttl(5, TimeUnit.MINUTES)
.lruEnabled(true)
.initialCapacity(16)
.build();
----

===== Custom Cache Implementation

If the built-in cache implementations don't meet your needs, you can provide a custom implementation through the `ExpressionCache` interface:

[source,java,indent=0]
----
ExpressionCache customCache = new ExpressionCache() {
// Implement cache methods
};

InitOptions initOptions = InitOptions.builder()
.expressionCache(customCache)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

==== Pre-caching Scripts

When scripts are executed for the first time, they're relatively slow because there's no cache.

You can cache scripts before first execution using the following method to ensure first execution speed:

Expand All @@ -380,7 +460,18 @@ You can cache scripts before first execution using the following method to ensur
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=parseToCache]
----

Note that this cache has an unlimited size; be sure to control its size in your application. You can periodically clear the compilation cache by calling the `clearCompileCache` method.
==== Clearing Cache

You can manage the cache using the following methods:

[source,java,indent=0]
----
// Clear all cache
runner.clearCompileCache();

// Get current cache size
int size = runner.getCompileCacheSize();
----

=== Clearing DFA Cache

Expand Down
99 changes: 96 additions & 3 deletions README-source.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -365,14 +365,96 @@ include::./src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java[

通过 `cache` 选项可以开启表达式缓存,这样相同的表达式就不会重新编译,能够大大提升性能。

注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用:
==== 基础缓存使用

基础缓存没有限制大小,只适合在表达式为有限数量的情况下使用:

[source,java,indent=0]
----
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cacheSwitch]
----

但是当脚本首次执行时,因为没有缓存,依旧会比较慢。
注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用。

==== 高级缓存配置

QLExpress4 提供了可配置的缓存实现,支持以下特性:

* **LRU 淘汰**:当缓存达到最大容量时,自动淘汰最近最少使用的条目
* **TTL 过期**:支持设置缓存条目的过期时间
* **自定义实现**:可以通过接口提供自定义缓存实现

===== LRU 缓存(限制最大条目数)

[source,java,indent=0]
----
// 创建最大容量为 100 的 LRU 缓存
CacheConfig config = CacheConfig.lruConfig(100);
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

当缓存达到最大容量时,最近最少使用的条目会被自动淘汰。

===== TTL 缓存(设置过期时间)

[source,java,indent=0]
----
// 创建 5 分钟 TTL 的缓存
CacheConfig config = CacheConfig.ttlConfig(TimeUnit.MINUTES.toMillis(5));
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

过期的缓存条目会在访问时自动清理,并有后台线程定期清理。

===== LRU + TTL 组合缓存

[source,java,indent=0]
----
// 创建最大容量为 100、TTL 为 5 分钟的缓存
CacheConfig config = CacheConfig.lruAndTtlConfig(100, TimeUnit.MINUTES.toMillis(5));
InitOptions initOptions = InitOptions.builder()
.cacheConfig(config)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

也可以通过 Builder 进行更精细的配置:

[source,java,indent=0]
----
CacheConfig config = CacheConfig.builder()
.maxSize(100)
.ttl(5, TimeUnit.MINUTES)
.lruEnabled(true)
.initialCapacity(16)
.build();
----

===== 自定义缓存实现

如果内置缓存实现不满足需求,可以通过 `ExpressionCache` 接口提供自定义实现:

[source,java,indent=0]
----
ExpressionCache customCache = new ExpressionCache() {
// 实现缓存方法
};

InitOptions initOptions = InitOptions.builder()
.expressionCache(customCache)
.build();
Express4Runner runner = new Express4Runner(initOptions);
----

==== 预缓存脚本

当脚本首次执行时,因为没有缓存,会比较慢。

可以通过下面的方法在首次执行前就将脚本缓存起来,保证首次执行的速度:

Expand All @@ -381,7 +463,18 @@ include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cach
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=parseToCache]
----

注意该缓存的大小是无限的,业务上注意控制大小,可以调用 `clearCompileCache` 方法定期清空编译缓存。
==== 清理缓存

可以调用以下方法管理缓存:

[source,java,indent=0]
----
// 清空所有缓存
runner.clearCompileCache();

// 获取当前缓存大小
int size = runner.getCompileCacheSize();
----

=== 清除 DFA 缓存

Expand Down
47 changes: 38 additions & 9 deletions src/main/java/com/alibaba/qlexpress4/Express4Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction;
import com.alibaba.qlexpress4.api.BatchAddFunctionResult;
import com.alibaba.qlexpress4.api.QLFunctionalVarargs;
import com.alibaba.qlexpress4.cache.CacheConfig;
import com.alibaba.qlexpress4.cache.DefaultExpressionCache;
import com.alibaba.qlexpress4.cache.ExpressionCache;
import com.alibaba.qlexpress4.cache.SimpleExpressionCache;
import com.alibaba.qlexpress4.exception.PureErrReporter;
import com.alibaba.qlexpress4.exception.QLException;
import com.alibaba.qlexpress4.exception.QLSyntaxException;
Expand Down Expand Up @@ -65,22 +69,39 @@
*/
public class Express4Runner {
private final OperatorManager operatorManager = new OperatorManager();
private final Map<String, Future<QCompileCache>> compileCache = new ConcurrentHashMap<>();

private final ExpressionCache compileCache;

private final Map<String, CustomFunction> userDefineFunction = new ConcurrentHashMap<>();

private final Map<String, CompileTimeFunction> compileTimeFunctions = new ConcurrentHashMap<>();

private final GeneratorScope globalScope = new GeneratorScope(null, "global", new ConcurrentHashMap<>());

private final ReflectLoader reflectLoader;

private final InitOptions initOptions;

public Express4Runner(InitOptions initOptions) {
this.initOptions = initOptions;
this.reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.isAllowPrivateAccess());
this.compileCache = initCache(initOptions);
}

private ExpressionCache initCache(InitOptions options) {
// If custom cache is provided, use it
if (options.getExpressionCache() != null) {
return options.getExpressionCache();
}

// If cache config is provided, use DefaultExpressionCache with config
CacheConfig config = options.getCacheConfig();
if (config != null) {
return new DefaultExpressionCache(config);
}

// Default: use SimpleExpressionCache (unlimited, thread-safe)
return new SimpleExpressionCache();
}

public CustomFunction getFunction(String functionName) {
Expand Down Expand Up @@ -648,7 +669,15 @@ private DFA[] getDecisionToDFA() {
public void clearCompileCache() {
compileCache.clear();
}


/**
* Get the current compile cache size.
* @return number of cached entries
*/
public int getCompileCacheSize() {
return compileCache.size();
}

private Future<QCompileCache> getParseFuture(String script) {
Future<QCompileCache> parseFuture = compileCache.get(script);
if (parseFuture != null) {
Expand Down
Loading