Browse Source

Lwan: Add cached queries benchmark (#5801)

* Update Lwan version

* Lwan: Update JSON encoder

* Lwan: Implement Cached Queries benchmark

This uses Lwan's already existing loading cache infrastructure.
Leandro A. F. Pereira 5 years ago
parent
commit
d6f495e82a

+ 1 - 0
frameworks/C/lwan/benchmark_config.json

@@ -6,6 +6,7 @@
         "json_url": "/json",
         "json_url": "/json",
         "db_url": "/db",
         "db_url": "/db",
         "query_url": "/queries?queries=",
         "query_url": "/queries?queries=",
+        "cached_worlds_url": "/cached-worlds?count=",
         "plaintext_url": "/plaintext",
         "plaintext_url": "/plaintext",
         "fortune_url": "/fortunes",
         "fortune_url": "/fortunes",
         "port": 8080,
         "port": 8080,

+ 1 - 1
frameworks/C/lwan/lwan-lua.dockerfile

@@ -18,7 +18,7 @@ RUN mkdir luajit && \
     cd luajit && \
     cd luajit && \
     PREFIX=/usr CFLAGS="-O3 -mtune=native -march=native -flto -ffat-lto-objects" make -j install
     PREFIX=/usr CFLAGS="-O3 -mtune=native -march=native -flto -ffat-lto-objects" make -j install
 
 
-RUN wget https://github.com/lpereira/lwan/archive/a75278f067189fc93ea41b25e5ec46f5eb95b217.tar.gz -O - | tar xz --strip-components=1 && \
+RUN wget https://github.com/lpereira/lwan/archive/b8bf6b0b048d171a1af7b03b0d763c0f8ffd0122.tar.gz -O - | tar xz --strip-components=1 && \
     mkdir build && cd build && \
     mkdir build && cd build && \
     cmake /lwan -DCMAKE_BUILD_TYPE=Release -DUSE_ALTERNATIVE_MALLOC=mimalloc && \
     cmake /lwan -DCMAKE_BUILD_TYPE=Release -DUSE_ALTERNATIVE_MALLOC=mimalloc && \
     make lwan-static
     make lwan-static

+ 1 - 1
frameworks/C/lwan/lwan.dockerfile

@@ -18,7 +18,7 @@ RUN mkdir luajit && \
     cd luajit && \
     cd luajit && \
     PREFIX=/usr CFLAGS="-O3 -mtune=native -march=native -flto -ffat-lto-objects" make -j install
     PREFIX=/usr CFLAGS="-O3 -mtune=native -march=native -flto -ffat-lto-objects" make -j install
 
 
-RUN wget https://github.com/lpereira/lwan/archive/a75278f067189fc93ea41b25e5ec46f5eb95b217.tar.gz -O - | tar xz --strip-components=1 && \
+RUN wget https://github.com/lpereira/lwan/archive/b8bf6b0b048d171a1af7b03b0d763c0f8ffd0122.tar.gz -O - | tar xz --strip-components=1 && \
     mkdir build && cd build && \
     mkdir build && cd build && \
     cmake /lwan -DCMAKE_BUILD_TYPE=Release -DUSE_ALTERNATIVE_MALLOC=mimalloc && \
     cmake /lwan -DCMAKE_BUILD_TYPE=Release -DUSE_ALTERNATIVE_MALLOC=mimalloc && \
     make lwan-static
     make lwan-static

+ 45 - 41
frameworks/C/lwan/src/json.c

@@ -80,6 +80,18 @@ static void emit(struct lexer *lexer, enum json_tokens token)
     lexer->start = lexer->pos;
     lexer->start = lexer->pos;
 }
 }
 
 
+static void* emit_cont(struct lexer *lexer, enum json_tokens token)
+{
+    emit(lexer, token);
+    return lexer_json;
+}
+
+static void* emit_end(struct lexer *lexer, enum json_tokens token)
+{
+    emit(lexer, token);
+    return NULL;
+}
+
 static int next(struct lexer *lexer)
 static int next(struct lexer *lexer)
 {
 {
     if (lexer->pos >= lexer->end) {
     if (lexer->pos >= lexer->end) {
@@ -112,8 +124,7 @@ static void *lexer_string(struct lexer *lexer)
         int chr = next(lexer);
         int chr = next(lexer);
 
 
         if (UNLIKELY(chr == '\0')) {
         if (UNLIKELY(chr == '\0')) {
-            emit(lexer, JSON_TOK_ERROR);
-            return NULL;
+            return emit_end(lexer, JSON_TOK_ERROR);
         }
         }
 
 
         if (chr == '\\') {
         if (chr == '\\') {
@@ -162,8 +173,7 @@ static void *lexer_string(struct lexer *lexer)
     }
     }
 
 
 error:
 error:
-    emit(lexer, JSON_TOK_ERROR);
-    return NULL;
+    return emit_end(lexer, JSON_TOK_ERROR);
 }
 }
 
 
 static int accept_run(struct lexer *lexer, const char *run)
 static int accept_run(struct lexer *lexer, const char *run)
@@ -184,31 +194,26 @@ static void *lexer_boolean(struct lexer *lexer)
     switch (next(lexer)) {
     switch (next(lexer)) {
     case 'r':
     case 'r':
         if (LIKELY(!accept_run(lexer, "ue"))) {
         if (LIKELY(!accept_run(lexer, "ue"))) {
-            emit(lexer, JSON_TOK_TRUE);
-            return lexer_json;
+            return emit_cont(lexer, JSON_TOK_TRUE);
         }
         }
         break;
         break;
     case 'a':
     case 'a':
         if (LIKELY(!accept_run(lexer, "lse"))) {
         if (LIKELY(!accept_run(lexer, "lse"))) {
-            emit(lexer, JSON_TOK_FALSE);
-            return lexer_json;
+            return emit_cont(lexer, JSON_TOK_FALSE);
         }
         }
         break;
         break;
     }
     }
 
 
-    emit(lexer, JSON_TOK_ERROR);
-    return NULL;
+    return emit_end(lexer, JSON_TOK_ERROR);
 }
 }
 
 
 static void *lexer_null(struct lexer *lexer)
 static void *lexer_null(struct lexer *lexer)
 {
 {
     if (UNLIKELY(accept_run(lexer, "ull") < 0)) {
     if (UNLIKELY(accept_run(lexer, "ull") < 0)) {
-        emit(lexer, JSON_TOK_ERROR);
-        return NULL;
+        return emit_end(lexer, JSON_TOK_ERROR);
     }
     }
 
 
-    emit(lexer, JSON_TOK_NULL);
-    return lexer_json;
+    return emit_cont(lexer, JSON_TOK_NULL);
 }
 }
 
 
 static void *lexer_number(struct lexer *lexer)
 static void *lexer_number(struct lexer *lexer)
@@ -221,9 +226,7 @@ static void *lexer_number(struct lexer *lexer)
         }
         }
 
 
         backup(lexer);
         backup(lexer);
-        emit(lexer, JSON_TOK_NUMBER);
-
-        return lexer_json;
+        return emit_cont(lexer, JSON_TOK_NUMBER);
     }
     }
 }
 }
 
 
@@ -234,23 +237,26 @@ static void *lexer_json(struct lexer *lexer)
 
 
         switch (chr) {
         switch (chr) {
         case '\0':
         case '\0':
-            emit(lexer, JSON_TOK_EOF);
-            return NULL;
+            return emit_end(lexer, JSON_TOK_EOF);
+
         case '}':
         case '}':
         case '{':
         case '{':
         case '[':
         case '[':
         case ']':
         case ']':
         case ',':
         case ',':
         case ':':
         case ':':
-            emit(lexer, (enum json_tokens)chr);
-            return lexer_json;
+            return emit_cont(lexer, (enum json_tokens)chr);
+
         case '"':
         case '"':
             return lexer_string;
             return lexer_string;
+
         case 'n':
         case 'n':
             return lexer_null;
             return lexer_null;
+
         case 't':
         case 't':
         case 'f':
         case 'f':
             return lexer_boolean;
             return lexer_boolean;
+
         case '-':
         case '-':
             if (LIKELY(isdigit(peek(lexer)))) {
             if (LIKELY(isdigit(peek(lexer)))) {
                 return lexer_number;
                 return lexer_number;
@@ -267,8 +273,7 @@ static void *lexer_json(struct lexer *lexer)
                 return lexer_number;
                 return lexer_number;
             }
             }
 
 
-            emit(lexer, JSON_TOK_ERROR);
-            return NULL;
+            return emit_end(lexer, JSON_TOK_ERROR);
         }
         }
     }
     }
 }
 }
@@ -609,26 +614,25 @@ int json_obj_parse(char *payload,
     return obj_parse(&obj, descr, descr_len, val);
     return obj_parse(&obj, descr, descr_len, val);
 }
 }
 
 
-static char escape_as(char chr)
+/*
+ * Routines has_zero() and has_value() are from
+ * https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
+ */
+static ALWAYS_INLINE uint64_t has_zero(uint64_t v)
 {
 {
-    switch (chr) {
-    case '"':
-        return '"';
-    case '\\':
-        return '\\';
-    case '\b':
-        return 'b';
-    case '\f':
-        return 'f';
-    case '\n':
-        return 'n';
-    case '\r':
-        return 'r';
-    case '\t':
-        return 't';
-    }
+    return (v - 0x0101010101010101UL) & ~v & 0x8080808080808080UL;
+}
 
 
-    return 0;
+static ALWAYS_INLINE uint64_t has_value(uint64_t x, char n)
+{
+    return has_zero(x ^ (~0UL / 255 * (uint64_t)n));
+}
+
+static char escape_as(char chr)
+{
+    static const char escaped[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 't'};
+    uint64_t mask = has_value(0x225c080c0a0d0909UL, chr);
+    return mask == 0 ? 0 : escaped[__builtin_clzl(mask) / 8];
 }
 }
 
 
 static int json_escape_internal(const char *str,
 static int json_escape_internal(const char *str,

+ 86 - 2
frameworks/C/lwan/src/techempower.c

@@ -23,9 +23,11 @@
 #include <string.h>
 #include <string.h>
 
 
 #include "lwan-private.h"
 #include "lwan-private.h"
+#include "lwan-cache.h"
 #include "lwan-config.h"
 #include "lwan-config.h"
 #include "lwan-template.h"
 #include "lwan-template.h"
 #include "lwan-mod-lua.h"
 #include "lwan-mod-lua.h"
+#include "int-to-str.h"
 
 
 #include "database.h"
 #include "database.h"
 #include "json.h"
 #include "json.h"
@@ -188,9 +190,9 @@ LWAN_HANDLER(json)
                              N_ELEMENTS(hello_world_json_desc), &j);
                              N_ELEMENTS(hello_world_json_desc), &j);
 }
 }
 
 
-static bool db_query(struct db_stmt *stmt, struct db_json *out)
+static bool db_query_key(struct db_stmt *stmt, struct db_json *out, int key)
 {
 {
-    struct db_row row = {.kind = 'i', .u.i = (rand() % 10000) + 1};
+    struct db_row row = {.kind = 'i', .u.i = key + 1};
     if (UNLIKELY(!db_stmt_bind(stmt, &row, 1)))
     if (UNLIKELY(!db_stmt_bind(stmt, &row, 1)))
         return false;
         return false;
 
 
@@ -204,6 +206,11 @@ static bool db_query(struct db_stmt *stmt, struct db_json *out)
     return true;
     return true;
 }
 }
 
 
+static inline bool db_query(struct db_stmt *stmt, struct db_json *out)
+{
+    return db_query_key(stmt, out, rand() % 10000);
+}
+
 LWAN_HANDLER(db)
 LWAN_HANDLER(db)
 {
 {
     struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
     struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
@@ -260,6 +267,75 @@ out:
     return ret;
     return ret;
 }
 }
 
 
+static struct cache *cached_queries_cache;
+struct db_json_cached {
+    struct cache_entry base;
+    struct db_json db_json;
+};
+
+static struct cache_entry *cached_queries_new(const char *key, void *context)
+{
+    struct db_json_cached *entry;
+    struct db_stmt *stmt;
+
+    entry = malloc(sizeof(*entry));
+    if (UNLIKELY(!entry))
+        return NULL;
+
+    stmt = db_prepare_stmt(get_db(), random_number_query,
+                           sizeof(random_number_query) - 1);
+    if (UNLIKELY(!stmt)) {
+        free(entry);
+        return NULL;
+    }
+
+    if (!db_query_key(stmt, &entry->db_json, atoi(key))) {
+        free(entry);
+        entry = NULL;
+    }
+
+    db_stmt_finalize(stmt);
+
+    return (struct cache_entry *)entry;
+}
+
+static void cached_queries_free(struct cache_entry *entry, void *context)
+{
+    free(entry);
+}
+
+LWAN_HANDLER(cached_worlds)
+{
+    const char *queries_str = lwan_request_get_query_param(request, "count");
+    long queries;
+
+    queries = LIKELY(queries_str)
+                  ? LWAN_MIN(500, LWAN_MAX(1, parse_long(queries_str, -1)))
+                  : 1;
+
+    struct queries_json qj = {.queries_len = (size_t)queries};
+    for (long i = 0; i < queries; i++) {
+        char key_buf[INT_TO_STR_BUFFER_SIZE];
+        struct db_json_cached *jc;
+        size_t discard;
+
+        jc = (struct db_json_cached *)cache_coro_get_and_ref_entry(
+            cached_queries_cache, request->conn->coro,
+            int_to_string(rand() % 10000, key_buf, &discard));
+        if (UNLIKELY(!jc))
+            return HTTP_INTERNAL_ERROR;
+
+        qj.queries[i] = jc->db_json;
+    }
+
+    /* Avoid reallocations/copies while building response.  Each response
+     * has ~32bytes.  500 queries (max) should be less than 16384 bytes,
+     * so this is a good approximation.  */
+    lwan_strbuf_grow_to(response->buffer, (size_t)(32l * queries));
+
+    return json_response_arr(response, &queries_array_desc, &qj);
+}
+
 LWAN_HANDLER(plaintext)
 LWAN_HANDLER(plaintext)
 {
 {
     lwan_strbuf_set_static(response->buffer, hello_world,
     lwan_strbuf_set_static(response->buffer, hello_world,
@@ -399,8 +475,16 @@ int main(void)
     if (!fortune_tpl)
     if (!fortune_tpl)
         lwan_status_critical("Could not compile fortune templates");
         lwan_status_critical("Could not compile fortune templates");
 
 
+    cached_queries_cache = cache_create(cached_queries_new,
+                                        cached_queries_free,
+                                        NULL,
+                                        3600 /* 1 hour */);
+    if (!cached_queries_cache)
+        lwan_status_critical("Could not create cached queries cache");
+
     lwan_main_loop(&l);
     lwan_main_loop(&l);
 
 
+    cache_destroy(cached_queries_cache);
     lwan_tpl_free(fortune_tpl);
     lwan_tpl_free(fortune_tpl);
     lwan_shutdown(&l);
     lwan_shutdown(&l);
 
 

+ 1 - 0
frameworks/C/lwan/techempower.conf

@@ -4,6 +4,7 @@ listener *:8080 {
     &json /json
     &json /json
     &db /db
     &db /db
     &queries /queries
     &queries /queries
+    &cached_worlds /cached-worlds
     &fortunes /fortunes
     &fortunes /fortunes
 
 
     # For Lua version of TWFB benchmarks
     # For Lua version of TWFB benchmarks