在我们基于游戏引擎 cocos2d-x 的 Android 游戏应用程序中,大部分代码都是用 C++ 编写的,自 Android 11 以来我们有一个非常奇怪和关键的问题:
当 native 库在 onLoadNativeLibraries
中加载时现在突然需要 60 多秒。在 Android 11 之前,一切正常,加载时间为 0.2-3 秒。现在,当您开始游戏时,您会看到 60 多秒的灰屏。
我们已经知道 JNI_OnLoad
在 60 秒的停顿结束后直接调用。
这是 onLoadNativeLibraries
的代码
功能:
protected void onLoadNativeLibraries()
{
try
{
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
String libName = bundle.getString("android.app.lib_name");
System.loadLibrary(libName); // line of 60 seconds stall
}
catch (Exception e)
{
e.printStackTrace();
}
}
我们已经尝试过时间分析,但没有任何成功。它只是如何在该功能上花费大量时间。通过调试暂停也不会导致任何进一步的线索。 native 调试器不会在代码的 C++ 端显示任何内容。有人知道为什么会发生这种情况,或者我们可以尝试解决什么问题吗?任何帮助将不胜感激:)
最佳答案
简短回答:
这是谷歌修复的 Android 11 中的一个错误,但尚未部署。
同时,如果您不关心在程序退出/库卸载时调用库中的静态和 thread_local 变量析构函数,请传递标志 -fno-c++-static-destructors
给编译器。 (有关使用 clang 注释的更细粒度的解决方案,请参阅长答案)
我在我的项目(不是 cocos2d)上使用了这个标志,没有问题,并且 lib 的加载速度比以前更快。
长答案:
不幸的是,这是 Google 团队在 android 11 (R) 中引入的性能回归。 google 正在跟踪该问题 here .
总而言之,当 System.loadLibrary()
被调用时,系统为加载的库中包含的每个 C++ 全局变量注册一个析构函数,使用 __cxa_atexit()
从Android 11(R)开始,这个函数在android中的实现有changed :
- In Q, __cxa_atexit uses a linked list of chunks, and calls mprotect twice on the single chunk to be modified.
- In R, __cxa_atexit calls mprotect twice on a single contiguous array of handlers. Each array entry is 2 pointers.
当它们是许多 C++ 全局变量时,这种变化大大降低了性能,这在 cocos2d so 库中似乎就是这种情况。
Google 已经实现了修复 https://android-review.googlesource.com/c/platform/bionic/+/1464716但
如问题所述:
this won't be in Android 11 until the March QPR at the earliest, and since this isn't a security issue it won't be mandatory for OEMs to actually take that patch.
谷歌团队还建议一些 workarounds在应用程序级别通过删除或跳过全局变量的析构函数:
- For a particular global variable, the [[clang::no_destroy]] attribute skips the destructor call.
- Pass -fno-c++-static-destructors to the compiler to skip the destructors for all static variables. This flag also skips destructors for thread_local variables. If there are thread_local variables with important destructors, those can be annotated with [[clang::always_destroy]] to override the compiler flag.
- Pass -Wexit-time-destructors to the compiler to make it warn on every instance of an exit-time destructor, to highlight where the __cxa_atexit registrations are coming from.
关于Android 11 - native C++ 库的 System.loadLibrary 需要 60 多秒,在 Android 10 及更低版本上运行速度非常快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64576236/