]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Implement a zero-allocation, zero-copy visitor for DNSName raw labels
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 5 May 2022 16:49:34 +0000 (18:49 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 1 Jun 2022 09:32:35 +0000 (11:32 +0200)
pdns/dnsname.cc
pdns/dnsname.hh
pdns/speedtest.cc
pdns/test-dnsname_cc.cc

index 31dfa67ef1a3b6c714880a54a7beee325346c22b..1f302e4bb5a915790e48a390a3c138d305cc7849 100644 (file)
@@ -562,3 +562,61 @@ const unsigned char dns_tolower_table[256] = {
   0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
   0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
 };
+
+DNSName::RawLabelsVisitor::RawLabelsVisitor(const DNSName::string_t& storage): d_storage(storage)
+{
+  size_t position = 0;
+  while (position < storage.size()) {
+    auto labelLength = static_cast<unsigned char>(storage.at(position));
+    if (labelLength == 0) {
+      break;
+    }
+    d_labelPositions.at(d_position) = position;
+    d_position++;
+    position += labelLength + 1;
+  }
+}
+
+DNSName::RawLabelsVisitor DNSName::getRawLabelsVisitor() const
+{
+  return DNSName::RawLabelsVisitor(getStorage());
+}
+
+std::string_view DNSName::RawLabelsVisitor::front() const
+{
+  if (d_position == 0) {
+    throw std::out_of_range("trying to access the front of an empty DNSName::RawLabelsVisitor");
+  }
+  uint8_t length = d_storage.at(0);
+  if (length == 0) {
+    return std::string_view();
+  }
+  return std::string_view(&d_storage.at(1), length);
+}
+
+std::string_view DNSName::RawLabelsVisitor::back() const
+{
+  if (d_position == 0) {
+    throw std::out_of_range("trying to access the back of an empty DNSName::RawLabelsVisitor");
+  }
+  size_t offset = d_labelPositions.at(d_position-1);
+  uint8_t length = d_storage.at(offset);
+  if (length == 0) {
+    return std::string_view();
+  }
+  return std::string_view(&d_storage.at(offset + 1), length);
+}
+
+bool DNSName::RawLabelsVisitor::pop_back()
+{
+  if (d_position > 0) {
+    d_position--;
+    return true;
+  }
+  return false;
+}
+
+bool DNSName::RawLabelsVisitor::empty() const
+{
+  return d_position == 0;
+}
index 566b3bf7ffae43997cb5ee0626420d818e4f2c4d..3c3ed3330a5ad259a05056ac29296fab16b275be 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include <array>
 #include <cstring>
 #include <string>
 #include <vector>
@@ -182,6 +183,33 @@ public:
 
   bool has8bitBytes() const; /* returns true if at least one byte of the labels forming the name is not included in [A-Za-z0-9_*./@ \\:-] */
 
+  class RawLabelsVisitor
+  {
+  public:
+    /* Zero-copy, zero-allocation raw labels visitor.
+       The general idea is that we walk the labels in the constructor,
+       filling up our array of labels position and setting the initial
+       value of d_position at the number of labels.
+       We then can easily provide string_view into the first and last label.
+       pop_back() moves d_position one label closer to the start, so we
+       can also easily walk back the labels in reverse order.
+       There is no copy because we use a reference into the DNSName storage,
+       so it is absolutely forbidden to alter the DNSName for as long as we
+       exist, and no allocation because we use a static array (there cannot
+       be more than 128 labels in a DNSName).
+    */
+    RawLabelsVisitor(const string_t& storage);
+    std::string_view front() const;
+    std::string_view back() const;
+    bool pop_back();
+    bool empty() const;
+  private:
+    std::array<uint8_t, 128> d_labelPositions;
+    const string_t& d_storage;
+    size_t d_position{0};
+  };
+  RawLabelsVisitor getRawLabelsVisitor() const;
+
 private:
   string_t d_storage;
 
@@ -396,20 +424,20 @@ struct SuffixMatchTree
       return nullptr;
     }
 
-    auto labels = name.getRawLabels();
-    return lookup(labels);
+    auto visitor = name.getRawLabelsVisitor();
+    return lookup(visitor);
   }
 
-  T* lookup(std::vector<std::string>& labels) const
+  T* lookup(DNSName::RawLabelsVisitor& visitor) const
   {
-    if (labels.empty()) { // optimization
+    if (visitor.empty()) { // optimization
       if (endNode) {
         return &d_value;
       }
       return nullptr;
     }
 
-    SuffixMatchTree smn(*labels.rbegin());
+    SuffixMatchTree smn(std::string(visitor.back()));
     auto child = children.find(smn);
     if (child == children.end()) {
       if(endNode) {
@@ -417,8 +445,8 @@ struct SuffixMatchTree
       }
       return nullptr;
     }
-    labels.pop_back();
-    auto result = child->lookup(labels);
+    visitor.pop_back();
+    auto result = child->lookup(visitor);
     if (result) {
       return result;
     }
index e227aaa49b6686673747608f8d09b7d524483ff4..d4c6a3c638a7de4864e530138a4d50215f0cbf50 100644 (file)
@@ -769,7 +769,33 @@ struct DNSNameRootTest
 
 };
 
+struct SuffixMatchNodeTest
+{
+  SuffixMatchNodeTest()
+  {
+    d_smn.add(d_exist);
+  }
+
+  string getName() const
+  {
+    return "SuffixMatchNode";
+  }
+
+  void operator()() const
+  {
+    if (!d_smn.check(d_exist)) {
+      throw std::runtime_error("Entry not found in SuffixMatchNodeTest");
+    }
+    if (d_smn.check(d_does_not_exist)) {
+      throw std::runtime_error("Non-existent entry found in SuffixMatchNodeTest");
+    }
+  }
 
+private:
+  const DNSName d_exist{"a.bb.ccc.ddd."};
+  const DNSName d_does_not_exist{"e.bb.ccc.ddd"};
+  SuffixMatchNode d_smn;
+};
 
 struct IEqualsTest
 {
@@ -1258,6 +1284,8 @@ try
   doRun(DNSNameParseTest());
   doRun(DNSNameRootTest());
 
+  doRun(SuffixMatchNodeTest());
+
   doRun(NetmaskTreeTest());
 
   doRun(UUIDGenTest());
index 41f882df531fb41e741ff6db1a5071f12d1fea8f..a874649e2cfee752223d866f0ba27f8206fd1895 100644 (file)
@@ -971,6 +971,27 @@ BOOST_AUTO_TEST_CASE(test_getrawlabel) {
   BOOST_CHECK_THROW(name.getRawLabel(name.countLabels()), std::out_of_range);
 }
 
+BOOST_AUTO_TEST_CASE(test_getrawlabels_visitor) {
+  DNSName name("a.bb.ccc.dddd.");
+  auto visitor = name.getRawLabelsVisitor();
+  BOOST_CHECK(!visitor.empty());
+  BOOST_CHECK_EQUAL(visitor.front(), *name.getRawLabels().begin());
+  BOOST_CHECK_EQUAL(visitor.back(), *name.getRawLabels().rbegin());
+
+  BOOST_CHECK_EQUAL(visitor.back(), "dddd");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "ccc");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "bb");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK_EQUAL(visitor.back(), "a");
+  BOOST_CHECK(visitor.pop_back());
+  BOOST_CHECK(visitor.empty());
+  BOOST_CHECK(!visitor.pop_back());
+  BOOST_CHECK_THROW(visitor.front(), std::out_of_range);
+  BOOST_CHECK_THROW(visitor.back(), std::out_of_range);
+}
+
 BOOST_AUTO_TEST_CASE(test_getlastlabel) {
   DNSName name("www.powerdns.com");
   DNSName ans = name.getLastLabel();