带有Eigen和IPOPT的矢量化标志

时间:2018-03-23 09:45:36

标签: eigen avx eigen3 ipopt fma

我有一些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})

1 个答案:

答案 0 :(得分:2)

启用FMA将导致不同的舍入行为,如果您的算法在数值上不稳定,则可能导致非常不同的结果。此外,在Eigen中启用AVX将导致不同的添加顺序,并且由于浮点数学是非关联的,这也可能导致略微不同的行为。

为了说明为什么非关联性可以产生影响,当使用SSE3或AXV添加8个连续的双精度a[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

如果没有更多细节,很难说出你的情况究竟会发生什么。