Hello everyone!
In our current, quite large project, we’ve encountered an unexpected problem, il2cpp metadata (IL2CppClass structures and things it contains) take up at least 100 mb of RAM used. I’ve tracked this memory to metadata allocation via both Visual Studio and XCode memory profiler.
I’ve written a simple cpp code to try to inspect are all classes have the same amount of metainformation or it depends on the class. I’ve managed to account ot approximatelly 60 mb’s of metainformation in this code (I’m sure I did not get it all). I’ll post the cpp code
long size_of_methodInfo(std::set<const MethodInfo*> alreadyCountedMethods, const MethodInfo* info)
{
if (info == 0 || alreadyCountedMethods.count(info) > 0)
{
return 0;
}
alreadyCountedMethods.insert(info);
long result = 0;
if (info->is_inflated == true && info->is_generic == false)
{
if (info->rgctx_data != 0)
{
result += sizeof(Il2CppRGCTXData);
//result += size_of_methodInfo(alreadyCountedMethods, info->rgctx_data->method);
}
}
else
{
if (info->methodDefinition != 0)
{
result += sizeof(Il2CppMethodDefinition);
}
}
if (info->is_inflated == true)
{
if (!info->is_generic && info->genericMethod != 0)
{
result += sizeof(Il2CppGenericMethod);
result += size_of_methodInfo(alreadyCountedMethods, info->genericMethod->methodDefinition);
}
}
else if (info->is_generic)
{
if (info->genericContainer != 0)
{
result += sizeof(Il2CppGenericContainer);
}
}
result += info->parameters_count * sizeof(ParameterInfo);
return result;
}
void GartherMetadataSizeInfo()
{
auto snapshot = il2cpp::vm::MemoryInformation::CaptureManagedMemorySnapshot();
std::ofstream myfile;
myfile.open("C:\\Metadata\\Data.txt");
std::set<const MethodInfo*> methodInfoSet = std::set<const MethodInfo*>();
for (int i = 0; i < snapshot->metadata.typeCount; ++i)
{
auto type = snapshot->metadata.types[i];
Il2CppClass* classPtr = (Il2CppClass*)type.typeInfoAddress;
long size = sizeof(Il2CppClass);
long uniqueMethodInfosSize = 0;
if (classPtr->typeDefinition != 0)
{
size += sizeof(Il2CppTypeDefinition);
}
if (classPtr->interopData != 0)
{
size += sizeof(Il2CppInteropData);
}
size += classPtr->method_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->method_count * sizeof(MethodInfo);
for (int idx = 0; idx < classPtr->method_count; ++idx)
{
const MethodInfo* methodInfo = classPtr->methods[idx];
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, methodInfo);
}
size += classPtr->event_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->event_count * sizeof(EventInfo);
for (int idx = 0; idx < classPtr->event_count; ++idx)
{
EventInfo eventInfo = classPtr->events[idx];
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, eventInfo.add);
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, eventInfo.raise);
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, eventInfo.remove);
}
size += classPtr->property_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->property_count * sizeof(PropertyInfo);
for (int idx = 0; idx < classPtr->property_count; ++idx)
{
PropertyInfo propertyInfo = classPtr->properties[idx];
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, propertyInfo.get);
uniqueMethodInfosSize += size_of_methodInfo(methodInfoSet, propertyInfo.set);
}
size += classPtr->interface_offsets_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->interface_offsets_count * sizeof(Il2CppRuntimeInterfaceOffsetPair);
size += classPtr->field_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->field_count * sizeof(FieldInfo);
size += classPtr->vtable_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->vtable_count * sizeof(VirtualInvokeData);
size += classPtr->rgctx_data == 0 ? 0 : sizeof(Il2CppRGCTXData);
size += classPtr->interfaces_count * static_cast<uint32_t>(sizeof(void*));
size += classPtr->nested_type_count * static_cast<uint32_t>(sizeof(void*));
if (classPtr->generic_class != 0)
{
auto generic_class = classPtr->generic_class;
}
myfile << type.assemblyName << "\t";
myfile << type.name << "\t";
myfile << uniqueMethodInfosSize << "\t";
myfile << size << "\t";
myfile << size + uniqueMethodInfosSize << std::endl;
}
myfile.close();
il2cpp::vm::MemoryInformation::FreeCapturedManagedMemorySnapshot(snapshot);
}
The results are interesting. The more methods and fields and properties the class has the more metainformation it requires and more memory it occupies; generics require slightly more memory than ordinaty classes; arrays for some reason are champions of metainformation size(there is a two-dimentional array in the project that has at least 200kb of metainformation). In our project most of the metainformation is generated by various generic specializations(generic collections and unirx are the main culprits here).
So innocent looking c# code produces code that is extremely memory hungry just to support its execution.
And now it is time for a call for help
1. Have someone encountered this problem and found some kind of a solution?
2. Can someone from Unity comment on this and maybe investigate ways to reduce memory consumption?