reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 221fc278a21371863bd652ab2c7b830e93cfed73
parent 6c3ed15525fc1d62390cdeed8957207cd18a9de3
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Thu,  6 Jan 2022 22:13:31 -0500

filter: reduce string memory allocations

Store all tokens as one copy of the input string partially converted to lowercase.

Diffstat:
Msrc/filter.cpp | 38+++++++++++++++++++++++++-------------
Msrc/filter.hpp | 16++++++----------
Mtest/filter.cpp | 33+--------------------------------
3 files changed, 32 insertions(+), 55 deletions(-)

diff --git a/src/filter.cpp b/src/filter.cpp @@ -33,16 +33,16 @@ void Filter::set(const std::string &input) m_input = input; m_root.clear(); - std::string buf; + std::string_view buf; char quote = 0; int flags = 0; Group *group = &m_root; - for(size_t i = 0; i < input.size(); ++i) { - const char c = input[i]; + for(size_t i = 0; i < m_input.size(); ++i) { + const char &c = m_input[i]; const bool isStart = buf.empty(), - isEnd = i+1 == input.size() || input[i+1] == '\x20'; + isEnd = i+1 == m_input.size() || m_input[i+1] == '\x20'; if((c == '"' || c == '\'') && ((!quote && isStart) || quote == c)) { if(quote) @@ -58,7 +58,7 @@ void Filter::set(const std::string &input) flags &= ~Node::FullWordFlag; else { group = group->push(buf, &flags); - buf.clear(); + buf = {}; continue; } } @@ -75,11 +75,14 @@ void Filter::set(const std::string &input) // force-close the token after having parsed a closing quote // and only after having parsed all trailing anchors group = group->push(buf, &flags); - buf.clear(); + buf = {}; } } - buf += c; + if(buf.empty()) + buf = { &c, 1 }; + else + buf = { buf.data(), buf.size() + 1 }; } group->push(buf, &flags); @@ -93,12 +96,19 @@ bool Filter::match(std::vector<std::string> rows) const return m_root.match(rows); } +static void convertToLower(const std::string_view &buf) +{ + char *data = const_cast<char *>(buf.data()); + std::transform(data, data + buf.size(), data, + [](unsigned char c){ return std::tolower(c); }); +} + Filter::Group::Group(Type type, int flags, Group *parent) : Node(flags), m_parent(parent), m_type(type) { } -Filter::Group *Filter::Group::push(const std::string &buf, int *flags) +Filter::Group *Filter::Group::push(const std::string_view &buf, int *flags) { if(buf.empty()) return this; @@ -141,6 +151,7 @@ Filter::Group *Filter::Group::push(const std::string &buf, int *flags) return this; } + convertToLower(buf); m_nodes.push_back(std::make_unique<Token>(buf, *flags)); *flags = 0; @@ -159,9 +170,9 @@ Filter::Group *Filter::Group::addSubGroup(const Type type, const int flags) return ptr; } -bool Filter::Group::pushSynonyms(const std::string &buf, int *flags) +bool Filter::Group::pushSynonyms(const std::string_view &buf, int *flags) { - static const std::vector<std::string> synonyms[] { + static const std::vector<std::string_view> synonyms[] { { "open", "display", "view", "show", "hide" }, { "delete", "clear", "remove", "erase" }, { "insert", "add" }, @@ -190,8 +201,10 @@ bool Filter::Group::pushSynonyms(const std::string &buf, int *flags) notGroup = this; Group *orGroup = notGroup->addSubGroup(MatchAny, 0); - if(!(*flags & FullWordFlag)) + if(!(*flags & FullWordFlag)) { + convertToLower(buf); orGroup->m_nodes.push_back(std::make_unique<Token>(buf, *flags)); + } for(const auto &word : *match) orGroup->m_nodes.push_back(std::make_unique<Token>(word, *flags | FullWordFlag)); @@ -213,10 +226,9 @@ bool Filter::Group::match(const std::vector<std::string> &rows) const return m_type == MatchAll && !test(NotFlag); } -Filter::Token::Token(const std::string &buf, int flags) +Filter::Token::Token(const std::string_view &buf, int flags) : Node(flags), m_buf(buf) { - boost::algorithm::to_lower(m_buf); } bool Filter::Token::match(const std::vector<std::string> &rows) const diff --git a/src/filter.hpp b/src/filter.hpp @@ -20,21 +20,17 @@ #include <memory> #include <string> +#include <string_view> #include <vector> class Filter { public: Filter(const std::string & = {}); - - const std::string get() const { return m_input; } void set(const std::string &); + Filter &operator=(const std::string &f) { set(f); return *this; } bool match(std::vector<std::string> rows) const; - Filter &operator=(const std::string &f) { set(f); return *this; } - bool operator==(const std::string &f) const { return m_input == f; } - bool operator!=(const std::string &f) const { return !(*this == f); } - private: class Node { public: @@ -65,13 +61,13 @@ private: Group(Type type, int flags = 0, Group *parent = nullptr); void clear() { m_nodes.clear(); } - Group *push(const std::string &, int *flags); + Group *push(const std::string_view &, int *flags); bool match(const std::vector<std::string> &) const override; private: Group *addSubGroup(Type, int flags); - bool pushSynonyms(const std::string &, int *flags); + bool pushSynonyms(const std::string_view &, int *flags); Group *m_parent; Type m_type; @@ -80,12 +76,12 @@ private: class Token : public Node { public: - Token(const std::string &buf, int flags); + Token(const std::string_view &buf, int flags); bool match(const std::vector<std::string> &) const override; bool matchRow(const std::string &) const; private: - std::string m_buf; + std::string_view m_buf; }; std::string m_input; diff --git a/test/filter.cpp b/test/filter.cpp @@ -10,43 +10,11 @@ TEST_CASE("basic matching", M) { REQUIRE(f.match({"world"})); f.set("hello"); - REQUIRE(f.match({"hello"})); REQUIRE(f.match({"HELLO"})); REQUIRE_FALSE(f.match({"world"})); } -TEST_CASE("get/set filter", M) { - Filter f; - REQUIRE(f.get().empty()); - - f.set("hello"); - REQUIRE(f.get() == "hello"); - REQUIRE(f.match({"hello"})); - - f.set("world"); - REQUIRE(f.get() == "world"); - REQUIRE(f.match({"world"})); -} - -TEST_CASE("filter operators", M) { - SECTION("assignment") { - Filter f; - f = "hello"; - REQUIRE(f.get() == "hello"); - } - - SECTION("equal") { - REQUIRE(Filter("hello") == "hello"); - REQUIRE_FALSE(Filter("hello") == "world"); - } - - SECTION("not equal") { - REQUIRE_FALSE(Filter("hello") != "hello"); - REQUIRE(Filter("hello") != "world"); - } -} - TEST_CASE("word matching", M) { Filter f; f.set("hello world"); @@ -376,6 +344,7 @@ TEST_CASE("synonymous words", M) { SECTION("case-insensitive") { f.set("OPEN"); + REQUIRE(f.match({"opening"})); REQUIRE(f.match({"display"})); }