diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp b/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp index d8650601ba..ed4488267a 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomValue.cpp @@ -61,6 +61,11 @@ namespace AZ::Dom return m_children; } + Value::Value(AZStd::shared_ptr string) + : m_value(string) + { + } + Value::Value() { } @@ -895,6 +900,11 @@ namespace AZ::Dom m_value = aznumeric_cast(value); } + void Value::SetString(AZStd::shared_ptr string) + { + m_value = string; + } + AZStd::string_view Value::GetString() const { switch (m_value.index()) @@ -992,7 +1002,7 @@ namespace AZ::Dom } else if constexpr (AZStd::is_same_v>) { - result = visitor.String(*arg, copyStrings ? Lifetime::Temporary : Lifetime::Persistent); + result = visitor.RefCountedString(arg, copyStrings ? Lifetime::Temporary : Lifetime::Persistent); } else if constexpr (AZStd::is_same_v) { diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValue.h b/Code/Framework/AzCore/AzCore/DOM/DomValue.h index 9399e10d15..ac6a9d0c51 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValue.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomValue.h @@ -127,6 +127,7 @@ namespace AZ::Dom Value(const Value&); Value(Value&&) noexcept; Value(AZStd::string_view string, bool copy); + Value(AZStd::shared_ptr string); Value(int32_t value); Value(uint32_t value); @@ -281,6 +282,7 @@ namespace AZ::Dom AZStd::string_view GetString() const; size_t GetStringLength() const; void SetString(AZStd::string_view); + void SetString(AZStd::shared_ptr); void CopyFromString(AZStd::string_view); // opaque type API... diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.cpp b/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.cpp index cf46a77d54..a8cc1f3947 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.cpp @@ -68,6 +68,12 @@ namespace AZ::Dom return FinishWrite(); } + Visitor::Result ValueWriter::RefCountedString(AZStd::shared_ptr value, [[maybe_unused]] Lifetime lifetime) + { + CurrentValue().SetString(value); + return FinishWrite(); + } + Visitor::Result ValueWriter::StartObject() { CurrentValue().SetObject(); diff --git a/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.h b/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.h index 617184a926..4c1d5a26ee 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomValueWriter.h @@ -26,6 +26,7 @@ namespace AZ::Dom Result Double(double value) override; Result String(AZStd::string_view value, Lifetime lifetime) override; + Result RefCountedString(AZStd::shared_ptr value, Lifetime lifetime) override; Result StartObject() override; Result EndObject(AZ::u64 attributeCount) override; Result Key(AZ::Name key) override; diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp index ad314da385..af64c4cea8 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp @@ -105,6 +105,11 @@ namespace AZ::Dom return VisitorSuccess(); } + Visitor::Result Visitor::RefCountedString(AZStd::shared_ptr value, Lifetime lifetime) + { + return String(*value, lifetime); + } + Visitor::Result Visitor::OpaqueValue([[maybe_unused]] const OpaqueType& value, [[maybe_unused]] Lifetime lifetime) { if (!SupportsOpaqueValues()) diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h index bbe78131c3..3439a566e2 100644 --- a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace AZ::Dom { @@ -170,6 +171,7 @@ namespace AZ::Dom //! Operates on a string value. As strings are a reference type. //! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy. virtual Result String(AZStd::string_view value, Lifetime lifetime); + virtual Result RefCountedString(AZStd::shared_ptr value, Lifetime lifetime); //! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to //! indicate where the value may be stored persistently or requires a copy. //! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special diff --git a/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp index 3e6143e805..98e62f4aa6 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp @@ -50,7 +50,7 @@ namespace Benchmark UnitTest::AllocatorsBenchmarkFixture::TearDown(st); } - AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength) + rapidjson::Document GenerateDomJsonBenchmarkDocument(int64_t entryCount, int64_t stringTemplateLength) { rapidjson::Document document; document.SetObject(); @@ -108,6 +108,13 @@ namespace Benchmark document.SetObject(); document.AddMember("entries", createObject(), document.GetAllocator()); + return document; + } + + AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength) + { + rapidjson::Document document = GenerateDomJsonBenchmarkDocument(entryCount, stringTemplateLength); + AZStd::string serializedJson; auto result = AZ::JsonSerializationUtils::WriteJsonString(document, serializedJson); AZ_Assert(result.IsSuccess(), "Failed to serialize generated JSON"); @@ -124,7 +131,7 @@ namespace Benchmark ->Args({ 100, 500 }) \ ->Unit(benchmark::kMillisecond); - BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocumentInPlace)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToRapidjsonInPlace)(benchmark::State& state) { AZ::Dom::JsonBackend backend; AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); @@ -146,9 +153,9 @@ namespace Benchmark state.SetBytesProcessed(serializedPayload.size() * state.iterations()); } - BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocumentInPlace) + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToRapidjsonInPlace) - BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDomValueInPlace)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToAzDomValueInPlace)(benchmark::State& state) { AZ::Dom::JsonBackend backend; AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); @@ -170,9 +177,9 @@ namespace Benchmark state.SetBytesProcessed(serializedPayload.size() * state.iterations()); } - BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDomValueInPlace) + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToAzDomValueInPlace) - BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocument)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToRapidjson)(benchmark::State& state) { AZ::Dom::JsonBackend backend; AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); @@ -190,9 +197,29 @@ namespace Benchmark state.SetBytesProcessed(serializedPayload.size() * state.iterations()); } - BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocument) + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToRapidjson) - BENCHMARK_DEFINE_F(DomJsonBenchmark, JsonUtilsDeserializeToDocument)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomJsonBenchmark, AzDomDeserializeToAzDomValue)(benchmark::State& state) + { + AZ::Dom::JsonBackend backend; + AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + auto result = AZ::Dom::Utils::WriteToValue( + [&](AZ::Dom::Visitor& visitor) + { + return AZ::Dom::Utils::ReadFromString(backend, serializedPayload, AZ::Dom::Lifetime::Temporary, visitor); + }); + + benchmark::DoNotOptimize(result.GetValue()); + } + + state.SetBytesProcessed(serializedPayload.size() * state.iterations()); + } + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, AzDomDeserializeToAzDomValue) + + BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonDeserializeToRapidjson)(benchmark::State& state) { AZ::Dom::JsonBackend backend; AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); @@ -206,9 +233,9 @@ namespace Benchmark state.SetBytesProcessed(serializedPayload.size() * state.iterations()); } - BENCHMARK_REGISTER_JSON(DomJsonBenchmark, JsonUtilsDeserializeToDocument) + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonDeserializeToRapidjson) - BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonPayloadGeneration)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonMakeComplexObject)(benchmark::State& state) { for (auto _ : state) { @@ -217,7 +244,44 @@ namespace Benchmark state.SetItemsProcessed(state.range(0) * state.range(0) * state.iterations()); } - BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonPayloadGeneration) + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonMakeComplexObject) + + BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonLookupMemberByString)(benchmark::State& state) + { + rapidjson::Document document(rapidjson::kObjectType); + AZStd::vector keys; + for (int64_t i = 0; i < state.range(0); ++i) + { + AZStd::string key(AZStd::string::format("key%" PRId64, i)); + keys.push_back(key); + document.AddMember(rapidjson::Value(key.data(), static_cast(key.size()), document.GetAllocator()), rapidjson::Value(i), document.GetAllocator()); + } + + for (auto _ : state) + { + for (const AZStd::string& key : keys) + { + benchmark::DoNotOptimize(document.FindMember(key.data())); + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + BENCHMARK_REGISTER_F(DomJsonBenchmark, RapidjsonLookupMemberByString)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(DomJsonBenchmark, RapidjsonDeepCopy)(benchmark::State& state) + { + rapidjson::Document original = GenerateDomJsonBenchmarkDocument(state.range(0), state.range(1)); + + for (auto _ : state) + { + rapidjson::Document copy; + original.Accept(copy); + } + + state.SetItemsProcessed(state.iterations()); + } + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, RapidjsonDeepCopy) #undef BENCHMARK_REGISTER_JSON } // namespace Benchmark diff --git a/Code/Framework/AzCore/Tests/DOM/DomValueBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomValueBenchmarks.cpp index d11ef7f69e..e3a0db594c 100644 --- a/Code/Framework/AzCore/Tests/DOM/DomValueBenchmarks.cpp +++ b/Code/Framework/AzCore/Tests/DOM/DomValueBenchmarks.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace AZ::Dom::Benchmark { @@ -99,7 +100,7 @@ namespace AZ::Dom::Benchmark } }; - BENCHMARK_DEFINE_F(DomValueBenchmark, ValuePayloadGeneration)(benchmark::State& state) + BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueMakeComplexObject)(benchmark::State& state) { for (auto _ : state) { @@ -108,10 +109,115 @@ namespace AZ::Dom::Benchmark state.SetItemsProcessed(state.range(0) * state.range(0) * state.iterations()); } - BENCHMARK_REGISTER_F(DomValueBenchmark, ValuePayloadGeneration) + BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueMakeComplexObject) ->Args({ 10, 5 }) ->Args({ 10, 500 }) ->Args({ 100, 5 }) ->Args({ 100, 500 }) ->Unit(benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueShallowCopy)(benchmark::State& state) + { + Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + Value copy = original; + benchmark::DoNotOptimize(copy); + } + + state.SetItemsProcessed(state.iterations()); + } + BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueShallowCopy) + ->Args({ 10, 5 }) + ->Args({ 10, 500 }) + ->Args({ 100, 5 }) + ->Args({ 100, 500 }) + ->Unit(benchmark::kNanosecond); + + BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueShallowCopyAndMutate)(benchmark::State& state) + { + Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + Value copy = original; + copy["entries"]["Key0"].PushBack(42); + benchmark::DoNotOptimize(copy); + } + + state.SetItemsProcessed(state.iterations()); + } + BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueShallowCopyAndMutate) + ->Args({ 10, 5 }) + ->Args({ 10, 500 }) + ->Args({ 100, 5 }) + ->Args({ 100, 500 }) + ->Unit(benchmark::kNanosecond); + + BENCHMARK_DEFINE_F(DomValueBenchmark, AzDomValueDeepCopy)(benchmark::State& state) + { + Value original = GenerateDomBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + Value copy = original.DeepCopy(); + benchmark::DoNotOptimize(copy); + } + + state.SetItemsProcessed(state.iterations()); + } + BENCHMARK_REGISTER_F(DomValueBenchmark, AzDomValueDeepCopy) + ->Args({ 10, 5 }) + ->Args({ 10, 500 }) + ->Args({ 100, 5 }) + ->Args({ 100, 500 }) + ->Unit(benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(DomValueBenchmark, LookupMemberByName)(benchmark::State& state) + { + Value value(Type::ObjectType); + AZStd::vector keys; + for (int64_t i = 0; i < state.range(0); ++i) + { + AZ::Name key(AZStd::string::format("key%" PRId64, i)); + keys.push_back(key); + value[key] = i; + } + + for (auto _ : state) + { + for (const AZ::Name& key : keys) + { + benchmark::DoNotOptimize(value[key]); + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + BENCHMARK_REGISTER_F(DomValueBenchmark, LookupMemberByName)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(DomValueBenchmark, LookupMemberByString)(benchmark::State& state) + { + Value value(Type::ObjectType); + AZStd::vector keys; + for (int64_t i = 0; i < state.range(0); ++i) + { + AZStd::string key(AZStd::string::format("key%" PRId64, i)); + keys.push_back(key); + value[key] = i; + } + + for (auto _ : state) + { + for (const AZStd::string& key : keys) + { + benchmark::DoNotOptimize(value[key]); + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + BENCHMARK_REGISTER_F(DomValueBenchmark, LookupMemberByString)->Arg(100)->Arg(1000)->Arg(10000)->Unit(benchmark::kMillisecond); + } // namespace AZ::Dom::Benchmark