OpenMP在ARM-Linux以及NDK中的编译和使用

OpenMP在ARM-Linux以及NDK中的编译和使用

参考:http://blog.sina.com.cn/s/blog_602f87700102w1ki.html

 

以前对OpenCV在ARM-Linux,ARM-Android上的优化做了很多编译方面的努力,例如添加TBB支持,添加CUDA支持(NvidiaK1平台上)。这次突然听同事说增加了OpenMP选项后,在Windows+X86上有极大的优势,adaboost速度提高3倍。所以赶快在ARM-Android-NDK上测试一下。

 

 

0. OpenMP基础:

OpenMP(OpenMulti-Processing)是由OpenMPArchitecture Review Board牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性注释(CompilerDirective)。OpenMP支持的编程语言包括C语言C++Fortran;而支持OpenMP的编译器包括Sun StudioIntel Compiler,以及开放源码GCCOpen64编译器。OpenMP提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。

 

1. OpenMP在X86 Linux上的展现:

例子代码:

#include
int main(int argc, char* argv[])
{

#pragma omp parallel

printf("Hello, world.\n");
return 0;
}

普通编译:

g++ OpenMP_Test.cpp -o test

运行:

# ./test
Hello, world.

 

增加OpenMP 编译选项的编译:

 g++ -fopenmp OpenMP_Test.cpp -o test

运行:

#./test
Hello, world.
Hello, world.
Hello, world.
Hello, world.

证明-fopenmp在GCC下有效。代码的OpenMP能力得到支持。

#pragma ompparallel 仅在您指定了 -fopenmp 编译器选项后才会发挥作用。在编译期间,GCC会根据硬件和操作系统配置在运行时生成代码,创建尽可能多的线程。每个线程的起始例程为代码块中位于指令之后的代码。这种行为是 隐式的并行化,而OpenMP本质上由一组功能强大的编译指示组成,帮您省去了编写大量样本文件的工作。我的Linux机器为4核CPU。所有有4个thread. 

 

 

2. OpenMP在ARM-Anrdoid-NDK上的展现:

代码不变。

Android.mk内容如下:

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := test
LOCAL_SRC_FILES := OpenMP_Test.cpp

 

 

LOCAL_CXXFLAGS := -fopenmp
LOCAL_CFLAGS +=  -fopenmp
LOCAL_LDLIBS := -llog -fopenmp
include $(BUILD_EXECUTABLE)

 

Application.mk内容如下:

# Build both ARMv5TE and ARMv7-A machine code.
APP_PLATFORM = android-8

APP_ABI := armeabi-v7a
#APP_ABI := $(ARM_ARCH)

#Sam modify it to release
#APP_OPTIM := release
APP_OPTIM := debug
#APP_OPTIM = $(MY_OPTIM)

APP_CPPFLAGS += -fexceptions
APP_CPPFLAGS += -frtti

#sam modify it from gnustl_static to gnustl_shared
#APP_STL := gnustl_static
#APP_STL       := gnustl_shared
APP_STL := gnustl_shared

#APP_CPPFLAGS += -fno-rtti


#
APP_CPPFLAGS += -Dlinux -fsigned-char
APP_CFLAGS += -fsigned-char
#APP_CPPFLAGS += $(MY_CPPFLAGS) -Dlinux
#STLPORT_FORCE_REBUILD := true

 

编译后运行:

$./test                                      
Hello, world.
Hello, world.
Hello, world.
Hello, world.

证明-fopenmp在NDK下有效。代码的OpenMP能力得到支持。

K1平台是4 Core的。所以有4个thread. 

 

3. OpenCV4AndroidOpenMP支持:

#!/bin/sh
cd `dirname $0`/..

mkdir -p build_android_arm
cd build_android_arm

cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON-DHAVE_EIGEN=1  -DHAVE_CAMV4L2=ON -DBUILD_TBB=ON-DWITH_TBB=ON -DHAVE_OPENMP=1 -DBUILD_EXA
MPLES=1 -DANDROID_ABI="armeabi-v7a" -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@../..

只要如此编译,则OpenCV 支持OpenMP.

 

4. OpenMP 指令和库函数:

 C/C++中,OpenMP指令的使用格式为:

pragmaomp 指令 [子句[子句]…]

#pragma omp parallel for
    for (int j = 0; j < 4; j++)
  {
        printf("j=[%d], ThreadId =[%d]\n", j, omp_get_thread_num());
    }
#endif

 

如果报找不到符号,可以:-lgom

总结一下,在pc上,时间加倍,但是在arm多核上,效果可以,时间减少约50%。

cmd.exe /C "cd . && D:\android-sdk-windows\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=aarch64-none-linux-android24 --sysroot=D:/android-sdk-windows/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -std=c++14 -fno-limit-debug-info -static-libstdc++ -Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--gc-sections -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libmyapplication.so -o D:\Projects\MyApplication\app\build\intermediates\cxx\Debug\1h1g1tb5\obj\arm64-v8a\libmyapplication.so CMakeFiles/myapplication.dir/src/pdr_local_interface.cpp.o CMakeFiles/myapplication.dir/src/pdr_infer.cpp.o CMakeFiles/myapplication.dir/native-lib.cpp.o D:/android-sdk-windows/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android/24/liblog.so -landroid -llog -latomic -lm && cd ." ld: error: undefined symbol: Riemann::Pdr::RiemannPdr::GetInstance() >>> referenced by pdr_infer.cpp:20 (D:/Projects/MyApplication/app/src/main/cpp/src/pdr_infer.cpp:20) >>> CMakeFiles/myapplication.dir/src/pdr_infer.cpp.o:(Riemann::Pdr::PdrInfer::Init(std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >)) >>> referenced by pdr_infer.cpp:285 (D:/Projects/MyApplication/app/src/main/cpp/src/pdr_infer.cpp:285) >>> CMakeFiles/myapplication.dir/src/pdr_infer.cpp.o:(Riemann::Pdr::PdrInfer::NetInference()) >>> referenced by native-lib.cpp:18 (D:/Projects/MyApplication/app/src/main/cpp/native-lib.cpp:18) >>> CMakeFiles/myapplication.dir/native-lib.cpp.o:(Java_com_example_myapplication_MainActivity_stringFromJNI) >>> referenced 1 more times
最新发布
07-09
<think>我们正在解决一个Android NDK编译链接错误,具体错误是:`undefined symbol: Riemann::Pdr::RiemannPdr::GetInstance()` 根据用户提供的引用信息,我们可以参考两个可能的错误原因: 1. 引用[1]中提到`clang++.exe: error: unknown argument: ‘-static-openmp’`,但用户当前的问题不是这个,所以暂时不考虑。 2. 引用[2]中提到一个链接错误`ld: error: undefined symbol: std::__ndk1:`,原因是`local.properties`中使用了过时的`ndk.dir`属性,并给出了解决方案。 然而,用户当前的错误是`undefined symbol: Riemann::Pdr::RiemannPdr::GetInstance()`,这是一个自定义类的静态成员函数(或单例模式的获取实例函数)未定义。这种错误通常发生在链接阶段,编译器找到了声明但找不到定义。 我们需要分析可能的原因: 1. 包含该函数声明的头文件被正确包含,但定义该函数的源文件没有被编译或链接到目标库/可执行文件中。 2. 函数定义存在,但名字修饰(name mangling)问题导致链接器无法匹配符号(比如CC++混用没有使用extern "C")。 3. 如果是静态成员函数,需要在类外提供定义(对于非内联的静态成员函数)。 由于问题发生在Android NDK编译环境中,我们还需要考虑NDK构建系统的配置。 解决步骤: 1. **确认函数定义存在**:检查`RiemannPdr`类的实现文件(.cpp或.cc)中是否定义了`GetInstance()`函数。例如,在类中声明为静态成员函数,那么在类外应该有这样的定义: ```cpp // 在RiemannPdr.cpp中 Riemann::Pdr::RiemannPdr* Riemann::Pdr::RiemannPdr::GetInstance() { // 实现代码 } ``` 如果没有定义,添加定义。 2. **检查该源文件是否被加入编译**:在Android.mk或CMakeLists.txt中,确保包含`GetInstance`函数实现的源文件被添加到源文件列表中。 3. **检查名字修饰**:如果头文件声明时使用了C++,而实现文件被当作C编译,或者反过来,会导致符号名不一致。确保所有相关文件都是C++编译(通常扩展名为.cpp,或者在CMake中明确指定语言)。如果头文件可能被CC++同时包含,应该使用`extern "C"`(但这里明显是C++类,所以不适用)。 4. **检查NDK构建配置**:参考引用[2]中的建议,确保`local.properties`中没有使用过时的`ndk.dir`,而是使用`android.ndkVersion`在`build.gradle`中指定NDK版本。这样可以避免NDK路径配置问题。 5. **清理并重新构建**:有时候构建系统可能没有正确更新,尝试清理项目(`Build > Clean Project`)然后重新构建。 6. **检查链接顺序**:如果使用静态库,确保链接时库的顺序正确,因为链接器是按顺序解析符号的。在CMake中,可以使用`target_link_libraries`将依赖库链接到目标。 7. **检查可见性**:在NDK中,默认情况下,只有JNI函数通过`__attribute__((visibility("default")))`显式导出的符号才会被导出。如果这个函数在一个动态库中,并且需要被其他库使用,那么需要确保它被正确导出。 8. **使用nm工具检查库文件**:如果问题仍然存在,可以尝试使用NDK中的`nm`工具(例如`aarch64-linux-android-nm`)来检查生成的库文件或对象文件,看是否有`GetInstance`函数的符号。命令示例: ``` nm -gC your_library.so | grep GetInstance ``` 如果符号存在,检查其可见性(如果是小写字母表示是局部符号,需要导出;大写字母表示全局符号)。如果不存在,则说明编译时未包含或未生成。 9. **检查C++运行时库**:如果使用了`stlport`或`c++_shared`等,确保所有模块使用相同的STL。 由于用户没有提供具体的构建脚本,我们只能给出一般性建议。如果用户能提供Android.mk或CMakeLists.txt以及相关代码结构,可以更具体地分析。 下面,我们按照用户要求生成相关问题。</think>### Android NDK链接错误:未定义符号 `Riemann::Pdr::RiemannPdr::GetInstance()` 解决方案 #### 问题分析 该错误发生在**链接阶段**,表明: 1. 函数声明(在头文件中)已被正确引用 2. 但函数实现(在源文件中)未被正确链接到最终库或可执行文件 3. 常见原因包括: - 实现文件未加入编译 - 符号可见性问题 - NDK工具链配置错误[^1] #### 逐步解决方案 ##### ✅ 1. 检查源文件编译状态 在 `CMakeLists.txt` 或 `Android.mk` 中确认包含 `GetInstance()` 实现的源文件(如 `RiemannPdr.cpp`)已添加到编译目标: ```cmake # CMake 示例 add_library(native-lib SHARED RiemannPdr.cpp # 确保包含实现文件 YourOtherFiles.cpp ) ``` 若使用 `Android.mk`: ```makefile LOCAL_SRC_FILES := RiemannPdr.cpp $(OTHER_SRCS) ``` ##### ✅ 2. 验证符号可见性(关键步骤) 在头文件中显式导出符号(NDK默认隐藏C++符号): ```cpp // RiemannPdr.h #ifdef __cplusplus #define JNI_EXPORT extern "C" JNIEXPORT #else #define JNI_EXPORT JNIEXPORT #endif class JNI_EXPORT RiemannPdr { // 添加导出宏 public: static RiemannPdr* GetInstance(); }; ``` ##### ✅ 3. 更新NDK配置(避免过期配置) 按官方建议[^2]修正 `local.properties` `build.gradle`: ```gradle // app/build.gradle android { ndkVersion "25.1.8937393" // 指定明确版本 externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" // 确保STL一致 } } } ``` 删除 `local.properties` 中的过期 `ndk.dir` 配置(仅保留 `sdk.dir`)。 ##### ✅ 4. 检查函数实现签名 确保声明与实现完全匹配(包括命名空间): ```cpp // RiemannPdr.cpp Riemann::Pdr::RiemannPdr* Riemann::Pdr::RiemannPdr::GetInstance() { // 全限定名 static RiemannPdr instance; return &instance; } ``` ##### ✅ 5. 清理并重建项目 执行以下命令强制刷新: ```bash ./gradlew clean # 清理构建 ./gradlew assembleDebug --info # 重新构建并显示详细日志 ``` #### 预防措施 1. **符号导出检查**:使用 `nm` 工具验证 `.so` 文件是否包含符号: ```bash $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm -gC yourlib.so | grep GetInstance ``` 应输出类似:`T Riemann::Pdr::RiemannPdr::GetInstance()` 2. **统一STL库**:所有模块使用相同的STL(如 `c++_shared`) 3. **NDK版本管理**:在 `build.gradle` 中固定 `ndkVersion` > **提示**:若使用第三方库,确保其编译使用NDK版本与主项目一致[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI算法网奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值