我有一些C++函数,我正在使用IPOPT进行优化。虽然成本函数、约束函数等都是用C++编写的,但代码最初是为使用 C 接口而编写的。我还没有费心去改变它,除非它被证明是问题所在。
无论如何。。。我们观察到一些意外的行为,当我们编译带有/没有矢量化标志的程序时,优化器收敛的方式不同。具体来说,在CMakeLists文件中,我们有
set(CMAKE_CXX_FLAGS "-Wall -mavx -mfma")
当我们使用这些设置运行优化器时,优化器会在大约 100 次迭代中收敛。目前为止,一切都好。
但是,我们有理由相信,当针对ARM(特别是Android)进行编译时,不会发生矢量化,因为性能与英特尔处理器有很大不同。Eigen 文档说应该始终为 64 位 ARM 启用 NEON 指令,但我们有理由怀疑这种情况不会发生。无论如何,这不是这里的问题。
由于这种怀疑,我们想看看如果我们禁用矢量化,我们的英特尔处理器的性能会有多差。这应该为我们提供一些指示,说明正在发生多少矢量化,以及我们期望在 ARM 中看到多少改进。但是,当我们将编译器标志更改为
set(CMAKE_CXX_FLAGS "-Wall")
(或者只是在我们只使用 AVX(没有 fma)的情况下),然后我们从优化器那里得到相同的通用解决方案,但收敛性能非常不同。具体而言,如果不进行矢量化,优化器大约需要 500 次迭代才能收敛到解决方案。
所以总结一下:
With AVX and FMA : 100 iterations to converge
With AVX : 200 iterations to converge
Without AVX and FMA : 500 iterations to converge
我们实际上只更改了 cmake 文件中的一行,而不是源代码。
我想就为什么会发生这种情况提供一些建议。
我的想法和更多背景信息:
在我看来,带或不带矢量化的版本都必须进行一些舍入,这使得 IPOPT 收敛方式不同。我的印象是,添加 AVX 和 FMA 标志不会改变函数的输出,而只会改变计算它们所需的时间。我似乎错了。
我们观察到的现象对我来说特别奇怪,因为一方面我们观察到优化器总是收敛到相同的解决方案。这在某种程度上表明问题不能太糟糕。然而,另一方面,优化器在有/没有矢量化标志的情况下表现不同的事实表明,这个问题确实对矢量化指令产生的任何小残差都很敏感。
要记住的另一件事是,我们将IPOPT预编译到一个库中,并且只是将我们的代码链接到该预编译库。所以我不认为 AVX 和 FMA 标志会影响优化器本身。这似乎意味着我们的函数必须输出具有明显不同值的值,具体取决于是否启用了矢量化。
对于那些感兴趣的人,这里是完整的cmake文件
cmake_minimum_required(VERSION 3.5)
# If a build type is not passed to cmake, then use this...
if(NOT CMAKE_BUILD_TYPE)
# set(CMAKE_BUILD_TYPE Release)
set(CMAKE_BUILD_TYPE Debug)
endif()
# If you are debugging, generate symbols.
set(CMAKE_CXX_FLAGS_DEBUG "-g")
# If in release mode, use all possible optimizations
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
# We need c++11
set(CMAKE_CXX_STANDARD 11)
# Show us all of the warnings and enable all vectorization options!!!
# I must be crazy because these vectorization flags seem to have no effect.
set(CMAKE_CXX_FLAGS "-Wall -mavx -mfma")
if (CMAKE_SYSTEM_NAME MATCHES "CYGWIN")
include_directories(../../Eigen/
/cygdrive/c/coin/windows/ipopt/include/coin/
/cygdrive/c/coin/windows/ipopt/include/coin/ThirdParty/)
find_library(IPOPT_LIBRARY ipopt HINTS /cygdrive/c/coin/windows/ipopt/lib/)
else ()
include_directories(../../Eigen/
../../coin/CoinIpopt/build/include/coin/
../../coin/CoinIpopt/build/include/coin/ThirdParty/)
find_library(IPOPT_LIBRARY ipopt HINTS ../../coin/CoinIpopt/build/lib/)
endif ()
# Build the c++ functions into an executable
add_executable(trajectory_optimization main.cpp)
# Link all of the libraries together so that the C++-executable can call IPOPT
target_link_libraries(trajectory_optimization ${IPOPT_LIBRARY})
如果您的算法在数值上不稳定,则启用 FMA 将导致不同的舍入行为,这可能会导致非常不同的结果。此外,在 Eigen 中启用 AVX 将导致不同的加法顺序,并且由于浮点数学是非关联的,这也可能导致行为略有不同。
为了说明为什么非结合性可以产生影响,当使用 SSE3 或 AXVa[8]
添加 8 个连续双精度时,Eigen 通常会生成等效于以下内容的代码:
// SSE:
double t[2]={a[0], a[1]};
for(i=2; i<8; i+=2)
t[0]+=a[i], t[1]+=a[i+1]; // addpd
t[0]+=t[1]; // haddpd
// AVX:
double t[4]={a[0],a[1],a[2],a[3]};
for(j=0; j<4; ++j) t[j]+=a[4+j]; // vaddpd
t[0]+=t[2]; t[1]+=t[3]; // vhaddpd
t[0]+=t[1]; // vhaddpd
如果没有更多细节,很难说出您的案件究竟发生了什么。