Improve performance of SliceTransform::AsString (#9401)

Summary:
1. Removed the options from the Capped/Fixed SliceTransforms.  Instead these classes are created with id.number.  This allows the GetID() id to be calculated and stored at class construction time.  This change puts the construction back to similar to how it was prior to the Customizable changes for SliceTransform.

2.  Improve the performance of AsString by using the ID only if there are no option properties (which is the case for all of the builtin transforms).

Ran tests of calling AsString in a loop 5M times and found approximately a 10x performance increase vs the original code.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/9401

Reviewed By: pdillinger

Differential Revision: D33668672

Pulled By: mrambacher

fbshipit-source-id: d0075912c6ece8ed754ee543bc6b0b49a169b309
This commit is contained in:
mrambacher 2022-01-27 10:04:35 -08:00 committed by Facebook GitHub Bot
parent 92822655fd
commit 37ec9d0c12
4 changed files with 83 additions and 44 deletions

View File

@ -388,6 +388,9 @@ class Configurable {
const std::string& name, void* opt_ptr, const std::string& name, void* opt_ptr,
const std::unordered_map<std::string, OptionTypeInfo>* opt_map); const std::unordered_map<std::string, OptionTypeInfo>* opt_map);
// Returns true if there are registered options for this Configurable object
inline bool HasRegisteredOptions() const { return !options_.empty(); }
private: private:
// Contains the collection of options (name, opt_ptr, opt_map) associated with // Contains the collection of options (name, opt_ptr, opt_map) associated with
// this object. This collection is typically set in the constructor of the // this object. This collection is typically set in the constructor of the

View File

@ -47,7 +47,7 @@ class SliceTransform : public Customizable {
std::shared_ptr<const SliceTransform>* result); std::shared_ptr<const SliceTransform>* result);
// Returns a string representation of this SliceTransform, representing the ID // Returns a string representation of this SliceTransform, representing the ID
// and any additional properties // and any additional properties.
std::string AsString() const; std::string AsString() const;
// Extract a prefix from a specified key. This method is called when // Extract a prefix from a specified key. This method is called when

View File

@ -2731,22 +2731,49 @@ TEST_F(OptionsTest, SliceTransformCreateFromString) {
SliceTransform::CreateFromString(config_options, "fixed", &transform)); SliceTransform::CreateFromString(config_options, "fixed", &transform));
ASSERT_NOK( ASSERT_NOK(
SliceTransform::CreateFromString(config_options, "capped", &transform)); SliceTransform::CreateFromString(config_options, "capped", &transform));
ASSERT_NOK(
SliceTransform::CreateFromString(config_options, "fixed:", &transform));
ASSERT_NOK(
SliceTransform::CreateFromString(config_options, "capped:", &transform));
ASSERT_NOK(SliceTransform::CreateFromString( ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.FixedPrefix:42", &transform)); config_options, "rocksdb.FixedPrefix:42", &transform));
ASSERT_NOK(SliceTransform::CreateFromString( ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.CappedPrefix:42", &transform)); config_options, "rocksdb.CappedPrefix:42", &transform));
ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.FixedPrefix", &transform));
ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.CappedPrefix", &transform));
ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.FixedPrefix.", &transform));
ASSERT_NOK(SliceTransform::CreateFromString(
config_options, "rocksdb.CappedPrefix.", &transform));
ASSERT_NOK( ASSERT_NOK(
SliceTransform::CreateFromString(config_options, "invalid", &transform)); SliceTransform::CreateFromString(config_options, "invalid", &transform));
#ifndef ROCKSDB_LITE #ifndef ROCKSDB_LITE
ASSERT_OK(SliceTransform::CreateFromString( ASSERT_OK(SliceTransform::CreateFromString(
config_options, "id=rocksdb.CappedPrefix; length=11", &transform)); config_options, "rocksdb.CappedPrefix.11", &transform));
ASSERT_NE(transform, nullptr); ASSERT_NE(transform, nullptr);
ASSERT_EQ(transform->GetId(), "rocksdb.CappedPrefix.11"); ASSERT_EQ(transform->GetId(), "rocksdb.CappedPrefix.11");
ASSERT_TRUE(transform->IsInstanceOf("capped"));
ASSERT_TRUE(transform->IsInstanceOf("capped:11"));
ASSERT_TRUE(transform->IsInstanceOf("rocksdb.CappedPrefix"));
ASSERT_TRUE(transform->IsInstanceOf("rocksdb.CappedPrefix.11"));
ASSERT_FALSE(transform->IsInstanceOf("fixed"));
ASSERT_FALSE(transform->IsInstanceOf("fixed:11"));
ASSERT_FALSE(transform->IsInstanceOf("rocksdb.FixedPrefix"));
ASSERT_FALSE(transform->IsInstanceOf("rocksdb.FixedPrefix.11"));
ASSERT_NOK(SliceTransform::CreateFromString( ASSERT_OK(SliceTransform::CreateFromString(
config_options, "id=rocksdb.CappedPrefix; length=11; invalid=true", config_options, "rocksdb.FixedPrefix.11", &transform));
&transform)); ASSERT_TRUE(transform->IsInstanceOf("fixed"));
ASSERT_TRUE(transform->IsInstanceOf("fixed:11"));
ASSERT_TRUE(transform->IsInstanceOf("rocksdb.FixedPrefix"));
ASSERT_TRUE(transform->IsInstanceOf("rocksdb.FixedPrefix.11"));
ASSERT_FALSE(transform->IsInstanceOf("capped"));
ASSERT_FALSE(transform->IsInstanceOf("capped:11"));
ASSERT_FALSE(transform->IsInstanceOf("rocksdb.CappedPrefix"));
ASSERT_FALSE(transform->IsInstanceOf("rocksdb.CappedPrefix.11"));
#endif // ROCKSDB_LITE #endif // ROCKSDB_LITE
} }

View File

@ -22,22 +22,16 @@
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
namespace { namespace {
static std::unordered_map<std::string, OptionTypeInfo>
slice_transform_length_info = {
#ifndef ROCKSDB_LITE
{"length",
{0, OptionType::kSizeT, OptionVerificationType::kNormal,
OptionTypeFlags::kDontSerialize | OptionTypeFlags::kCompareNever}},
#endif // ROCKSDB_LITE
};
class FixedPrefixTransform : public SliceTransform { class FixedPrefixTransform : public SliceTransform {
private: private:
size_t prefix_len_; size_t prefix_len_;
std::string id_;
public: public:
explicit FixedPrefixTransform(size_t prefix_len) : prefix_len_(prefix_len) { explicit FixedPrefixTransform(size_t prefix_len) : prefix_len_(prefix_len) {
RegisterOptions(Name(), &prefix_len_, &slice_transform_length_info); id_ = std::string(kClassName()) + "." +
ROCKSDB_NAMESPACE::ToString(prefix_len_);
} }
static const char* kClassName() { return "rocksdb.FixedPrefix"; } static const char* kClassName() { return "rocksdb.FixedPrefix"; }
@ -45,10 +39,21 @@ class FixedPrefixTransform : public SliceTransform {
const char* Name() const override { return kClassName(); } const char* Name() const override { return kClassName(); }
const char* NickName() const override { return kNickName(); } const char* NickName() const override { return kNickName(); }
std::string GetId() const override { bool IsInstanceOf(const std::string& name) const override {
return std::string(Name()) + "." + ROCKSDB_NAMESPACE::ToString(prefix_len_); if (name == id_) {
return true;
} else if (StartsWith(name, kNickName())) {
std::string alt_id = std::string(kNickName()) + ":" +
ROCKSDB_NAMESPACE::ToString(prefix_len_);
if (name == alt_id) {
return true;
}
}
return SliceTransform::IsInstanceOf(name);
} }
std::string GetId() const override { return id_; }
Slice Transform(const Slice& src) const override { Slice Transform(const Slice& src) const override {
assert(InDomain(src)); assert(InDomain(src));
return Slice(src.data(), prefix_len_); return Slice(src.data(), prefix_len_);
@ -75,18 +80,31 @@ class FixedPrefixTransform : public SliceTransform {
class CappedPrefixTransform : public SliceTransform { class CappedPrefixTransform : public SliceTransform {
private: private:
size_t cap_len_; size_t cap_len_;
std::string id_;
public: public:
explicit CappedPrefixTransform(size_t cap_len) : cap_len_(cap_len) { explicit CappedPrefixTransform(size_t cap_len) : cap_len_(cap_len) {
RegisterOptions(Name(), &cap_len_, &slice_transform_length_info); id_ =
std::string(kClassName()) + "." + ROCKSDB_NAMESPACE::ToString(cap_len_);
} }
static const char* kClassName() { return "rocksdb.CappedPrefix"; } static const char* kClassName() { return "rocksdb.CappedPrefix"; }
static const char* kNickName() { return "capped"; } static const char* kNickName() { return "capped"; }
const char* Name() const override { return kClassName(); } const char* Name() const override { return kClassName(); }
const char* NickName() const override { return kNickName(); } const char* NickName() const override { return kNickName(); }
std::string GetId() const override { std::string GetId() const override { return id_; }
return std::string(Name()) + "." + ROCKSDB_NAMESPACE::ToString(cap_len_);
bool IsInstanceOf(const std::string& name) const override {
if (name == id_) {
return true;
} else if (StartsWith(name, kNickName())) {
std::string alt_id = std::string(kNickName()) + ":" +
ROCKSDB_NAMESPACE::ToString(cap_len_);
if (name == alt_id) {
return true;
}
}
return SliceTransform::IsInstanceOf(name);
} }
Slice Transform(const Slice& src) const override { Slice Transform(const Slice& src) const override {
@ -144,8 +162,7 @@ const SliceTransform* NewNoopTransform() { return new NoopTransform; }
static int RegisterBuiltinSliceTransform(ObjectLibrary& library, static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
const std::string& /*arg*/) { const std::string& /*arg*/) {
// For the builtin transforms, the format is typically // For the builtin transforms, the format is typically
// [Name] or [Name].[0-9]+ // [Name].[0-9]+ or [NickName]:[0-9]+
// [NickName]:[0-9]+
library.AddFactory<const SliceTransform>( library.AddFactory<const SliceTransform>(
NoopTransform::kClassName(), NoopTransform::kClassName(),
[](const std::string& /*uri*/, [](const std::string& /*uri*/,
@ -165,17 +182,13 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
return guard->get(); return guard->get();
}); });
library.AddFactory<const SliceTransform>( library.AddFactory<const SliceTransform>(
ObjectLibrary::PatternEntry(FixedPrefixTransform::kClassName(), true) ObjectLibrary::PatternEntry(FixedPrefixTransform::kClassName(), false)
.AddNumber("."), .AddNumber("."),
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard, [](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
std::string* /*errmsg*/) { std::string* /*errmsg*/) {
if (uri == FixedPrefixTransform::kClassName()) { auto len = ParseSizeT(
guard->reset(NewFixedPrefixTransform(0)); uri.substr(strlen(FixedPrefixTransform::kClassName()) + 1));
} else { guard->reset(NewFixedPrefixTransform(len));
auto len = ParseSizeT(
uri.substr(strlen(FixedPrefixTransform::kClassName()) + 1));
guard->reset(NewFixedPrefixTransform(len));
}
return guard->get(); return guard->get();
}); });
library.AddFactory<const SliceTransform>( library.AddFactory<const SliceTransform>(
@ -189,17 +202,13 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
return guard->get(); return guard->get();
}); });
library.AddFactory<const SliceTransform>( library.AddFactory<const SliceTransform>(
ObjectLibrary::PatternEntry(CappedPrefixTransform::kClassName(), true) ObjectLibrary::PatternEntry(CappedPrefixTransform::kClassName(), false)
.AddNumber("."), .AddNumber("."),
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard, [](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
std::string* /*errmsg*/) { std::string* /*errmsg*/) {
if (uri == CappedPrefixTransform::kClassName()) { auto len = ParseSizeT(
guard->reset(NewCappedPrefixTransform(0)); uri.substr(strlen(CappedPrefixTransform::kClassName()) + 1));
} else { guard->reset(NewCappedPrefixTransform(len));
auto len = ParseSizeT(
uri.substr(strlen(CappedPrefixTransform::kClassName()) + 1));
guard->reset(NewCappedPrefixTransform(len));
}
return guard->get(); return guard->get();
}); });
size_t num_types; size_t num_types;
@ -270,13 +279,13 @@ Status SliceTransform::CreateFromString(
} }
std::string SliceTransform::AsString() const { std::string SliceTransform::AsString() const {
#ifndef ROCKSDB_LITE if (HasRegisteredOptions()) {
ConfigOptions config_options; ConfigOptions opts;
config_options.delimiter = ";"; opts.delimiter = ";";
return ToString(config_options); return ToString(opts);
#else } else {
return GetId(); return GetId();
#endif // ROCKSDB_LITE }
} }
// 2 small internal utility functions, for efficient hex conversions // 2 small internal utility functions, for efficient hex conversions