From 3f06ddfb7b1877daca3f4a7289be1f1be5266182 Mon Sep 17 00:00:00 2001 From: "Filipe Oliveira (Redis)" Date: Mon, 24 Feb 2025 04:33:14 +0000 Subject: [PATCH] Reuse lookupCommand data on consecutive same command calls on main thread (#13764) We can see that on fast commands and fast pipeline use-cases, lookupCommand() takes 1.9% to 3.4% of total cpu cyles (depending on pipeline). In cases in which consecutives commands are the same we can avoid the call to lookupCommand() completely without changing or adding new fields to the client struct (we simply reuse the info already avaiable in lastcmd). This change can represent an improvement of around 4.4% in QPS on the high pipeline use-cases. --------- Co-authored-by: debing.sun --- src/server.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/server.c b/src/server.c index 9e1250ca0..3fb17da59 100644 --- a/src/server.c +++ b/src/server.c @@ -80,12 +80,25 @@ struct redisServer server; /* Server global state */ /*============================ Internal prototypes ========================== */ static inline int isShutdownInitiated(void); +static inline int isCommandReusable(struct redisCommand *cmd, robj *commandArg); int isReadyToShutdown(void); int finishShutdown(void); const char *replstateToString(int replstate); /*============================ Utility functions ============================ */ +/* Check if a given command can be reused without performing a lookup. + * A command is reusable if: + * - It is not NULL. + * - It does not have subcommands (subcommands_dict == NULL). + * This preserves simplicity on the check and accounts for the majority of the use cases. + * - Its full name matches the provided command argument. */ +static inline int isCommandReusable(struct redisCommand *cmd, robj *commandArg) { + return cmd != NULL && + cmd->subcommands_dict == NULL && + strcasecmp(cmd->fullname, commandArg->ptr) == 0; +} + /* This macro tells if we are in the context of loading an AOF. */ #define isAOFLoadingContext() \ ((server.current_client && server.current_client->id == CLIENT_ID_AOF) ? 1 : 0) @@ -3979,8 +3992,12 @@ int processCommand(client *c) { * In case we are reprocessing a command after it was blocked, * we do not have to repeat the same checks */ if (!client_reprocessing_command) { - struct redisCommand *cmd = c->iolookedcmd ? c->iolookedcmd : lookupCommand(c->argv, c->argc); - + /* check if we can reuse the last command instead of looking up if we already have that info */ + struct redisCommand *cmd = NULL; + if (isCommandReusable(c->lastcmd, c->argv[0])) + cmd = c->lastcmd; + else + cmd = c->iolookedcmd ? c->iolookedcmd : lookupCommand(c->argv, c->argc); if (!cmd) { /* Handle possible security attacks. */ if (!strcasecmp(c->argv[0]->ptr,"host:") || !strcasecmp(c->argv[0]->ptr,"post")) {