Android 11 - 本机 C++ 库的 System.loadLibrary 需要 60 秒以上,在 Android 10 及更低版本上运行速度非常快

key*_*ard 8 c++ java-native-interface android cocos2d-x android-11

在我们基于游戏引擎cocos2d-x的Android游戏应用程序中,大部分代码都是用C++编写的,自Android 11以来,我们遇到了一个非常奇怪且关键的问题:

当本地库加载时,onLoadNativeLibraries现在突然需要 60 多秒。在 Android 11 之前,一切正常,加载时间为 0.2-3 秒。现在,当您启动游戏时,您会看到 60 秒以上的灰屏。

我们已经知道JNI_OnLoad60 秒停顿结束后会直接调用。

这是该函数的代码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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们已经尝试过时间分析,但没有成功。只是它在该功能上花费了大量时间。此外,通过调试暂停不会导致任何进一步的线索。本机调试器不会在代码的 C++ 端显示任何内容。

有谁知道为什么会发生这种情况,或者我们可以尝试解决什么问题?任何帮助将不胜感激:)

fin*_*odd 7

简短回答:

这是 Android 11 中的一个错误,已被谷歌修复,但尚未部署。

同时,如果您不关心在程序退出/库卸载时调用 lib 中的staticthread_local-fno-c++-static-destructors变量析构函数,请将标志传递给编译器。(有关使用 clang 注释的更细粒度的解决方案,请参阅长答案)

我在我的项目(不是 cocos2d)上使用了这个标志,没有任何问题,并且 lib 的加载速度比以前更快。

长答案:

不幸的是,这是 Google 团队在 Android 11 (R) 中引入的性能回归。谷歌正在跟踪这个问题

总而言之,调用时System.loadLibrary(),系统使用__cxa_atexit()为加载的库中包含的每个 C++ 全局变量注册一个析构函数。

从 Android 11 (R) 开始,该函数在 android 中的实现发生了变化

  • 在 Q 中,__cxa_atexit 使用块的链表,并对要修改的单个块调用 mprotect 两次。
  • 在 R 中,__cxa_atexit 对单个连续处理程序数组调用 mprotect 两次。每个数组条目有 2 个指针。

当它们是许多 C++ 全局变量时,这种变化会大幅降低性能,在 cocos2d so库中似乎就是这种情况。

谷歌已经实施了修复https://android-review.googlesource.com/c/platform/bionic/+/1464716,但如问题中所述:

这最早要到 3 月份的 QPR 才会出现在 Android 11 中,而且由于这不是一个安全问题,因此 OEM 厂商不会强制要求实际安装该补丁。

Google 团队还建议通过删除或跳过全局变量的析构函数来在应用程序级别提供一些解决方法:

  • 对于特定的全局变量, [[clang::no_destroy]] 属性会跳过析构函数调用。
  • 将 -fno-c++-static-destructors 传递给编译器以跳过所有静态变量的析构函数。该标志还跳过 thread_local 变量的析构函数。如果存在带有重要析构函数的 thread_local 变量,则可以使用 [[clang::always_destroy]] 注释这些变量以覆盖编译器标志。
  • 将 -Wexit-time-destructors 传递给编译器,使其对退出时析构函数的每个实例发出警告,以突出显示 __cxa_atexit 注册的来源。