From 193c0927cc1c6e45168d84cfd62d592f1a2b2f77 Mon Sep 17 00:00:00 2001 From: "Wenshuai.Song" Date: Tue, 12 Aug 2025 07:38:18 +0800 Subject: [PATCH 1/2] Add compile-time hash table for efficient function dispatch Implement O(1) function lookup using pre-computed hash tables instead of O(n) switch statements. Includes collision resolution and performance monitoring for better scalability with large interfaces. --- erpcgen/src/Generator.cpp | 133 ++++++++++++++++++ erpcgen/src/Generator.hpp | 28 ++++ .../templates/cpp_interface_header.template | 4 +- .../src/templates/cpp_server_header.template | 29 ++++ .../src/templates/cpp_server_source.template | 80 ++++++++--- 5 files changed, 253 insertions(+), 21 deletions(-) diff --git a/erpcgen/src/Generator.cpp b/erpcgen/src/Generator.cpp index 1feae7975..e0579a3b3 100644 --- a/erpcgen/src/Generator.cpp +++ b/erpcgen/src/Generator.cpp @@ -471,6 +471,10 @@ data_list Generator::makeGroupInterfacesTemplateData(Group *group) } } + // Generate compile-time hash table for this interface + data_map hashTableInfo = generateHashTableData(iface, functions); + ifaceInfo["hashTable"] = hashTableInfo; + interfaces.push_back(ifaceInfo); } @@ -759,3 +763,132 @@ void Generator::getCallbacksTemplateData(Group *group, const Interface *iface, d } } } + +data_map Generator::generateHashTableData(Interface *iface, const data_list &functions) +{ + data_map hashTableInfo; + + // Get function count and calculate optimal hash table size + uint32_t functionCount = static_cast(functions.size()); + uint32_t hashTableSize = ERPC_HASH_TABLE_MIN_SIZE; + + // Find next power of 2 that gives target load factor + while (hashTableSize * ERPC_HASH_TABLE_TARGET_LOAD_FACTOR < functionCount && + hashTableSize < ERPC_HASH_TABLE_MAX_SIZE) + { + hashTableSize *= 2; + } + + // Check if we hit the size limit and report performance impact + double loadFactor = static_cast(functionCount) / hashTableSize; + if (hashTableSize >= ERPC_HASH_TABLE_MAX_SIZE && loadFactor > ERPC_HASH_TABLE_TARGET_LOAD_FACTOR) { + Log::warning("Interface '%s': Hash table size limit reached (%d functions in %d-entry table)\n" + "Load factor: %.3f, Expected probes: %.1f\n" + "Consider splitting interface for better performance.\n", + iface->getName().c_str(), functionCount, hashTableSize, + loadFactor, 0.5 * (1 + 1/(1-loadFactor))); + } + + Log::info("Hash table for '%s': size=%d, functions=%d, load_factor=%.3f\n", + iface->getName().c_str(), hashTableSize, functionCount, loadFactor); + + hashTableInfo["size"] = make_data(hashTableSize); + hashTableInfo["functionCount"] = make_data(functionCount); + hashTableInfo["loadFactor"] = make_data(static_cast(functionCount) / hashTableSize); + + // Hash function implementation (Knuth multiplicative method) + auto hash_function_id = [hashTableSize](uint32_t id) -> uint32_t { + uint32_t hash = id; + hash = ((hash >> 16) ^ hash) * 0x45d9f3bU; + hash = ((hash >> 16) ^ hash) * 0x45d9f3bU; + hash = (hash >> 16) ^ hash; + return hash & (hashTableSize - 1); + }; + + // Initialize hash table array + vector hashTable(hashTableSize); + for (uint32_t i = 0; i < hashTableSize; i++) + { + data_map entry; + entry["isEmpty"] = make_data(true); + entry["index"] = make_data(i); + entry["id"] = make_data(0U); + entry["name"] = make_data(string("")); + entry["comment"] = make_data(string("Empty")); + entry["probeDistance"] = make_data(0); + hashTable[i] = entry; + } + + // Place functions in hash table using linear probing + uint32_t collisions = 0; + uint32_t maxProbe = 0; + + for (size_t i = 0; i < functions.size(); ++i) + { + data_ptr funcPtr = functions[i]; + assert(dynamic_cast(funcPtr.get().get())); + DataMap *functionData = dynamic_cast(funcPtr.get().get()); + + // Extract function ID and name from template data + uint32_t functionId = static_cast(stoul(functionData->getmap()["id"]->getvalue())); + string functionName = functionData->getmap()["name"]->getvalue(); + + // Calculate hash position + uint32_t hashPos = hash_function_id(functionId); + uint32_t probeDistance = 0; + + // Linear probing collision resolution + while (!hashTable[hashPos]["isEmpty"]->getvalue().empty() && + hashTable[hashPos]["isEmpty"]->getvalue() != "true") + { + hashPos = (hashPos + 1) % hashTableSize; + probeDistance++; + if (probeDistance > 0 && probeDistance == 1) + { + collisions++; + } + } + + // Insert function into hash table + data_map entry; + entry["isEmpty"] = make_data(false); + entry["index"] = make_data(hashPos); + entry["id"] = make_data(functionId); + entry["name"] = make_data(functionName); + entry["probeDistance"] = make_data(probeDistance); + + if (probeDistance == 0) + { + entry["comment"] = make_data(string("Direct hash")); + } + else + { + entry["comment"] = make_data(string("Collision resolved (probe +") + to_string(probeDistance) + ")"); + } + + hashTable[hashPos] = entry; + + if (probeDistance > maxProbe) + { + maxProbe = probeDistance; + } + } + + // Convert hash table to template data + data_list hashTableEntries; + for (const auto& entry : hashTable) + { + hashTableEntries.push_back(entry); + } + + // Statistics + hashTableInfo["entries"] = hashTableEntries; + hashTableInfo["collisions"] = make_data(collisions); + hashTableInfo["maxProbe"] = make_data(maxProbe); + hashTableInfo["primaryHitRate"] = make_data(static_cast(functionCount - collisions) / functionCount * 100.0); + + Log::info("Generated hash table for interface %s: size=%d, functions=%d, collisions=%d, maxProbe=%d\n", + iface->getName().c_str(), hashTableSize, functionCount, collisions, maxProbe); + + return hashTableInfo; +} diff --git a/erpcgen/src/Generator.hpp b/erpcgen/src/Generator.hpp index 18dfb5a03..accda1e8a 100644 --- a/erpcgen/src/Generator.hpp +++ b/erpcgen/src/Generator.hpp @@ -33,6 +33,22 @@ #include #include +//////////////////////////////////////////////////////////////////////////////// +// Hash Table Configuration +//////////////////////////////////////////////////////////////////////////////// + +/*! @brief Maximum hash table size limit (power of 2) */ +#define ERPC_HASH_TABLE_MAX_SIZE 4096 + +/*! @brief Target load factor for optimal performance */ +#define ERPC_HASH_TABLE_TARGET_LOAD_FACTOR 0.65 + +/*! @brief Warning threshold for load factor */ +#define ERPC_HASH_TABLE_WARNING_LOAD_FACTOR 0.75 + +/*! @brief Minimum hash table size */ +#define ERPC_HASH_TABLE_MIN_SIZE 8 + //////////////////////////////////////////////////////////////////////////////// // Classes //////////////////////////////////////////////////////////////////////////////// @@ -407,6 +423,18 @@ class Generator */ void getCallbacksTemplateData(Group *group, const Interface *iface, cpptempl::data_list &callbackTypesInt, cpptempl::data_list &callbackTypesExt, cpptempl::data_list &callbackTypesAll); + + /*! + * @brief Generate compile-time hash table data for efficient function dispatch. + * + * This function generates hash table data similar to your symbols implementation, + * creating a pre-computed static array for O(1) function lookup. + * + * @param[in] iface Interface to generate hash table for + * @param[in] functions List of functions in the interface + * @return Template data for hash table generation + */ + cpptempl::data_map generateHashTableData(Interface *iface, const cpptempl::data_list &functions); }; } // namespace erpcgen diff --git a/erpcgen/src/templates/cpp_interface_header.template b/erpcgen/src/templates/cpp_interface_header.template index 27c528343..1135647f8 100644 --- a/erpcgen/src/templates/cpp_interface_header.template +++ b/erpcgen/src/templates/cpp_interface_header.template @@ -32,9 +32,9 @@ class {$iface.interfaceClassName} {% endfor %} {%endif %} - static const uint8_t m_serviceId = {$iface.id}; + static inline const uint32_t m_serviceId = {$iface.id}; {% for fn in iface.functions %} - static const uint8_t {$getClassFunctionIdName(fn)} = {$fn.id}; + static inline const uint32_t {$getClassFunctionIdName(fn)} = {$fn.id}; {% endfor -- fn %} virtual ~{$iface.interfaceClassName}(void); diff --git a/erpcgen/src/templates/cpp_server_header.template b/erpcgen/src/templates/cpp_server_header.template index adc9bece9..da2b83b42 100644 --- a/erpcgen/src/templates/cpp_server_header.template +++ b/erpcgen/src/templates/cpp_server_header.template @@ -34,6 +34,35 @@ public: virtual erpc_status_t handleInvocation(uint32_t methodId, uint32_t sequence, erpc::Codec * codec, erpc::MessageBufferFactory *messageFactory, erpc::Transport * transport); private: + /*! @brief Function pointer type for shim functions */ + typedef erpc_status_t ({$iface.serviceClassName}::*ShimFunction)(erpc::{$codecClass} * codec, erpc::MessageBufferFactory *messageFactory, erpc::Transport * transport, uint32_t sequence); + + /*! @brief Compile-time hash table entry structure */ + struct FunctionEntry { + uint32_t id; + ShimFunction func; + const char* name; // For debugging + }; + + /*! @brief Hash table size (power of 2, load factor ~0.65) */ + static constexpr uint32_t HASH_TABLE_SIZE = {$iface.hashTable.size}; + static constexpr uint32_t FUNCTION_COUNT = {$iface.hashTable.functionCount}; + + /*! @brief Compile-time hash function (Knuth multiplicative method) */ + static constexpr uint32_t hash_function_id(uint32_t id) { + uint32_t hash = id; + hash = ((hash >> 16) ^ hash) * 0x45d9f3bU; + hash = ((hash >> 16) ^ hash) * 0x45d9f3bU; + hash = (hash >> 16) ^ hash; + return hash & (HASH_TABLE_SIZE - 1); + } + + /*! @brief Get the pre-computed hash table */ + static const FunctionEntry* getFunctionTable(); + + /*! @brief Find function by ID using compile-time hash table */ + ShimFunction findFunction(uint32_t methodId) const; + {$iface.interfaceClassName} *m_handler; {% for fn in iface.functions %} /*! @brief Server shim for {$fn.name} of {$iface.name} interface. */ diff --git a/erpcgen/src/templates/cpp_server_source.template b/erpcgen/src/templates/cpp_server_source.template index 8667e2bdb..fe9ea18ba 100644 --- a/erpcgen/src/templates/cpp_server_source.template +++ b/erpcgen/src/templates/cpp_server_source.template @@ -206,37 +206,79 @@ static erpc_status_t {$iface.interfaceClassName}_{$cb.name}_shim({$iface.interfa { } +// Get the pre-computed compile-time hash table +const {$iface.serviceClassName}::FunctionEntry* {$iface.serviceClassName}::getFunctionTable() +{ + /* + * Compile-time computed hash table with collision resolution + * Hash table size: {$iface.hashTable.size} + * Function count: {$iface.hashTable.functionCount} + * Load factor: {$iface.hashTable.loadFactor} + * Collisions resolved: {$iface.hashTable.collisions} + * Maximum probe distance: {$iface.hashTable.maxProbe} + * Primary hit rate: {$iface.hashTable.primaryHitRate}% + */ + static const FunctionEntry s_functionTable[HASH_TABLE_SIZE] = { +{% for entry in iface.hashTable.entries %} +{% if entry.isEmpty %} + /* Index {$entry.index} - {$entry.comment} */ + {0U, nullptr, nullptr}, +{% else %} + /* Index {$entry.index} - {$entry.comment} */ + {{$entry.id}U, &{$iface.serviceClassName}::{$entry.name}_shim, "{$entry.name}"}, +{% endif %} +{% endfor -- entry %} + }; + return s_functionTable; +} + +// Find function by ID using compile-time hash table with linear probing +{$iface.serviceClassName}::ShimFunction {$iface.serviceClassName}::findFunction(uint32_t methodId) const +{ + const FunctionEntry* table = getFunctionTable(); + uint32_t hash_pos = hash_function_id(methodId); + uint32_t original_pos = hash_pos; + + // Linear probing search (same as your symbols implementation) + do { + if (table[hash_pos].id == methodId && table[hash_pos].func != nullptr) { + return table[hash_pos].func; + } + if (table[hash_pos].id == 0) { // Empty slot + break; + } + // Move to next slot + hash_pos = (hash_pos + 1) & (HASH_TABLE_SIZE - 1); + } while (hash_pos != original_pos); + + return nullptr; // Not found +} + // return service interface handler. {$iface.interfaceClassName}* {$iface.serviceClassName}::getHandler(void) { return m_handler; } -// Call the correct server shim based on method unique ID. +// Call the correct server shim based on method unique ID using O(1) compile-time hash table lookup. erpc_status_t {$iface.serviceClassName}::handleInvocation(uint32_t methodId, uint32_t sequence, Codec * codec, MessageBufferFactory *messageFactory, Transport * transport) { - erpc_status_t erpcStatus; {% if codecClass != "Codec" %} {$codecClass} *_codec = static_cast<{$codecClass} *>(codec); -{% endif %} - switch (methodId) +{% endif %} + + // O(1) compile-time hash table lookup for function dispatch + ShimFunction shimFunc = findFunction(methodId); + if (shimFunc != nullptr) { -{% for fn in iface.functions %} - case {$iface.interfaceClassName}::{$getClassFunctionIdName(fn)}: - { - erpcStatus = {$fn.name}_shim({%if codecClass == "Codec" %}codec{% else %}_codec{% endif %}, messageFactory, transport, sequence); - break; - } - -{% endfor -- fn %} - default: - { - erpcStatus = kErpcStatus_InvalidArgument; - break; - } + // Call the found shim function using function pointer + return (this->*shimFunc)({%if codecClass == "Codec" %}codec{% else %}_codec{% endif %}, messageFactory, transport, sequence); + } + else + { + // Method ID not found + return kErpcStatus_InvalidArgument; } - - return erpcStatus; } {% for fn in iface.functions %} From 0869e962c94213350a25fcb26becd7389344308e Mon Sep 17 00:00:00 2001 From: "Wenshuai.Song" Date: Tue, 12 Aug 2025 09:08:34 +0800 Subject: [PATCH 2/2] Fix comment test failure in hash table dispatch implementation The test_comments.yml was expecting the original comment pattern "// Call the correct server shim based on method unique ID." but the hash table implementation changed it to include additional details. Restore the original comment to maintain test compatibility while preserving all hash table functionality. --- erpcgen/src/templates/cpp_server_source.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpcgen/src/templates/cpp_server_source.template b/erpcgen/src/templates/cpp_server_source.template index fe9ea18ba..136b039d7 100644 --- a/erpcgen/src/templates/cpp_server_source.template +++ b/erpcgen/src/templates/cpp_server_source.template @@ -260,7 +260,7 @@ const {$iface.serviceClassName}::FunctionEntry* {$iface.serviceClassName}::getFu return m_handler; } -// Call the correct server shim based on method unique ID using O(1) compile-time hash table lookup. +// Call the correct server shim based on method unique ID. erpc_status_t {$iface.serviceClassName}::handleInvocation(uint32_t methodId, uint32_t sequence, Codec * codec, MessageBufferFactory *messageFactory, Transport * transport) { {% if codecClass != "Codec" %}