Files
UnrealEngineUWP/Engine/Source/Runtime/Serialization/Public/Backends/JsonStructSerializerBackend.h
wojciech krywult 344371f68b Json: Reduced the probability of OOM crashes when running UE.Automation tests on some memory-constrained platforms.
The crashes were caused by us not being able to allocate large memory buffers. Plenty of physical memory was available but virtual memory was too fragmented. This fragmentation was caused by our tests repeatedly (after each rendering test) allocating very large chunks of memory i.e. ~70 MB when sending image data back to the connected PC coordinating the tests. This 70 MB was needed because of very inefficient serialization (FStructSerializer), noticeable in particular when handling raw binary buffers (TArray<int8>).

The core issue is that the serializer does the job in two passes. First, it creates descriptions of all collection items (FStructSerializerState), which are added to a common stack, and then iterating over all the stack elements. It means that each byte from a serialized binary buffer, we would allocate one FStructSerializerState (64 bytes). That's a 64x increase!

Reworking the whole FStructSerializer would be a pretty large change, so instead I'm implementing a workaround specifically for binary buffer. It implements the existing serialization logic in a slightly different way as a WritePODArray special case. We simply directly iterate over binary arrays and skip the state generation step completelly.

#rb wojciech.krywult
#jira UE-219991, UE-219992, UE-218514, UE-219986

[CL 35432011 by wojciech krywult in ue5-main branch]
2024-08-09 15:07:15 -04:00

131 lines
4.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/UnrealString.h"
#include "CoreMinimal.h"
#include "CoreTypes.h"
#include "IStructSerializerBackend.h"
#include "Misc/EnumClassFlags.h"
#include "Serialization/JsonWriter.h"
#include "Templates/SharedPointer.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/UnrealType.h"
class FArchive;
/**
* Implements a writer for UStruct serialization using Json.
*
* Note: The underlying Json serializer is currently hard-coded to use UCS2CHAR and pretty-print.
* This is because the current JsonWriter API does not allow writers to be substituted since it's
* all based on templates. At some point we will refactor the low-level Json API to provide more
* flexibility for serialization.
*/
class FJsonStructSerializerBackend
: public IStructSerializerBackend
{
public:
/**
* Creates and initializes a new legacy instance.
* @note Deprecated, use the two-parameter constructor with EStructSerializerBackendFlags::Legacy if you need backwards compatibility with code compiled prior to 4.22.
*
* @param InArchive The archive to serialize into.
*/
UE_DEPRECATED(4.22, "Use the two-parameter constructor with EStructSerializerBackendFlags::Legacy only if you need backwards compatibility with code compiled prior to 4.22; otherwise use EStructSerializerBackendFlags::Default.")
FJsonStructSerializerBackend( FArchive& InArchive )
: JsonWriter(TJsonWriter<UCS2CHAR>::Create(&InArchive))
, Flags(EStructSerializerBackendFlags::Legacy)
{ }
/**
* Creates and initializes a new instance with the given flags.
*
* @param InArchive The archive to serialize into.
* @param InFlags The flags that control the serialization behavior (typically EStructSerializerBackendFlags::Default).
*/
FJsonStructSerializerBackend( FArchive& InArchive, const EStructSerializerBackendFlags InFlags )
: JsonWriter(TJsonWriter<UCS2CHAR>::Create(&InArchive))
, Flags(InFlags)
{ }
public:
// IStructSerializerBackend interface
SERIALIZATION_API virtual void BeginArray(const FStructSerializerState& State) override;
SERIALIZATION_API virtual void BeginStructure(const FStructSerializerState& State) override;
SERIALIZATION_API virtual void EndArray(const FStructSerializerState& State) override;
SERIALIZATION_API virtual void EndStructure(const FStructSerializerState& State) override;
SERIALIZATION_API virtual void WriteComment(const FString& Comment) override;
SERIALIZATION_API virtual void WriteProperty(const FStructSerializerState& State, int32 ArrayIndex = 0) override;
SERIALIZATION_API virtual bool WritePODArray(const FStructSerializerState& State) override;
protected:
// Allow access to the internal JsonWriter to subclasses
TSharedRef<TJsonWriter<UCS2CHAR>>& GetWriter()
{
return JsonWriter;
}
// Writes a property value to the serialization output.
template<typename ValueType>
void WritePropertyValue(const FStructSerializerState& State, const ValueType& Value)
{
//Write only value in case of no property or container elements
if ((State.ValueProperty == nullptr) ||
((State.ValueProperty->ArrayDim > 1
|| State.ValueProperty->GetOwner<FArrayProperty>()
|| State.ValueProperty->GetOwner<FSetProperty>()
|| (State.ValueProperty->GetOwner<FMapProperty>() && State.KeyProperty == nullptr)) && !EnumHasAnyFlags(State.StateFlags, EStructSerializerStateFlags::WritingContainerElement)))
{
JsonWriter->WriteValue(Value);
}
//Write Key:Value in case of a map entry
else if (State.KeyProperty != nullptr)
{
FString KeyString;
State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None);
JsonWriter->WriteValue(KeyString, Value);
}
//Write PropertyName:Value for any other cases (single array element, single property, etc...)
else
{
JsonWriter->WriteValue(State.ValueProperty->GetName(), Value);
}
}
// Writes a null value to the serialization output.
void WriteNull(const FStructSerializerState& State)
{
if ((State.ValueProperty == nullptr) ||
((State.ValueProperty->ArrayDim > 1
|| State.ValueProperty->GetOwner<FArrayProperty>()
|| State.ValueProperty->GetOwner<FSetProperty>()
|| (State.ValueProperty->GetOwner<FMapProperty>() && State.KeyProperty == nullptr)) && !EnumHasAnyFlags(State.StateFlags, EStructSerializerStateFlags::WritingContainerElement)))
{
JsonWriter->WriteNull();
}
else if (State.KeyProperty != nullptr)
{
FString KeyString;
State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None);
JsonWriter->WriteNull(KeyString);
}
else
{
JsonWriter->WriteNull(State.ValueProperty->GetName());
}
}
private:
/** Holds the Json writer used for the actual serialization. */
TSharedRef<TJsonWriter<UCS2CHAR>> JsonWriter;
/** Flags controlling the serialization behavior. */
EStructSerializerBackendFlags Flags;
};