На сколько большие могут быть структуры и таблицы

На сколько большие могут быть структуры? Я имею в виду есть ли какой то лимит по параметрам у структур за который не следует переходить? Например есть структура в которой 100 параметров из них половина это другие структуры и т. д.

Сложно абстрактно ответить на такой вопрос, поэтому опишу все моменты, которые важны. Получилась мини лекция )

1. Первое, самое простое, о чем стоит задуматься, когда видим в коде гигантскую структуру — все ли у нас верно с точки зрения семантики, т.е. имеет ли смысл такой сложный тип данных или это оверхэд. Может создать несколько простых типов; может стоит использовать базу данных (в случае с базой данных могут вступить в игру другие детали: как часто мы читаем данные, нужно ли поля отображать в блюпринтах и т.д.). Также я всегда рекомендую при написании кода думать о том, будет ли он понятен другому человеку при прочтении, это помогает.

2.1 Размер структуры влияет на физическую память. Размер структуры в байтах равен количеству байт занимаемых каждым полем. Сколько байтов соответствует каждому типу в Unreal можно посмотреть в документации. Указатели имеют размер 8 байт на 64х-битной машине вне зависимости от типа.

Пример:

USTRUCT(BlueprintType)
struct FSimpleStructInt32
{
    GENERATED_USTRUCT_BODY()
 
    int32 Data;
};
 
USTRUCT(BlueprintType)
struct FSimpleStructDouble
{
    GENERATED_USTRUCT_BODY()
 
    double Data;
};
 
USTRUCT(BlueprintType)
struct FSimpleStructDoublePtr
{
    GENERATED_USTRUCT_BODY()
 
    double* Data;
};
 
USTRUCT(BlueprintType)
struct FSimpleStructTexPtr
{
    GENERATED_USTRUCT_BODY()
 
    UTexture2D* Data;
};
 
UE_LOG(LogTemp, Display, TEXT("int32 size: %i  FSimpleStructInt32 size: %i "), sizeof(int32), sizeof(FSimpleStructInt32));
UE_LOG(LogTemp, Display, TEXT("double size: %i  FSimpleStructDouble size: %i "), sizeof(double), sizeof(FSimpleStructDouble));
UE_LOG(LogTemp, Display, TEXT("double ptr size: %i  FSimpleStructDoublePtr size: %i "), sizeof(double*), sizeof(FSimpleStructDoublePtr));
UE_LOG(LogTemp, Display, TEXT("tex size: %i, tex ptr size: %i  FSimpleStructTexPtr size: %i "), sizeof(UTexture2D), sizeof(UTexture2D*), sizeof(FSimpleStructTexPtr));

Вывод получится следующий:

LogTemp: Display: int32 size: 4  FSimpleStructInt32 size: 4 
LogTemp: Display: double size: 8  FSimpleStructDouble size: 8 
LogTemp: Display: double ptr size: 8  FSimpleStructDoublePtr size: 8 
LogTemp: Display: tex size: 848, tex ptr size: 8  FSimpleStructTexPtr size: 8

Eсли у вас в структуре 1000 переменных типа int32 , то размер структуры будет 4 * 1000 байт.

Eсли у вас в структуре 1000 переменных типа double , то размер структуры будет 8 * 1000 байт.

2.2 Структуры выравниваются в памяти (здесь наглядно с картинками можно прочитать). Ее размер может зависеть от порядка полей структуры.

Пример:

USTRUCT(BlueprintType)
struct FSimpleAlignTest
{
    GENERATED_USTRUCT_BODY()
 
   int32 Data1;
   int32* Data2;
   uint8 Data3;
   uint8* Data4;
};
 
UE_LOG(LogTemp, Display, TEXT("int32 size: %i"), sizeof(int32));
UE_LOG(LogTemp, Display, TEXT("int32 ptr size: %i"), sizeof(int32*));
UE_LOG(LogTemp, Display, TEXT("uint8 size: %i"), sizeof(uint8));
UE_LOG(LogTemp, Display, TEXT("uint8 ptr size: %i"), sizeof(uint8*));
UE_LOG(LogTemp, Display, TEXT("struct size: %i"), sizeof(FSimpleAlignTest));

Вывод:

LogTemp: Display: int32 size: 4
LogTemp: Display: int32 ptr size: 8
LogTemp: Display: uint8 size: 1
LogTemp: Display: uint8 ptr size: 8
LogTemp: Display: struct size: 32

Т.е. поля Data1 и Data2 выравнялись до 8 байт, поэтому получилось 4 * 8 = 32 байта (хотя сумма 4 + 8 + 1 + 8 = 21 байт)

Далее, меняем порядок полей у той же структуры:

USTRUCT(BlueprintType)
struct FSimpleAlignTest
{
    GENERATED_USTRUCT_BODY()
 
   int32 Data1;
   uint8 Data2;
   int32* Data3;
   uint8* Data4;
};
 
UE_LOG(LogTemp, Display, TEXT("int32 size: %i"), sizeof(int32));
UE_LOG(LogTemp, Display, TEXT("uint8 size: %i"), sizeof(uint8));
UE_LOG(LogTemp, Display, TEXT("int32 ptr size: %i"), sizeof(int32*));
UE_LOG(LogTemp, Display, TEXT("uint8 ptr size: %i"), sizeof(uint8*));
UE_LOG(LogTemp, Display, TEXT("struct size: %i"), sizeof(FSimpleAlignTest));

Вывод:

LogTemp: Display: int32 size: 4
LogTemp: Display: uint8 size: 1
LogTemp: Display: int32 ptr size: 8
LogTemp: Display: uint8 ptr size: 8
LogTemp: Display: struct size: 24

Два первых поля занимают 5 байт, они упаковались вместе в 8 байт, поэтому получилось 8 * 3 = 24 байта (хотя сумма все та же 21 байт)

2.2 Теперь смоделируем ситуацию, которую вы описали. В C++ имееются ограничения на размер объекта, который можно создать на стеке. В примере я использую шаблон TFixedAllocator, который позволяет аллоцировать массив фиксированного размера:

USTRUCT(BlueprintType)
struct FDataStruct1
{
    GENERATED_USTRUCT_BODY()
 
    TArray<int32, TFixedAllocator<1024>> SomeIntData;
};
 
USTRUCT(BlueprintType)
struct FDataStruct2
{
    GENERATED_USTRUCT_BODY()
 
    TArray<FDataStruct1, TFixedAllocator<1024>> SomeDataStruct1;
};
 
constexpr int32 ArrSize = 400;
 
USTRUCT(BlueprintType)
struct FDataStruct3
{
    GENERATED_USTRUCT_BODY()
 
    TArray<FDataStruct2, TFixedAllocator<ArrSize>> SomeDataStruct2;
};
 
.........
 
 
void ASTUBaseCharacter::BeginPlay()
{
    Super::BeginPlay();
 
    FDataStruct3 data;
}

Код выше скомпилируется. Все ОК (если интересно, можете посчитать сколько памяти занимает FDataStruct3) А теперь увеличим параметр ArrSize до 500:

constexpr int32 ArrSize = 500;

Код не скомпилируется, компилятор выдаст сообщение об ошибке:

fatal error C1126: automatic allocation exceeds 2G

2.3 При создании объекта динамически в куче (heap) таких ограничений нет. USTRUCT динамически создать не получится, но можно, обернуть ее в UObject и создать динамически такой объект, ошибки компиляции уже не будет в данном случае:

UCLASS(Blueprintable, BlueprintType)
class SHOOTTHEMUP_API UStructContainerObject : public UObject
{
    GENERATED_BODY()
public:
    FDataStruct3 Data;
};
 
...
 
const auto DataContainer = NewObject<UStructContainerObject>();

3. Если у вас структура с массивом массивов массивов структур и вы волняете поиск по каким-то ключам, и у вас три вложенных цикла, то тут в игру вступает время поиска (можно почитать в интернете про сложность алгоритмов и т.п.). Производительность может упасть легко.

4. Нужно быть аккуратным с копированием больших структур: не делать лишнего, передавать по ссылке везде, где возможно.

5. Если структура заполнена большим количеством данных в блюпринтах, то это может повлять на процесс сериализации/десериализации, чтения/записи на диск в процессе разработки.

6. Полезные указания по использованию структур из документации

Ну и аналогичный вопрос по таблицам. Насколько большими могут быть хранилища данных такие как таблицы? Что лучше 10 маленьких таблиц или 1 большая? Меня интересует на сколько правильно там хранить информацию к которой нужен доступ постоянно ну если не постоянно то с определенной частотой обращаться к этим данным и на сколько велики могут быть эти хранилища для беспроблемной работы.

Если посмотреть в исходники на класс UDataTable, то можно увидеть, что в сухом остатке UDataTable это UObject, который хранит структуры в ассоциативном массиве:

/** Map of name of row to row data structure. */
TMap<FName, uint8*>		RowMap;

Т. е. в целом это проецируется на обсуждение первого вопроса. Можете представить, что вы объявляете структуру, в которой одно поле - ассоциативный массив структур другого типа.

Хранить данные в таблицах нормальное решение + удобство в том, что данный контейнер с данными могут использовать разные несвязанные друг с другом классы.

В таблице данные сгрупированы также логически, т.е. строки это объекты структуры одного типа. Поэтому просто создавать еще одну таблицу на основе такой же структуры думаю не стоит.

Подводя итог:

Универсального решения естественно нет. Все зависит от конкретной ситуации и как вы используете структуру. Я предпочитаю начинать с семантической оценки. Так же можете почитать про антипаттерн Blob (это в целом про классы, но смысл примерно тот же)

А какие самые большие структуры вы видели за свою практику? Может у вас есть примеры из практики?

Постоянно встречаются:

1. Конфигурационные структуры (например оружия): параметры цветов, анимаций, связи модификаций, настройки стрельбы, ущерба в различных ситуациях.

2. Деревья прокачки персонажа.

3. В проектах с пользовательской информацией: когда необходимо запросить из базы данные о зарегистрированных пользователях, распарсить, проанализировать, отсортировать всю информацию, от которой может зависеть весь внешний вид игры в принципе (структура данных может быть очень! сложной; для примера можно посмотреть как работают API различных сервисов: google, twitter и сколько данных можно получить разными запросами).

Но к слову сказать, я не могу припомнить ситуацию, когда сложная структура была узким местом - это просто хранилище данных. Скорее это будет неправильная группировка данных и в целом работа с данными из этой структуры: поиск, сортировка и т.д.

Last updated