关于milvus:Milvus-编译环境演进

11次阅读

共计 6112 个字符,预计需要花费 16 分钟才能阅读完成。

一、手写动静链接

Milvus 代码库分为了 C++Go 两个局部,Go 局部负责零碎主体架构、分布式系统、存储 / 查问链路等,C++ 局部负责查问、索引引擎专一于单机场景下的高性能,两者之间通过 cgo 接口调用。

为了保护两种语言的代码,就须要退出两种语言的生态。Go 作为一个年老、古代的语言,开箱自带包治理、自动化测试框架和丰盛的规范库;而经典的 C++ 就走向了另一个极其,尽管有极致的性能和可控的内存治理,但生态过于碎片化。幸好在 build system 畛域,CMake 有成为事实标准的趋势。

Milvus 很天然的抉择 CMake 作为 C++ 构建零碎,通过编写 CMakeLists.txt 形容要生成的 library 和 headers,而 Go 则通过 cgo 接口链接到相应的 library,在晚期版本里是这样写的:

/*
#cgo CFLAGS: -I${SRCDIR}/../core/output/include
#cgo darwin LDFLAGS: -L${SRCDIR}/../core/output/lib -lmilvus_segcore -Wl,-rpath,"${SRCDIR}/../core/output/lib"
#cgo linux LDFLAGS: -L${SRCDIR}/../core/output/lib -lmilvus_segcore -Wl,-rpath=${SRCDIR}/../core/output/lib
#include "segcore/collection_c.h"
#include "common/type_c.h"
#include "segcore/segment_c.h"
*/
import "C"
import (
        "errors"
        "fmt"
        "unsafe"
        "github.com/milvus-io/milvus/internal/util/cgoconverter"
)

不难发现这样写有几个问题:

1. 不同操作系统须要指定不同的编译参数

2. hard code 库文件门路,耦合重大,不利于保护

以上两个问题绝对容易解决,在应用第三方 go library 时,问题会更难解决,例如 Milvus 应用了 https://github.com/tecbot/gorocksdb 作为 Go 的 rocksdb 接口。

gorocksdb 须要批改 CGO 的一系列 go env 能力编译胜利,究其原因也是因为 gorocksdb 在应用 rocksdb library 时没有指定 library 和 header 的门路,必须在零碎门路中能力找到 librocksdb。

package gorocksdb

// #include "stdlib.h"
// #include "rocksdb/c.h"
import "C"
import (
        "reflect"
        "unsafe"
)

这就导致了 Milvus 的编译脚本中须要 hack go env 能力顺利编译:

go env -w CGO_CFLAGS="-I${OUTPUT_LIB}/include"
ldflags=""if [-f"${OUTPUT_LIB}/lib/librocksdb.a" ]; then
     case "${unameOut}" in
          Linux*)     ldflags="-L${OUTPUT_LIB}/lib -l:librocksdb.a -lstdc++ -lm -lz";;
          Darwin*)    ldflags="-L${OUTPUT_LIB}/lib -lrocksdb -stdlib=libc++ -lm -lz -lbz2 -ldl";;
          *)          echo "UNKNOWN:${unameOut}"; exit 0;
     esac
else
     case "${unameOut}" in
          Linux*)     ldflags="-L${OUTPUT_LIB}/lib64 -l:librocksdb.a -lstdc++ -lm -lz";;
          Darwin*)    ldflags="-L${OUTPUT_LIB}/lib64 -lrocksdb -stdlib=libc++ -lm -lz -lbz2 -ldl";;
          *)          echo "UNKNOWN:${unameOut}" ; exit 0;
      esac
fi

if ["$MSYSTEM" == "MINGW64"] ; then
  ldflags="-L${OUTPUT_LIB}/lib -lrocksdb -lstdc++ -lm -lz -lshlwapi -lrpcrt4"
fi

if [[$(arch) == 'arm64' ]]; then
  go env -w GOARCH=arm64
fi

go env -w CGO_LDFLAGS="$ldflags" && GO111MODULE=on
go get github.com/tecbot/gorocksdb

二、pkg-config 链接治理

晚期的做法也不是不能用 😏,顶多净化一下环境变量,搭开发环境的时候苦楚一次,容忍度比拟高的同学也能够承受。

为了让更多的开发者顺利的在本地能开发 Milvus,以上问题急需解决。于是在 https://github.com/milvus-io/milvus/pull/17502 里引入了 pkg-config 治理 library 和 header 门路。

在 Milvus 里须要做三个革新:

一是 在 C++ 生成动态链接库时同时生成 pkg-config 的 .pc 文件

function(MILVUS_ADD_PKG_CONFIG MODULE)
    configure_file(${MODULE}.pc.in "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.pc" @ONLY)
    install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${MODULE}.pc"
          DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/")
endfunction()
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@

Name: Milvus Segcore
Description: Segcore modules for Milvus
Version: @MILVUS_VERSION@

Libs: -L${libdir} -lmilvus_segcore
Cflags: -I${includedir}

二是 在 go 文件中通过 pkg-config 指定 pc 文件

package querynode

/*
#cgo pkg-config: milvus_segcore

#include "segcore/collection_c.h"
#include "common/type_c.h"
#include "segcore/segment_c.h"
*/
import "C"
import (
        "errors"
        "fmt"
        "unsafe"
        "github.com/milvus-io/milvus/internal/util/cgoconverter"
)

三是 在编译时批改将 pc 文件门路退出到环境变量中


unameOut="$(uname -s)"
case "${unameOut}" in
    Linux*)     
      export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:$ROOT_DIR/internal/core/output/lib/pkgconfig:$ROOT_DIR/internal/core/output/lib64/pkgconfig"
      export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:$ROOT_DIR/internal/core/output/lib:$ROOT_DIR/internal/core/output/lib64"
      export RPATH=$LD_LIBRARY_PATH;;
    Darwin*)    
      export PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:$ROOT_DIR/internal/core/output/lib/pkgconfig"
      export DYLD_LIBRARY_PATH=$ROOT_DIR/internal/core/output/lib
      export RPATH=$DYLD_LIBRARY_PATH;;
    MINGW*)          
      extra_path=$(cygpath -w "$ROOT_DIR/internal/core/output/lib")
      export PKG_CONFIG_PATH="${PKG_CONFIG_PATH};${extra_path}\pkgconfig"
      export LD_LIBRARY_PATH=$extra_path
      export RPATH=$LD_LIBRARY_PATH;;
    *)
      echo "does not supported"
esac

通过以上的批改,Milvus 的代码无需 hard code library 门路,也无需 hack 环境变量,就能够无效的解决开发环境的搭建问题。

三、conan 包治理

在 2.0 之前,Milvus C++ 局部的内部依赖不多,仅有 Boost、Protobuf、Arrow、GTest 等出名的第三方库,而有些曾经在 linux 发行版里自带,只有通过 apt、yum、brew 等命令间接装置即可应用。但随着 C++ 引擎反对的索引越来越多、性能愈发简单,依赖项也急剧收缩,在 2.2 系列里开始引入 aws-cpp-sdk、marisa、json 等 library,可见的将来中会引入更多的依赖项,C++ 包治理的需要也就提上了日程。

conan 是目前几个 c++ 包治理产品中绝对成熟的,conancenter 中 library 也绝对短缺,跟 CMake 集成很容易。所以在 https://github.com/milvus-io/milvus/pull/19920 中尝试将 conan 引入作为包管理器。

C++ 编译受操作系统、编译器类型、C++ 版本、libstdc++ 版本影响很大,这些变量交错在一起会产生很多意想不到的谬误,这里把遇到的一些问题整顿一番。

1. 引入 conan 很容易,只有定义 conanfile.txt,在 CMakefiles.txt 中退出几行代码即可。

[requires]
rocksdb/6.29.5
boost/1.80.0
onetbb/2021.3.0
zstd/1.5.2
arrow/8.0.1
openssl/1.1.1q
aws-sdk-cpp/1.9.234
benchmark/1.7.0
gtest/1.8.1
protobuf/3.9.1
rapidxml/1.13
yaml-cpp/0.7.0
marisa/0.2.6
zlib/1.2.13

[generators]
cmake

[options]
rocksdb:shared=True
arrow:parquet=True
arrow:compute=True
arrow:with_zstd=True
aws-sdk-cpp:text-to-speech=False
aws-sdk-cpp:transfer=False

[imports]
lib, *.dylib -> ./lib
lib, *.dll -> ./lib
lib, *.so* -> ../lib

2. 不同的操作系统,须要抉择不同的 libstdcxx 版本。

unameOut="$(uname -s)"
case "${unameOut}" in
  Darwin*)
    conan install ${CPP_SRC_DIR} --install-folder conan --build=missing -s compiler.libcxx=libc++
    ;;
  Linux*)
    # gcc4.8 及以下不反对 c++11, 而以上的版本如果参数中蕴含 --with-default-libstdcxx-abi=gcc4-compatitable 也会应用不同的 symbol,同样编译报错
    if [[`gcc -v 2>&1 | sed -n 's/.*\(--with-default-libstdcxx-abi\)=\(\w*\).*/\2/p'` == "gcc4" ]]; then
      conan install ${CPP_SRC_DIR} --install-folder conan --build=missing
    else 
      conan install ${CPP_SRC_DIR} --install-folder conan --build=missing -s compiler.libcxx=libstdc++11
    fi 
    ;;
  *)
    echo "Do not support"   
    ;;
    
  esac

3. 对于 Centos7 等较老版本 Linux,libstdc++ 版本太低,会导致 link 失败,简略的解决办法是应用 anaconda 带的 libstdc++.so。

mkdir /tmp/stdlib && cd /tmp/stdlib && \
    wget https://repo.anaconda.com/archive/Anaconda3-2019.07-Linux-x86_64.sh && \
    sh Anaconda3-2019.07-Linux-x86_64.sh -b -p conda && \
    cp conda/lib/libstdc++.so.6.0.26 /usr/lib64 && \
    rm /usr/lib64/libstdc++.so.6 && \
    ln -s /usr/lib64/libstdc++.so.6.0.26 /usr/lib64/libstdc++.so.6 && \
    cp conda/lib/libatomic.so.1.2.0 /usr/lib64 && \
    ln -s /usr/lib64/libatomic.so.1.2.0 /usr/lib64/libatomic.so && \
    ln -s /usr/lib64/libatomic.so.1.2.0 /usr/lib64/libatomic.so.1 && \
    rm -rf /tmp/stdlib

能够通过命令 strings /lib64/libstdc++.so.6 | grep GLIBC 查看其版本。

4. conanfile.txt 中的参数解释。

[options]
rocksdb:shared=True
# Milvus 须要 parquet 反对
arrow:parquet=True
arrow:compute=True
arrow:with_zstd=True
# aws-sdk-cpp 会把 aws 所有的性能都蕴含在内,text-to-speech 须要太多的依赖项,须要精简
aws-sdk-cpp:text-to-speech=False 
aws-sdk-cpp:transfer=False

5. windows 有诸多 subsystem(cygwin、mingw、wsl),以及不同的编译器(gcc、clang、visual studio),临时未找到适合的选项。

以上就是对于 Milvus 编译环境的演进过程。咱们心愿通过一直地优化和改良,帮忙用户更不便地应用 Milvus,更释怀、更简略地享受到向量检索、召回的价值和乐趣!

正文完
 0