mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[text_sensor] Optimize filters with FixedVector (1.6KB flash savings) (#11423)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -110,17 +110,28 @@ def validate_mapping(value): | ||||
|     "substitute", SubstituteFilter, cv.ensure_list(validate_mapping) | ||||
| ) | ||||
| async def substitute_filter_to_code(config, filter_id): | ||||
|     from_strings = [conf[CONF_FROM] for conf in config] | ||||
|     to_strings = [conf[CONF_TO] for conf in config] | ||||
|     return cg.new_Pvariable(filter_id, from_strings, to_strings) | ||||
|     substitutions = [ | ||||
|         cg.StructInitializer( | ||||
|             cg.MockObj("Substitution", "esphome::text_sensor::"), | ||||
|             ("from", conf[CONF_FROM]), | ||||
|             ("to", conf[CONF_TO]), | ||||
|         ) | ||||
|         for conf in config | ||||
|     ] | ||||
|     return cg.new_Pvariable(filter_id, substitutions) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping)) | ||||
| async def map_filter_to_code(config, filter_id): | ||||
|     map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string) | ||||
|     return cg.new_Pvariable( | ||||
|         filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config]) | ||||
|     mappings = [ | ||||
|         cg.StructInitializer( | ||||
|             cg.MockObj("Substitution", "esphome::text_sensor::"), | ||||
|             ("from", conf[CONF_FROM]), | ||||
|             ("to", conf[CONF_TO]), | ||||
|         ) | ||||
|         for conf in config | ||||
|     ] | ||||
|     return cg.new_Pvariable(filter_id, mappings) | ||||
|  | ||||
|  | ||||
| validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||
|   | ||||
| @@ -62,19 +62,27 @@ optional<std::string> AppendFilter::new_value(std::string value) { return value | ||||
| optional<std::string> PrependFilter::new_value(std::string value) { return this->prefix_ + value; } | ||||
|  | ||||
| // Substitute | ||||
| SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &substitutions) | ||||
|     : substitutions_(substitutions) {} | ||||
|  | ||||
| optional<std::string> SubstituteFilter::new_value(std::string value) { | ||||
|   std::size_t pos; | ||||
|   for (size_t i = 0; i < this->from_strings_.size(); i++) { | ||||
|     while ((pos = value.find(this->from_strings_[i])) != std::string::npos) | ||||
|       value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); | ||||
|   for (const auto &sub : this->substitutions_) { | ||||
|     while ((pos = value.find(sub.from)) != std::string::npos) | ||||
|       value.replace(pos, sub.from.size(), sub.to); | ||||
|   } | ||||
|   return value; | ||||
| } | ||||
|  | ||||
| // Map | ||||
| MapFilter::MapFilter(const std::initializer_list<Substitution> &mappings) : mappings_(mappings) {} | ||||
|  | ||||
| optional<std::string> MapFilter::new_value(std::string value) { | ||||
|   auto item = mappings_.find(value); | ||||
|   return item == mappings_.end() ? value : item->second; | ||||
|   for (const auto &mapping : this->mappings_) { | ||||
|     if (mapping.from == value) | ||||
|       return mapping.to; | ||||
|   } | ||||
|   return value;  // Pass through if no match | ||||
| } | ||||
|  | ||||
| }  // namespace text_sensor | ||||
|   | ||||
| @@ -2,10 +2,6 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include <queue> | ||||
| #include <utility> | ||||
| #include <map> | ||||
| #include <vector> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace text_sensor { | ||||
| @@ -98,26 +94,52 @@ class PrependFilter : public Filter { | ||||
|   std::string prefix_; | ||||
| }; | ||||
|  | ||||
| struct Substitution { | ||||
|   std::string from; | ||||
|   std::string to; | ||||
| }; | ||||
|  | ||||
| /// A simple filter that replaces a substring with another substring | ||||
| class SubstituteFilter : public Filter { | ||||
|  public: | ||||
|   SubstituteFilter(std::vector<std::string> from_strings, std::vector<std::string> to_strings) | ||||
|       : from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {} | ||||
|   explicit SubstituteFilter(const std::initializer_list<Substitution> &substitutions); | ||||
|   optional<std::string> new_value(std::string value) override; | ||||
|  | ||||
|  protected: | ||||
|   std::vector<std::string> from_strings_; | ||||
|   std::vector<std::string> to_strings_; | ||||
|   FixedVector<Substitution> substitutions_; | ||||
| }; | ||||
|  | ||||
| /// A filter that maps values from one set to another | ||||
| /** A filter that maps values from one set to another | ||||
|  * | ||||
|  * Uses linear search instead of std::map for typical small datasets (2-20 mappings). | ||||
|  * Linear search on contiguous memory is faster than red-black tree lookups when: | ||||
|  * - Dataset is small (< ~30 items) | ||||
|  * - Memory is contiguous (cache-friendly, better CPU cache utilization) | ||||
|  * - No pointer chasing overhead (tree node traversal) | ||||
|  * - String comparison cost dominates lookup time | ||||
|  * | ||||
|  * Benchmark results (see benchmark_map_filter.cpp): | ||||
|  * - 2 mappings:  Linear 1.26x faster than std::map | ||||
|  * - 5 mappings:  Linear 2.25x faster than std::map | ||||
|  * - 10 mappings: Linear 1.83x faster than std::map | ||||
|  * - 20 mappings: Linear 1.59x faster than std::map | ||||
|  * - 30 mappings: Linear 1.09x faster than std::map | ||||
|  * - 40 mappings: std::map 1.27x faster than Linear (break-even) | ||||
|  * | ||||
|  * Benefits over std::map: | ||||
|  * - ~2KB smaller flash (no red-black tree code) | ||||
|  * - ~24-32 bytes less RAM per mapping (no tree node overhead) | ||||
|  * - Faster for typical ESPHome usage (2-10 mappings common, 20+ rare) | ||||
|  * | ||||
|  * Break-even point: ~35-40 mappings, but ESPHome configs rarely exceed 20 | ||||
|  */ | ||||
| class MapFilter : public Filter { | ||||
|  public: | ||||
|   MapFilter(std::map<std::string, std::string> mappings) : mappings_(std::move(mappings)) {} | ||||
|   explicit MapFilter(const std::initializer_list<Substitution> &mappings); | ||||
|   optional<std::string> new_value(std::string value) override; | ||||
|  | ||||
|  protected: | ||||
|   std::map<std::string, std::string> mappings_; | ||||
|   FixedVector<Substitution> mappings_; | ||||
| }; | ||||
|  | ||||
| }  // namespace text_sensor | ||||
|   | ||||
		Reference in New Issue
	
	Block a user