From 390de6003d0fd15b1f48dd0a42cb287106667958 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 20 Mar 2021 23:06:46 +0100 Subject: [PATCH 1/2] compose: add a cache for parsed files Parsing /usr/share/X11/locale/en_US.UTF-8/Compose takes about 21% of the startup of GLFW on my PinePhone according to perf, this patch lowers it by about 33ms down to approximately 2%. This adds an optional dependency on xxhash. Signed-off-by: Emmanuel Gil Peyrot --- meson.build | 13 +++ meson_options.txt | 6 ++ src/compose/cache.c | 212 +++++++++++++++++++++++++++++++++++++++++++ src/compose/cache.h | 42 +++++++++ src/compose/parser.c | 43 ++++++++- 5 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 src/compose/cache.c create mode 100644 src/compose/cache.h diff --git a/meson.build b/meson.build index 18d16c0ee..9b34cb8b4 100644 --- a/meson.build +++ b/meson.build @@ -152,6 +152,18 @@ have_version_script = cc.links( name: '-Wl,--version-script', ) +if get_option('enable-cache') + xxh3_dep = dependency('libxxhash') + configh_data.set('HAVE_XXHASH', 1) + cache_sources = [ + 'src/compose/cache.c', + 'src/compose/cache.h', + ] +else + configh_data.set('HAVE_XXHASH', 0) + cache_sources = [] +endif + # libxkbcommon. # Note: we use some yacc extensions, which work with either GNU bison @@ -176,6 +188,7 @@ else endif endif libxkbcommon_sources = [ + cache_sources, 'src/compose/parser.c', 'src/compose/parser.h', 'src/compose/paths.c', diff --git a/meson_options.txt b/meson_options.txt index 04982c6a8..ff3fb6ebf 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -67,3 +67,9 @@ option( value: true, description: 'Enable building libxkbregistry', ) +option( + 'enable-cache', + type: 'boolean', + value: true, + description: 'Enable cache support for Compose files', +) diff --git a/src/compose/cache.c b/src/compose/cache.c new file mode 100644 index 000000000..62958b50c --- /dev/null +++ b/src/compose/cache.c @@ -0,0 +1,212 @@ +/* + * Copyright © 2021 Emmanuel Gil Peyrot + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#if !HAVE_XXHASH +#error "libxxhash is required for the compose cache" +#endif + +#include +#include +#define XXH_INLINE_ALL +#include + +#include "table.h" +#include "cache.h" + +static bool +hash_string(const char *string, size_t len, XXH128_canonical_t *out) +{ + XXH3_state_t *state; + XXH_errorcode err; + XXH128_hash_t hash; + + state = XXH3_createState(); + if (!state) + return false; + + err = XXH3_128bits_reset(state); + if (err != XXH_OK) { + XXH3_freeState(state); + return false; + } + + err = XXH3_128bits_update(state, string, len); + if (err != XXH_OK) { + XXH3_freeState(state); + return false; + } + + hash = XXH3_128bits_digest(state); + XXH3_freeState(state); + XXH128_canonicalFromHash(out, hash); + return true; +} + +bool +cache_get_path_from_string(const char *string, size_t len, char **cache_path) +{ + const char *home, *xdg; + char *path = NULL; + char *hash_part; + XXH128_canonical_t hash; + + /* Mostly copied over from xkb_context_include_path_append_default(). */ + home = secure_getenv("HOME"); + xdg = secure_getenv("XDG_CACHE_HOME"); + if (xdg != NULL) { + path = asprintf_safe("%s/xkb/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", xdg); + } else if (home != NULL) { + /* XDG_CACHE_HOME fallback is $HOME/.cache/ */ + path = asprintf_safe("%s/.cache/xkb/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", home); + } + + if (!path) + return false; + + if (!hash_string(string, len, &hash)) { + free(path); + return false; + } + + hash_part = strrchr(path, '/') + 1; + *hash_part = '\0'; + mkdir(path, 0755); + + for (int i = 0; i < 16; i++) { + sprintf(hash_part, "%2.2x", hash.digest[i]); + hash_part += 2; + } + + *cache_path = path; + return true; +} + +bool +cache_read(struct xkb_compose_table *table, const char *path) +{ + FILE *f; + unsigned length; + +#define FREAD(ptr, size, nmemb, stream) \ + do { \ + if (fread(ptr, size, nmemb, stream) < nmemb) \ + return false; \ + } while (0) +#define FREAD_n(ptr, nmemb, stream) \ + FREAD(ptr, sizeof(*ptr), nmemb, stream) +#define FREAD_1(value, stream) \ + FREAD_n(&value, 1, stream) +#define FREAD_DARRAY(array, stream) \ + do { \ + FREAD_1(length, f); \ + darray_resize(array, length); \ + FREAD_n(array.item, length, stream); \ + } while (0) + + f = fopen(path, "rb"); + if (!f) + return false; + + FREAD_1(table->format, f); + if (table->format != XKB_COMPOSE_FORMAT_TEXT_V1) + goto error; + + FREAD_1(table->flags, f); + if (table->flags != XKB_COMPOSE_COMPILE_NO_FLAGS) + goto error; + + FREAD_1(length, f); + table->locale = realloc(table->locale, length + 1); + if (!table->locale) + goto error; + FREAD(table->locale, length, 1, f); + table->locale[length] = '\0'; + fseek(f, (4 - (length % 4)) % 4, SEEK_CUR); + + FREAD_DARRAY(table->utf8, f); + FREAD_DARRAY(table->nodes, f); + +#undef FREAD_DARRAY +#undef FREAD_1 +#undef FREAD_n +#undef FREAD + + fclose(f); + return true; + +error: + fclose(f); + unlink(path); + return false; +} + +void +cache_write(struct xkb_compose_table *table, const char *path) +{ + FILE *f; + unsigned locale_len = strlen(table->locale); + +#define FWRITE(ptr, size, nmemb, stream) \ + do { \ + if (fwrite(ptr, size, nmemb, stream) < nmemb) \ + goto error; \ + } while (0) +#define FWRITE_n(ptr, nmemb, stream) \ + FWRITE(ptr, sizeof(*ptr), nmemb, stream) +#define FWRITE_1(value, stream) \ + FWRITE_n(&value, 1, stream) +#define FWRITE_DARRAY(array, stream) \ + do { \ + FWRITE_1(array.size, f); \ + FWRITE_n(array.item, array.size, stream); \ + } while (0) + + f = fopen(path, "wb"); + if (!f) + return; + + FWRITE_1(table->format, f); + FWRITE_1(table->flags, f); + FWRITE_1(locale_len, f); + FWRITE(table->locale, locale_len, 1, f); + FWRITE("\0\0", (4 - (locale_len % 4)) % 4, 1, f); + FWRITE_DARRAY(table->utf8, f); + FWRITE_DARRAY(table->nodes, f); + + if (fclose(f) != 0) + goto fclose_error; + + return; + +#undef FWRITE_DARRAY +#undef FWRITE_1 +#undef FWRITE_n +#undef FWRITE + +error: + fclose(f); +fclose_error: + unlink(path); +} diff --git a/src/compose/cache.h b/src/compose/cache.h new file mode 100644 index 000000000..4c535b232 --- /dev/null +++ b/src/compose/cache.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2021 Emmanuel Gil Peyrot + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef COMPOSE_CACHE_H +#define COMPOSE_CACHE_H + +#include "config.h" + +#if !HAVE_XXHASH +#error "libxxhash is required for the compose cache" +#endif + +bool +cache_get_path_from_string(const char *string, size_t len, char **cache_path); + +bool +cache_read(struct xkb_compose_table *table, const char *path); + +void +cache_write(struct xkb_compose_table *table, const char *path); + +#endif diff --git a/src/compose/parser.c b/src/compose/parser.c index 0f85a92ee..0b77616ef 100644 --- a/src/compose/parser.c +++ b/src/compose/parser.c @@ -63,6 +63,10 @@ OR PERFORMANCE OF THIS SOFTWARE. #include "utf8.h" #include "parser.h" +#if HAVE_XXHASH +#include "cache.h" +#endif + #define MAX_LHS_LEN 10 #define MAX_INCLUDE_DEPTH 5 @@ -705,9 +709,9 @@ lhs_mod_list_tok: { return true; } -bool -parse_string(struct xkb_compose_table *table, const char *string, size_t len, - const char *file_name) +static bool +do_parse_string(struct xkb_compose_table *table, const char *string, size_t len, + const char *file_name) { struct scanner s; scanner_init(&s, table->ctx, string, len, file_name, NULL); @@ -719,6 +723,39 @@ parse_string(struct xkb_compose_table *table, const char *string, size_t len, return true; } +bool +parse_string(struct xkb_compose_table *table, const char *string, size_t len, + const char *file_name) +{ + int ret; + +#if HAVE_XXHASH + bool usable_cache; + char *cache_path; + + usable_cache = cache_get_path_from_string(string, len, &cache_path); + + /* Attempt to read the table from cache. */ + if (usable_cache && cache_read(table, cache_path)) { + free(cache_path); + return true; + } +#endif + + /* The Compose string wasn’t in cache already. */ + ret = do_parse_string(table, string, len, file_name); + +#if HAVE_XXHASH + /* If everything went well, cache the parsed table. */ + if (usable_cache && ret) + cache_write(table, cache_path); + + free(cache_path); +#endif + + return ret; +} + bool parse_file(struct xkb_compose_table *table, FILE *file, const char *file_name) { From 8c96caf248db043cefd0d5fb5d9e911c9bfb7c1c Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 21 Mar 2021 01:48:48 +0100 Subject: [PATCH 2/2] ci: install or disable Compose cache Signed-off-by: Emmanuel Gil Peyrot --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c86409738..966817ac3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: sudo apt update -y sudo env DEBIAN_FRONTEND=noninteractive apt install -y \ doxygen libxcb-xkb-dev valgrind ninja-build \ - libwayland-dev wayland-protocols bison graphviz + libwayland-dev wayland-protocols bison graphviz libxxhash-dev - name: Setup run: | meson setup build @@ -46,7 +46,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip meson - brew install libxml2 doxygen bison ninja + brew install libxml2 doxygen bison ninja xxhash brew link bison --force env: HOMEBREW_NO_AUTO_UPDATE: 1 @@ -81,7 +81,7 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - meson setup -Denable-wayland=false -Denable-x11=false -Denable-docs=false -Denable-xkbregistry=false build + meson setup -Denable-wayland=false -Denable-x11=false -Denable-docs=false -Denable-xkbregistry=false -Denable-cache=false build env: CC: cl - name: Build