mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +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) |     "substitute", SubstituteFilter, cv.ensure_list(validate_mapping) | ||||||
| ) | ) | ||||||
| async def substitute_filter_to_code(config, filter_id): | async def substitute_filter_to_code(config, filter_id): | ||||||
|     from_strings = [conf[CONF_FROM] for conf in config] |     substitutions = [ | ||||||
|     to_strings = [conf[CONF_TO] for conf in config] |         cg.StructInitializer( | ||||||
|     return cg.new_Pvariable(filter_id, from_strings, to_strings) |             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)) | @FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping)) | ||||||
| async def map_filter_to_code(config, filter_id): | async def map_filter_to_code(config, filter_id): | ||||||
|     map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string) |     mappings = [ | ||||||
|     return cg.new_Pvariable( |         cg.StructInitializer( | ||||||
|         filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config]) |             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="_") | 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; } | optional<std::string> PrependFilter::new_value(std::string value) { return this->prefix_ + value; } | ||||||
|  |  | ||||||
| // Substitute | // Substitute | ||||||
|  | SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &substitutions) | ||||||
|  |     : substitutions_(substitutions) {} | ||||||
|  |  | ||||||
| optional<std::string> SubstituteFilter::new_value(std::string value) { | optional<std::string> SubstituteFilter::new_value(std::string value) { | ||||||
|   std::size_t pos; |   std::size_t pos; | ||||||
|   for (size_t i = 0; i < this->from_strings_.size(); i++) { |   for (const auto &sub : this->substitutions_) { | ||||||
|     while ((pos = value.find(this->from_strings_[i])) != std::string::npos) |     while ((pos = value.find(sub.from)) != std::string::npos) | ||||||
|       value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); |       value.replace(pos, sub.from.size(), sub.to); | ||||||
|   } |   } | ||||||
|   return value; |   return value; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Map | // Map | ||||||
|  | MapFilter::MapFilter(const std::initializer_list<Substitution> &mappings) : mappings_(mappings) {} | ||||||
|  |  | ||||||
| optional<std::string> MapFilter::new_value(std::string value) { | optional<std::string> MapFilter::new_value(std::string value) { | ||||||
|   auto item = mappings_.find(value); |   for (const auto &mapping : this->mappings_) { | ||||||
|   return item == mappings_.end() ? value : item->second; |     if (mapping.from == value) | ||||||
|  |       return mapping.to; | ||||||
|  |   } | ||||||
|  |   return value;  // Pass through if no match | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace text_sensor | }  // namespace text_sensor | ||||||
|   | |||||||
| @@ -2,10 +2,6 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include <queue> |  | ||||||
| #include <utility> |  | ||||||
| #include <map> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace text_sensor { | namespace text_sensor { | ||||||
| @@ -98,26 +94,52 @@ class PrependFilter : public Filter { | |||||||
|   std::string prefix_; |   std::string prefix_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct Substitution { | ||||||
|  |   std::string from; | ||||||
|  |   std::string to; | ||||||
|  | }; | ||||||
|  |  | ||||||
| /// A simple filter that replaces a substring with another substring | /// A simple filter that replaces a substring with another substring | ||||||
| class SubstituteFilter : public Filter { | class SubstituteFilter : public Filter { | ||||||
|  public: |  public: | ||||||
|   SubstituteFilter(std::vector<std::string> from_strings, std::vector<std::string> to_strings) |   explicit SubstituteFilter(const std::initializer_list<Substitution> &substitutions); | ||||||
|       : from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {} |  | ||||||
|   optional<std::string> new_value(std::string value) override; |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<std::string> from_strings_; |   FixedVector<Substitution> substitutions_; | ||||||
|   std::vector<std::string> to_strings_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// 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 { | class MapFilter : public Filter { | ||||||
|  public: |  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; |   optional<std::string> new_value(std::string value) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::map<std::string, std::string> mappings_; |   FixedVector<Substitution> mappings_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace text_sensor | }  // namespace text_sensor | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user