Android 11 - 本机 C++ 库的 System.loadLibrary 需要 60 秒以上,在 Android 10 及以下版本上运行速度非常快
Android 11 - System.loadLibrary for native C++ library takes 60+ seconds, works perfectly fast on Android 10 and below
在我们基于游戏引擎 cocos2d-x 的 Android 游戏应用程序中,大部分代码是用 C++ 编写的,我们有一个非常奇怪和关键的问题,因为 Android 11:
当原生库在 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();
}
}
我们已经尝试过时间分析,但没有成功。它只是如何在该功能上花费大量时间。通过调试暂停也不会导致任何进一步的线索。本机调试器不会在代码的 C++ 端显示任何内容。
有没有人知道为什么会这样或者我们可以尝试解决什么问题?任何帮助将不胜感激:)
简答:
这是 Android 11 中的一个错误,由 google 修复但尚未部署。
同时,如果您不关心在程序运行时在您的库中调用 static 和 thread_local 变量析构函数exiting/library 被卸载,将标志 -fno-c++-static-destructors
传递给编译器。 (使用 clang 注释查看更详细的解决方案的长答案)
我在我的项目(不是 cocos2d)中使用了这个标志,没有任何问题,而且库的加载速度比以前更快。
长答案:
不幸的是,这是 Google 团队在 android 11 (R) 中引入的性能回归。 google here.
正在跟踪此问题
总而言之,当调用System.loadLibrary()
时,系统会为加载库中包含的每个C++全局变量注册一个析构函数,使用__cxa_atexit()
因为Android11(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.
Google 团队还建议 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.
在我们基于游戏引擎 cocos2d-x 的 Android 游戏应用程序中,大部分代码是用 C++ 编写的,我们有一个非常奇怪和关键的问题,因为 Android 11:
当原生库在 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();
}
}
我们已经尝试过时间分析,但没有成功。它只是如何在该功能上花费大量时间。通过调试暂停也不会导致任何进一步的线索。本机调试器不会在代码的 C++ 端显示任何内容。
有没有人知道为什么会这样或者我们可以尝试解决什么问题?任何帮助将不胜感激:)
简答:
这是 Android 11 中的一个错误,由 google 修复但尚未部署。
同时,如果您不关心在程序运行时在您的库中调用 static 和 thread_local 变量析构函数exiting/library 被卸载,将标志 -fno-c++-static-destructors
传递给编译器。 (使用 clang 注释查看更详细的解决方案的长答案)
我在我的项目(不是 cocos2d)中使用了这个标志,没有任何问题,而且库的加载速度比以前更快。
长答案:
不幸的是,这是 Google 团队在 android 11 (R) 中引入的性能回归。 google here.
正在跟踪此问题总而言之,当调用System.loadLibrary()
时,系统会为加载库中包含的每个C++全局变量注册一个析构函数,使用__cxa_atexit()
因为Android11(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.
Google 团队还建议 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.