diff --git a/samples/basic/hash_map/CMakeLists.txt b/samples/basic/hash_map/CMakeLists.txt new file mode 100644 index 00000000000000..12516d8974f4d8 --- /dev/null +++ b/samples/basic/hash_map/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2022 Meta +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(hash_map) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/basic/hash_map/Kconfig b/samples/basic/hash_map/Kconfig new file mode 100644 index 00000000000000..53cd271f6d738a --- /dev/null +++ b/samples/basic/hash_map/Kconfig @@ -0,0 +1,28 @@ +# Copyright (c) 2022 Meta +# +# SPDX-License-Identifier: Apache-2.0 + +config TEST_LIB_HASH_MAP_MAX_ENTRIES + int "Maximum number of Hashmap entries" + default 40 + help + When benchmarking the performance of the Hashmap, it helps to be able + to vary the number of entries to insert or remove from the hash table + in a convenient way. This option translates to MANY in the test sources. + + CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES + + Of course, using realloc(), we are limited by the amount of available + heap memory. For test scenarios using the Minimal C library, the heap + size is controlled via + + CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE + + For native_posix_64, the number of entries can be configured + independently of the arena size since the native libc is used. + +config TEST_LIB_HASH_MAP_DURATION_S + int "Duration of test (in seconds)" + default 3 + +source "Kconfig.zephyr" diff --git a/samples/basic/hash_map/README.rst b/samples/basic/hash_map/README.rst new file mode 100644 index 00000000000000..5126e826d23a93 --- /dev/null +++ b/samples/basic/hash_map/README.rst @@ -0,0 +1,50 @@ +.. _system_hashmap: + +System Hashmap +############## + +Overview +******** + +This is a simple example that repeatedly + +* inserts up to ``CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES`` +* replaces up to the same number that were previously inserted +* removes all previously inserted keys + +Building +******** + +This application can be built on native_posix as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/basic/hash_map + :host-os: unix + :board: native_posix + :goals: build + :compact: + +To build for another board, change "native_posix" above to that board's name. + +Additionally, it is possible to use one of the other Hashmap implementations by specifying + +* ``CONFIG_SYS_HASH_MAP_CHOICE_SC=y`` (Separate Chaining) +* ``CONFIG_SYS_HASH_MAP_CHOICE_OA_LP=y`` (Open Addressing / Linear Probe) +* ``CONFIG_SYS_HASH_MAP_CHOICE_CXX=y`` (C Wrapper around the C++ ``std::unordered_map``) + +To stress the Hashmap implementation, adjust ``CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES``. + +Running +******* + +Run ``build/zephyr/zephyr.exe`` + +Sample Output +************* + +.. code-block:: console + + System Hashmap sample + + [00:00:11.000,000] hashmap_sample: n_insert: 118200 n_remove: 295500 n_replace: 329061 n_miss: 0 n_error: 0 max_size: 118200 + [00:00:11.010,000] hashmap_sample: success diff --git a/samples/basic/hash_map/prj.conf b/samples/basic/hash_map/prj.conf new file mode 100644 index 00000000000000..adc66649c21e30 --- /dev/null +++ b/samples/basic/hash_map/prj.conf @@ -0,0 +1,16 @@ +# Copyright (c) 2022 Meta +# +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_BOOT_BANNER=n +CONFIG_LOG=y + +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=8192 +CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=8192 +CONFIG_PICOLIBC_HEAP_SIZE=8192 + +CONFIG_SYS_HASH_FUNC32=y +CONFIG_SYS_HASH_MAP=y + +CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES=40 diff --git a/samples/basic/hash_map/sample.yaml b/samples/basic/hash_map/sample.yaml new file mode 100644 index 00000000000000..80a04aeaee0abc --- /dev/null +++ b/samples/basic/hash_map/sample.yaml @@ -0,0 +1,58 @@ +# Copyright (c) 2022 Meta +# +# SPDX-License-Identifier: Apache-2.0 + +sample: + description: System Hashmap sample + name: System Hashmap sample + +common: + min_ram: 24 + integration_platforms: + - qemu_x86_64 + - mps2_an385 + harness: console + harness_config: + type: one_line + regex: + - .*success + +tests: + # Minimal Libc + libraries.hash_map.minimal.separate_chaining.djb2: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_SC=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + libraries.hash_map.minimal.open_addressing.djb2: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_OA_LP=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + # Newlib + libraries.hash_map.newlib.separate_chaining.djb2: + extra_configs: + - CONFIG_NEWLIB_LIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_SC=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + libraries.hash_map.newlib.open_addressing.djb2: + extra_configs: + - CONFIG_NEWLIB_LIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_OA_LP=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + libraries.hash_map.newlib.cxx_unordered_map.djb2: + extra_configs: + - CONFIG_NEWLIB_LIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_CXX=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + # PicoLibc + libraries.hash_map.picolibc.separate_chaining.djb2: + extra_configs: + - CONFIG_PICOLIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_SC=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y + libraries.hash_map.picolibc.open_addressing.djb2: + extra_configs: + - CONFIG_PICOLIBC=y + - CONFIG_SYS_HASH_MAP_CHOICE_OA_LP=y + - CONFIG_SYS_HASH_FUNC32_CHOICE_DJB2=y diff --git a/samples/basic/hash_map/src/main.c b/samples/basic/hash_map/src/main.c new file mode 100644 index 00000000000000..d07efd124d49fa --- /dev/null +++ b/samples/basic/hash_map/src/main.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 Meta + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(hashmap_sample); + +SYS_HASHMAP_DEFINE_STATIC(map); + +void print_sys_memory_stats(void); + +struct _stats { + uint64_t n_insert; + uint64_t n_remove; + uint64_t n_replace; + uint64_t n_error; + uint64_t n_miss; + size_t max_size; +}; + +static void print_stats(const struct _stats *stats); + +void main(void) +{ + size_t i; + int ires; + bool bres; + struct _stats stats = {0}; + + printk("CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES: %u\n", CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES); + + do { + for (i = 0; i < CONFIG_TEST_LIB_HASH_MAP_MAX_ENTRIES; ++i) { + + ires = sys_hashmap_insert(&map, i, i, NULL); + if (ires < 0) { + break; + } + + __ASSERT(ires == 1, "Expected to insert %zu", i); + ++stats.n_insert; + ++stats.max_size; + + LOG_DBG("Inserted %zu", i); + + if (k_uptime_get() / MSEC_PER_SEC > CONFIG_TEST_LIB_HASH_MAP_DURATION_S) { + goto out; + } + } + + for (i = 0; i < stats.max_size; ++i) { + + ires = sys_hashmap_insert(&map, i, stats.max_size - i, NULL); + __ASSERT(ires == 0, "Failed to replace %zu", i); + ++stats.n_replace; + + LOG_DBG("Replaced %zu", i); + + if (k_uptime_get() / MSEC_PER_SEC > CONFIG_TEST_LIB_HASH_MAP_DURATION_S) { + goto out; + } + } + + for (i = stats.max_size; i > 0; --i) { + bres = sys_hashmap_remove(&map, i - 1, NULL); + __ASSERT(bres, "Failed to remove %zu", i - 1); + ++stats.n_remove; + + LOG_DBG("Removed %zu", i - 1); + + if (k_uptime_get() / MSEC_PER_SEC > CONFIG_TEST_LIB_HASH_MAP_DURATION_S) { + goto out; + } + } + /* These architectures / boards seem to have trouble with basic timekeeping atm */ + } while (!IS_ENABLED(CONFIG_ARCH_POSIX) && !IS_ENABLED(CONFIG_BOARD_QEMU_NIOS2)); + +out: + + print_stats(&stats); + + sys_hashmap_clear(&map, NULL, NULL); + + LOG_INF("success"); + + if (IS_ENABLED(CONFIG_ARCH_POSIX)) { + exit(0); + } +} + +static void print_stats(const struct _stats *stats) +{ + LOG_INF("n_insert: %" PRIu64 " n_remove: %" PRIu64 " n_replace: %" PRIu64 + " n_miss: %" PRIu64 " n_error: %" PRIu64 " max_size: %zu", + stats->n_insert, stats->n_remove, stats->n_replace, stats->n_miss, stats->n_error, + stats->max_size); +}