diff options
author | Harkrishn Patro <harkrisp@amazon.com> | 2022-07-03 23:47:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-04 09:47:34 +0300 |
commit | a3704d4e87b65a9c071b9dd1ead04cc338d3bc18 (patch) | |
tree | d5823eb7b2d97bb6bec9d66e9df24777ff79aaed | |
parent | 33b7ff387cbc75b5d53d4c42ec6303c8dbd5f656 (diff) | |
download | redis-a3704d4e87b65a9c071b9dd1ead04cc338d3bc18.tar.gz |
Optimize number of realloc syscall during multi/exec flow (#10921)
## Issue
During the MULTI/EXEC flow, each command gets queued until the `EXEC`
command is received and during this phase on every command queue, a
`realloc` is being invoked. This could be expensive based on the realloc
behavior (if copy to a new memory location).
## Solution
In order to reduce the no. of syscall, couple of optimization I've used.
1. By default, reserve memory for atleast two commands. `MULTI/EXEC` for a
single command doesn't have any significance. Hence, I believe customer wouldn't use it.
2. For further reservation, increase the memory allocation in exponent growth (power of 2).
This reduces the no. of `realloc` call from `N` to `log(N)` times.
## Other changes:
* Include multi exec queued command array in client memory consumption calculation
(affects client eviction too)
-rw-r--r-- | src/multi.c | 16 | ||||
-rw-r--r-- | src/server.h | 1 |
2 files changed, 14 insertions, 3 deletions
diff --git a/src/multi.c b/src/multi.c index 53194e55e..c1204cef3 100644 --- a/src/multi.c +++ b/src/multi.c @@ -38,6 +38,7 @@ void initClientMultiState(client *c) { c->mstate.cmd_flags = 0; c->mstate.cmd_inv_flags = 0; c->mstate.argv_len_sums = 0; + c->mstate.alloc_count = 0; } /* Release all the resources associated with MULTI/EXEC state */ @@ -65,9 +66,16 @@ void queueMultiCommand(client *c, uint64_t cmd_flags) { * aborted. */ if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) return; - - c->mstate.commands = zrealloc(c->mstate.commands, - sizeof(multiCmd)*(c->mstate.count+1)); + if (c->mstate.count == 0) { + /* If a client is using multi/exec, assuming it is used to execute at least + * two commands. Hence, creating by default size of 2. */ + c->mstate.commands = zmalloc(sizeof(multiCmd)*2); + c->mstate.alloc_count = 2; + } + if (c->mstate.count == c->mstate.alloc_count) { + c->mstate.alloc_count = c->mstate.alloc_count < INT_MAX/2 ? c->mstate.alloc_count*2 : INT_MAX; + c->mstate.commands = zrealloc(c->mstate.commands, sizeof(multiCmd)*(c->mstate.alloc_count*2)); + } mc = c->mstate.commands+c->mstate.count; mc->cmd = c->cmd; mc->argc = c->argc; @@ -466,5 +474,7 @@ size_t multiStateMemOverhead(client *c) { size_t mem = c->mstate.argv_len_sums; /* Add watched keys overhead, Note: this doesn't take into account the watched keys themselves, because they aren't managed per-client. */ mem += listLength(c->watched_keys) * (sizeof(listNode) + sizeof(watchedKey)); + /* Reserved memory for queued multi commands. */ + mem += c->mstate.alloc_count * sizeof(multiCmd); return mem; } diff --git a/src/server.h b/src/server.h index 4b4bd29e4..e65b69122 100644 --- a/src/server.h +++ b/src/server.h @@ -959,6 +959,7 @@ typedef struct multiState { is possible to know if all the commands have a certain flag. */ size_t argv_len_sums; /* mem used by all commands arguments */ + int alloc_count; /* total number of multiCmd struct memory reserved. */ } multiState; /* This structure holds the blocking operation state for a client. |