[PATCH 3/3] Use GPerf for command names.

Tomash Brechko tomash.brechko at gmail.com
Wed Nov 7 13:12:47 UTC 2007


Using one lookup per command instead of a sequence of strcmp() is more
scalable.  This patch doesn't change command syntax or semantics in
any way.  The next step would be to use GPerf for the rest of the
parsing.

Note that generated file command.c is not committed, so you'll have to
have gprof program to bootstrap the source tree.  However command.c is
included into distribution, so building memcached won't require gperf.

At least with gperf 3.0.1 gcc produces a warning:

  memcached.c:1531: warning: assignment discards qualifiers from pointer target type

This is because GPerf defines lookup function parameter as

  register const char *str

and register qualifier is discarded.  Perhaps new versions of GPerf do
not use register.  Otherwise this may be fixed by patching generated file.

diff --git a/trunk/server/Makefile.am b/trunk/server/Makefile.am
index bec67d3..91db1a2 100644
--- a/trunk/server/Makefile.am
+++ b/trunk/server/Makefile.am
@@ -1,6 +1,8 @@
 bin_PROGRAMS = memcached memcached-debug
 
-memcached_SOURCES = memcached.c slabs.c slabs.h items.c items.h assoc.c assoc.h memcached.h thread.c stats.c stats.h
+memcached_SOURCES = memcached.c slabs.c slabs.h items.c items.h \
+	assoc.c assoc.h memcached.h thread.c stats.c stats.h \
+	command.c command.h
 memcached_debug_SOURCES = $(memcached_SOURCES)
 memcached_CPPFLAGS = -DNDEBUG
 memcached_LDADD = @LIBOBJS@
@@ -8,7 +10,11 @@ memcached_debug_LDADD = $(memcached_LDADD)
 
 SUBDIRS = doc
 DIST_DIRS = scripts
-EXTRA_DIST = doc scripts TODO t memcached.spec
+EXTRA_DIST = doc scripts TODO t memcached.spec command.gperf command.c
+
+
+$(srcdir)/command.c:  $(srcdir)/command.gperf
+	$(GPERF) --output-file="$@" $^
 
 test:	memcached-debug
 	prove $(srcdir)/t
diff --git a/trunk/server/command.gperf b/trunk/server/command.gperf
new file mode 100644
index 0000000..028e4b1
--- /dev/null
+++ b/trunk/server/command.gperf
@@ -0,0 +1,39 @@
+%language=ANSI-C
+%define lookup-function-name get_command
+%compare-lengths
+%compare-strncmp
+%readonly-tables
+%enum
+%includes
+%switch=1
+%struct-type
+%define slot-name name
+%{
+#include "command.h"
+%}
+struct command;
+%%
+#
+#  keyword   command code     ntokens (negative means no less then -ntokens)
+#
+add,         CMD_ADD,         6
+append,      CMD_APPEND,      6
+bg,          CMD_BG,          3
+bget,        CMD_BGET,       -3
+cas,         CMD_CAS,         6
+decr,        CMD_DECR,        4
+delete,      CMD_DELETE,     -3
+disown,      CMD_DISOWN,      3
+flush_all,   CMD_FLUSH_ALL,  -2
+get,         CMD_GET,        -3
+gets,        CMD_GETS,       -3
+incr,        CMD_INCR,        4
+own,         CMD_OWN,         3
+prepend,     CMD_PREPEND,     6
+quit,        CMD_QUIT,        2 
+replace,     CMD_REPLACE,     6
+set,         CMD_SET,         6
+slabs,       CMD_SLABS,       5
+stats,       CMD_STATS,      -2
+verbosity,   CMD_VERBOSITY,   3
+version,     CMD_VERSION,     2
diff --git a/trunk/server/command.h b/trunk/server/command.h
new file mode 100644
index 0000000..dadb6d8
--- /dev/null
+++ b/trunk/server/command.h
@@ -0,0 +1,43 @@
+#ifndef COMMAND_H
+#define COMMAND_H 1
+
+
+enum command_code
+{
+  CMD_ADD,
+  CMD_APPEND,
+  CMD_BG,
+  CMD_BGET,
+  CMD_CAS,
+  CMD_DECR,
+  CMD_DELETE,
+  CMD_DISOWN,
+  CMD_FLUSH_ALL,
+  CMD_GET,
+  CMD_GETS,
+  CMD_INCR,
+  CMD_OWN,
+  CMD_PREPEND,
+  CMD_QUIT,
+  CMD_REPLACE,
+  CMD_SET,
+  CMD_SLABS,
+  CMD_STATS,
+  CMD_VERBOSITY,
+  CMD_VERSION
+};
+
+struct command
+{
+  const char *name;
+  enum command_code code;
+  int ntokens;
+};
+
+
+extern
+const struct command *
+get_command(const char *str, unsigned int len);
+
+
+#endif /* ! COMMAND_H */
diff --git a/trunk/server/configure.ac b/trunk/server/configure.ac
index 9bd37d7..b40c104 100644
--- a/trunk/server/configure.ac
+++ b/trunk/server/configure.ac
@@ -169,5 +169,14 @@ AC_ARG_ENABLE(threads,
 
 AC_CHECK_FUNCS(mlockall)
 
+AC_PATH_PROG(GPERF, gperf, [false])
+
 AC_CONFIG_FILES(Makefile doc/Makefile)
 AC_OUTPUT
+
+if test "x$GPERF" == "xfalse"; then
+   AC_MSG_NOTICE([
+
+  gperf program was not found.  However it is not required to build memcached
+])
+fi
diff --git a/trunk/server/memcached.c b/trunk/server/memcached.c
index 6abaf06..b3c5eb4 100644
--- a/trunk/server/memcached.c
+++ b/trunk/server/memcached.c
@@ -16,6 +16,7 @@ std *
  *  $Id$
  */
 #include "memcached.h"
+#include "command.h"
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/un.h>
@@ -1504,7 +1505,7 @@ static void process_command(conn *c, char *command) {
 
     token_t tokens[MAX_TOKENS];
     size_t ntokens;
-    int comm;
+    struct command *cmd;
 
     assert(c != NULL);
 
@@ -1525,172 +1526,194 @@ static void process_command(conn *c, char *command) {
     }
 
     ntokens = tokenize_command(command, tokens, MAX_TOKENS);
-    if (ntokens >= 3 &&
-        ((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) ||
-         (strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) {
 
-        process_get_command(c, tokens, ntokens, false);
-
-    } else if (ntokens == 6 &&
-               ((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
-                (strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) ||
-                (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)) ||
-                (strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) ||
-                (strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) )) {
+    cmd = get_command(tokens[COMMAND_TOKEN].value,
+                      tokens[COMMAND_TOKEN].length);
 
-        process_update_command(c, tokens, ntokens, comm, false);
-
-    } else if (ntokens == 6 && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0)) {
+    if (!cmd || (cmd->ntokens > 0 && cmd->ntokens != ntokens)
+        || (cmd->ntokens < 0 && -cmd->ntokens > ntokens)) {
+      out_string(c, "ERROR");
+      return;
+    }
 
+    switch (cmd->code) {
+    case CMD_GET:
+    case CMD_BGET:
+        process_get_command(c, tokens, ntokens, false);
+        break;
+    case CMD_ADD:
+        process_update_command(c, tokens, ntokens, NREAD_ADD, false);
+        break;
+    case CMD_SET:
+        process_update_command(c, tokens, ntokens, NREAD_SET, false);
+        break;
+    case CMD_REPLACE:
+        process_update_command(c, tokens, ntokens, NREAD_REPLACE, false);
+        break;
+    case CMD_PREPEND:
+        process_update_command(c, tokens, ntokens, NREAD_PREPEND, false);
+        break;
+    case CMD_APPEND:
+        process_update_command(c, tokens, ntokens, NREAD_APPEND, false);
+        break;
+    case CMD_CAS:
         process_update_command(c, tokens, ntokens, NREAD_REPLACE, true);
-
-    } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) {
-
+        break;
+    case CMD_INCR:
         process_arithmetic_command(c, tokens, ntokens, 1);
-
-    } else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) {
-
+        break;
+    case CMD_GETS:
         process_get_command(c, tokens, ntokens, true);
-
-    } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {
-
+        break;
+    case CMD_DECR:
         process_arithmetic_command(c, tokens, ntokens, 0);
-
-    } else if (ntokens >= 3 && ntokens <= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0)) {
-
-        process_delete_command(c, tokens, ntokens);
-
-    } else if (ntokens == 3 && strcmp(tokens[COMMAND_TOKEN].value, "own") == 0) {
-        unsigned int bucket, gen;
-        if (!settings.managed) {
-            out_string(c, "CLIENT_ERROR not a managed instance");
-            return;
-        }
-
-        if (sscanf(tokens[1].value, "%u:%u", &bucket,&gen) == 2) {
-            if ((bucket < 0) || (bucket >= MAX_BUCKETS)) {
-                out_string(c, "CLIENT_ERROR bucket number out of range");
+        break;
+    case CMD_DELETE:
+        if (ntokens <= 4)
+            process_delete_command(c, tokens, ntokens);
+        else
+            out_string(c, "ERROR");
+        break;
+    case CMD_OWN:
+        {
+            unsigned int bucket, gen;
+            if (!settings.managed) {
+                out_string(c, "CLIENT_ERROR not a managed instance");
                 return;
             }
-            buckets[bucket] = gen;
-            out_string(c, "OWNED");
-            return;
-        } else {
-            out_string(c, "CLIENT_ERROR bad format");
-            return;
-        }
-
-    } else if (ntokens == 3 && (strcmp(tokens[COMMAND_TOKEN].value, "disown")) == 0) {
 
-        int bucket;
-        if (!settings.managed) {
-            out_string(c, "CLIENT_ERROR not a managed instance");
-            return;
-        }
-        if (sscanf(tokens[1].value, "%u", &bucket) == 1) {
-            if ((bucket < 0) || (bucket >= MAX_BUCKETS)) {
-                out_string(c, "CLIENT_ERROR bucket number out of range");
+            if (sscanf(tokens[1].value, "%u:%u", &bucket,&gen) == 2) {
+                if ((bucket < 0) || (bucket >= MAX_BUCKETS)) {
+                    out_string(c, "CLIENT_ERROR bucket number out of range");
+                    return;
+                }
+                buckets[bucket] = gen;
+                out_string(c, "OWNED");
+                return;
+            } else {
+                out_string(c, "CLIENT_ERROR bad format");
                 return;
             }
-            buckets[bucket] = 0;
-            out_string(c, "DISOWNED");
-            return;
-        } else {
-            out_string(c, "CLIENT_ERROR bad format");
-            return;
         }
-
-    } else if (ntokens == 3 && (strcmp(tokens[COMMAND_TOKEN].value, "bg")) == 0) {
-        int bucket, gen;
-        if (!settings.managed) {
-            out_string(c, "CLIENT_ERROR not a managed instance");
-            return;
+        break;
+    case CMD_DISOWN:
+        {
+            int bucket;
+            if (!settings.managed) {
+                out_string(c, "CLIENT_ERROR not a managed instance");
+                return;
+            }
+            if (sscanf(tokens[1].value, "%u", &bucket) == 1) {
+                if ((bucket < 0) || (bucket >= MAX_BUCKETS)) {
+                    out_string(c, "CLIENT_ERROR bucket number out of range");
+                    return;
+                }
+                buckets[bucket] = 0;
+                out_string(c, "DISOWNED");
+                return;
+            } else {
+                out_string(c, "CLIENT_ERROR bad format");
+                return;
+            }
         }
-        if (sscanf(tokens[1].value, "%u:%u", &bucket, &gen) == 2) {
-            /* we never write anything back, even if input's wrong */
-            if ((bucket < 0) || (bucket >= MAX_BUCKETS) || (gen <= 0)) {
-                /* do nothing, bad input */
+        break;
+    case CMD_BG:
+        {
+            int bucket, gen;
+            if (!settings.managed) {
+                out_string(c, "CLIENT_ERROR not a managed instance");
+                return;
+            }
+            if (sscanf(tokens[1].value, "%u:%u", &bucket, &gen) == 2) {
+                /* we never write anything back, even if input's wrong */
+                if ((bucket < 0) || (bucket >= MAX_BUCKETS) || (gen <= 0)) {
+                    /* do nothing, bad input */
+                } else {
+                    c->bucket = bucket;
+                    c->gen = gen;
+                }
+                conn_set_state(c, conn_read);
+                return;
             } else {
-                c->bucket = bucket;
-                c->gen = gen;
+                out_string(c, "CLIENT_ERROR bad format");
+                return;
             }
-            conn_set_state(c, conn_read);
-            return;
-        } else {
-            out_string(c, "CLIENT_ERROR bad format");
-            return;
         }
-
-    } else if (ntokens >= 2 && (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0)) {
-
+        break;
+    case CMD_STATS:
         process_stat(c, tokens, ntokens);
+        break;
+    case CMD_FLUSH_ALL:
+        if (ntokens <= 3) {
+            time_t exptime = 0;
+            set_current_time();
+
+            if(ntokens == 2) {
+                settings.oldest_live = current_time - 1;
+                item_flush_expired();
+                out_string(c, "OK");
+                return;
+            }
 
-    } else if (ntokens >= 2 && ntokens <= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0)) {
-        time_t exptime = 0;
-        set_current_time();
+            exptime = strtol(tokens[1].value, NULL, 10);
+            if(errno == ERANGE) {
+                out_string(c, "CLIENT_ERROR bad command line format");
+                return;
+            }
 
-        if(ntokens == 2) {
-            settings.oldest_live = current_time - 1;
+            settings.oldest_live = realtime(exptime) - 1;
             item_flush_expired();
             out_string(c, "OK");
             return;
+        } else {
+            out_string(c, "ERROR");
         }
-
-        exptime = strtol(tokens[1].value, NULL, 10);
-        if(errno == ERANGE) {
-            out_string(c, "CLIENT_ERROR bad command line format");
-            return;
-        }
-
-        settings.oldest_live = realtime(exptime) - 1;
-        item_flush_expired();
-        out_string(c, "OK");
-        return;
-
-    } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "version") == 0)) {
-
+        break;
+    case CMD_VERSION:
         out_string(c, "VERSION " VERSION);
-
-    } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "quit") == 0)) {
-
+        break;
+    case CMD_QUIT:
         conn_set_state(c, conn_closing);
-
-    } else if (ntokens == 5 && (strcmp(tokens[COMMAND_TOKEN].value, "slabs") == 0 &&
-                                strcmp(tokens[COMMAND_TOKEN + 1].value, "reassign") == 0)) {
+        break;
+    case CMD_SLABS:
+        if (strcmp(tokens[COMMAND_TOKEN + 1].value, "reassign") == 0) {
 #ifdef ALLOW_SLABS_REASSIGN
 
-        int src, dst, rv;
+            int src, dst, rv;
 
-        src = strtol(tokens[2].value, NULL, 10);
-        dst  = strtol(tokens[3].value, NULL, 10);
+            src = strtol(tokens[2].value, NULL, 10);
+            dst  = strtol(tokens[3].value, NULL, 10);
 
-        if(errno == ERANGE) {
-            out_string(c, "CLIENT_ERROR bad command line format");
-            return;
-        }
+            if(errno == ERANGE) {
+                out_string(c, "CLIENT_ERROR bad command line format");
+                return;
+            }
 
-        rv = slabs_reassign(src, dst);
-        if (rv == 1) {
-            out_string(c, "DONE");
-            return;
-        }
-        if (rv == 0) {
-            out_string(c, "CANT");
-            return;
-        }
-        if (rv == -1) {
-            out_string(c, "BUSY");
-            return;
-        }
+            rv = slabs_reassign(src, dst);
+            if (rv == 1) {
+                out_string(c, "DONE");
+                return;
+            }
+            if (rv == 0) {
+                out_string(c, "CANT");
+                return;
+            }
+            if (rv == -1) {
+                out_string(c, "BUSY");
+                return;
+            }
 #else
-        out_string(c, "CLIENT_ERROR Slab reassignment not supported");
+            out_string(c, "CLIENT_ERROR Slab reassignment not supported");
 #endif
-    } else if (ntokens == 3 && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
+
+        } else {
+            out_string(c, "ERROR");
+        }
+        break;
+    case CMD_VERBOSITY:
         process_verbosity_command(c, tokens, ntokens);
-    } else {
-        out_string(c, "ERROR");
+        break;
     }
-    return;
 }
 
 /*
-- 
1.5.3.5.529.ge3d6d


More information about the memcached mailing list