mirror of
https://github.com/redis/redis.git
synced 2026-07-16 13:32:51 -04:00
Add LCS LEN fast path using rolling two-row DP
When LCS is invoked with LEN (and without IDX), the actual subsequence or match indices are not needed, so allocating the full O(n*m) table is wasteful. Compute the length using two rolling rows of size O(min(|a|,|b|)) instead. This removes the proto-max-bulk-len ceiling that previously rejected moderately large inputs (e.g. 16K x 16K) and improves wall time for larger inputs (~1.9x at 8KB x 8KB) thanks to better cache locality and much smaller transient allocations. Smaller inputs are unaffected. The rolling computation is extracted into lcsReplyLength() to keep lcsCommand focused on argument parsing and dispatch. Signed-off-by: charsyam <charsyam@naver.com>
This commit is contained in:
parent
0d85627bf0
commit
fc0897bfc1
1 changed files with 55 additions and 0 deletions
|
|
@ -973,6 +973,54 @@ void strlenCommand(client *c) {
|
|||
addReplyLongLong(c,stringObjectLen(kv));
|
||||
}
|
||||
|
||||
/* Compute only the LCS length using two rolling rows, and reply to the client.
|
||||
* Used when LCS is invoked with LEN (and without IDX), since recovering the
|
||||
* actual subsequence or match indices is not required. Memory usage is
|
||||
* O(min(|a|,|b|)) instead of O(|a|*|b|). On allocation/limit failures this
|
||||
* sends an error reply; in all cases the client receives exactly one reply. */
|
||||
static void lcsReplyLength(client *c, sds a, sds b) {
|
||||
uint32_t alen = sdslen(a);
|
||||
uint32_t blen = sdslen(b);
|
||||
/* Use the shorter string for the inner dimension to minimize memory. */
|
||||
const char *astr = a;
|
||||
const char *bstr = b;
|
||||
if (blen > alen) {
|
||||
uint32_t tmp_len = alen; alen = blen; blen = tmp_len;
|
||||
const char *tmp_str = astr; astr = bstr; bstr = tmp_str;
|
||||
}
|
||||
|
||||
size_t row_bytes = ((size_t)blen + 1) * sizeof(uint32_t);
|
||||
if (row_bytes * 2 > (size_t)server.proto_max_bulk_len) {
|
||||
addReplyError(c, "Insufficient memory, transient memory for LCS exceeds proto-max-bulk-len");
|
||||
return;
|
||||
}
|
||||
uint32_t *prev = ztrycalloc(row_bytes);
|
||||
uint32_t *curr = ztrycalloc(row_bytes);
|
||||
if (!prev || !curr) {
|
||||
if (prev) zfree(prev);
|
||||
if (curr) zfree(curr);
|
||||
addReplyError(c, "Insufficient memory, failed allocating transient memory for LCS");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t ii = 1; ii <= alen; ii++) {
|
||||
curr[0] = 0;
|
||||
for (uint32_t jj = 1; jj <= blen; jj++) {
|
||||
if (astr[ii - 1] == bstr[jj - 1]) {
|
||||
curr[jj] = prev[jj - 1] + 1;
|
||||
} else {
|
||||
uint32_t v1 = prev[jj];
|
||||
uint32_t v2 = curr[jj - 1];
|
||||
curr[jj] = v1 > v2 ? v1 : v2;
|
||||
}
|
||||
}
|
||||
uint32_t *tmp = prev; prev = curr; curr = tmp;
|
||||
}
|
||||
addReplyLongLong(c, prev[blen]);
|
||||
zfree(prev);
|
||||
zfree(curr);
|
||||
}
|
||||
|
||||
/* LCS key1 key2 [LEN] [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN] */
|
||||
void lcsCommand(client *c) {
|
||||
uint32_t i, j;
|
||||
|
|
@ -1032,6 +1080,13 @@ void lcsCommand(client *c) {
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Fast path: when only LEN is requested, compute with rolling DP
|
||||
* to avoid allocating the full O(n*m) table. */
|
||||
if (getlen && !getidx) {
|
||||
lcsReplyLength(c, a, b);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
|
|
|
|||
Loading…
Reference in a new issue