VPP多架构处理器支持

对于转发层面的关键节点(node),VPP针对处理器架构编译多份代码,在运行时检测处理器架构,动态确定使用的代码分支。VPP提供两种对多处理器架构的支持,除了节点函数外,还可指定任意函数支持多架构。

node节点多架构

编译系统将node函数所在文件编译多次,每次使用不同的编译选项,生成多个node函数版本。每个node的构造函数(constructor)根据处理器硬件选择对应的版本,构造函数将结构vlib_node_registration_t的成员function附上选择的函数版本。

VLIB_NODE_FN (ip4_rewrite_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
                 vlib_frame_t * frame)
{
  if (adj_are_counters_enabled ())
    return ip4_rewrite_inline (vm, node, frame, 1, 0, 0);
  else
    return ip4_rewrite_inline (vm, node, frame, 0, 0, 0);
}

所以在使用宏VLIB_REGISTER_NODE定义节点时(初始化vlib_node_registration_t结构),不要指定function成员。

VLIB_REGISTER_NODE (ip4_rewrite_node) = {
  .name = "ip4-rewrite",
  .vector_size = sizeof (u32),

  ...
};

node函数所在文件中,不重要的函数,如错误字符串定义和报文追踪函数(trace),不需要支持多处理器架构。使用"#ifndef CLIB_MARCH_VARIANT...#endif"来去除。

#ifndef CLIB_MARCH_VARIANT
/* Common trace function for all ip4-forward next nodes. */
void
ip4_forward_next_trace (vlib_main_t * vm,
            vlib_node_runtime_t * node,
            vlib_frame_t * frame, vlib_rx_or_tx_t which_adj_index)
{

}
#endif

CMakeLists.txt文件中需要将ip/ip4_forward.c文件同时添加到VNET_SOURCES和VNET_MULTIARCH_SOURCES链表,add_vpp_library将对其处理。

list(APPEND VNET_SOURCES
  ip/ip4_forward.c
)
list(APPEND VNET_MULTIARCH_SOURCES
  ip/ip4_forward.c
)

add_vpp_library(vnet
  SOURCES ${VNET_SOURCES}
  MULTIARCH_SOURCES ${VNET_MULTIARCH_SOURCES}
  INSTALL_HEADERS ${VNET_HEADERS}

节点函数调用的子函数(ip4_rewrite_inline),需要使用always_inline修饰符。否则,编译器很可能不会生成多架构代码。可以在vpp运行时用perf top命令检查,比如当前处理器支持avx2,可以看到许多类似"xxx_node_fn_avx2"名字的节点处理函数。如果某个节点的名称为"xxx_inline.isra.1",表明其很可能没有使用always_inline修饰符,而是使用了static inline。

always_inline uword
ip4_rewrite_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
            vlib_frame_t *frame, int do_counters, int is_midchain,
            int is_mcast)
{

任意函数多架构

可以使用CLIB_MARCH_FN修饰需要支持多架构的函数,在调用函数时使用CLIB_MARCH_FN_SELECT选择特定的版本。

CLIB_MARCH_FN (svm_fifo_copy_to_chunk, void, svm_fifo_t *f,
           svm_fifo_chunk_t *c, u32 tail_idx, const u8 *src, u32 len,
           fs_sptr_t *last)
{

类似于node节点的多架构支持,CLIB_MARCH_FN宏所在源文件也需要多次编译。比如,在生成的vpp程序中,可能出现多个此函数的版本:svm_fifo_copy_to_chunk_avx2、svm_fifo_copy_to_chunk_avx512等。

函数svm_fifo_copy_to_chunk调用了以上的svm_fifo_copy_to_chunk函数,由于svm_fifo_copy_to_chunk为多架构版本,svm_fifo_copy_to_chunk函数不需要进行多架构编译,使用#ifndef CLIB_MARCH_VARIANT禁止多架构编译功能。

CLIB_MARCH_FN_SELECT的开销相等于间接函数调用。

#ifndef CLIB_MARCH_VARIANT

static inline void
svm_fifo_copy_to_chunk (svm_fifo_t *f, svm_fifo_chunk_t *c, u32 tail_idx,
            const u8 *src, u32 len, fs_sptr_t *last)
{
  CLIB_MARCH_FN_SELECT (svm_fifo_copy_to_chunk) (f, c, tail_idx, src, len,
                         last);
}
#endif

另外,还可以使用CLIB_MARCH_FN_REGISTRATION和CLIB_MARCH_FN_POINTER来实现函数的多架构编译。

多处理器架构

在文件vpp/src/cmake/cpu.cmake文件中,加入对多架构的支持(add_vpp_march_variant)。对x86_64处理器,默认使用corei7和corei7-avx编译选项。多架构支持增加了haswell、tremont、skylake和icelake四种架构。默认关闭了tremount架构的支持(OFF)。skylake和icelake架构的支持事编译器而定。

if(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64.*|x86_64.*|AMD64.*")
  set(VPP_DEFAULT_MARCH_FLAGS -march=corei7 -mtune=corei7-avx)

  add_vpp_march_variant(hsw
    FLAGS -march=haswell -mtune=haswell
  )
  add_vpp_march_variant(trm
    FLAGS -march=tremont -mtune=tremont
    OFF
  )
  if (GNU_ASSEMBLER_AVX512_BUG)
     message(WARNING "AVX-512 multiarch variant(s) disabled due to GNU Assembler bug")
  else()
    add_vpp_march_variant(skx
      FLAGS -march=skylake-avx512 -mtune=skylake-avx512 -mprefer-vector-width=256
    )
    add_vpp_march_variant(icl
      FLAGS -march=icelake-client -mtune=icelake-client -mprefer-vector-width=512
    )
  endif()

使用cmake内置函数check_c_compiler_flag检查C编译器是否支持指定的flag,对于支持的flag添加到MARCH_VARIANTS,比如最终的值可能为:(hsw; -march=haswell -mtune=haswell;skx; -march=skylake-avx512 -mtune=skylake-avx512 -mprefer-vector-width=256)。MARCH_VARIANTS_DISABLED的值为:(trm; -march=tremont -mtune=tremont)。

check_c_compiler_flag函数参见https://cmake.org/cmake/help/latest/module/CheckCCompilerFlag.html

macro(add_vpp_march_variant v)
  if(ARG_FLAGS)
    set(flags_ok 1)
    set(fs "")
    foreach(f ${ARG_FLAGS})
      string(APPEND fs " ${f}")
      string(REGEX REPLACE "[-=+]" "_" sfx ${f})
      if(NOT DEFINED compiler_flag${sfx})
        check_c_compiler_flag(${f} compiler_flag${sfx})
      endif()
      if(NOT compiler_flag${sfx})
        unset(flags_ok)
      endif()
    endforeach()
    if(flags_ok)
      string(TOUPPER ${v} uv)
      if (VPP_MARCH_VARIANT_${uv})
        list(APPEND MARCH_VARIANTS "${v}\;${fs}")
      else()
        list(APPEND MARCH_VARIANTS_DISABLED "${v}\;${fs}")
      endif()
    endif()
  endif()
endmacro()

多架构源文件

使用add_vpp_library添加多架构源文件,最终调用函数vpp_library_set_multiarch_sources。

macro(add_vpp_library lib)
  cmake_parse_arguments(ARG
    "LTO"
    "COMPONENT"
    "SOURCES;MULTIARCH_SOURCES;API_FILES;LINK_LIBRARIES;INSTALL_HEADERS;DEPENDS"
    ${ARGN}
  )
  if(ARG_MULTIARCH_SOURCES)
    vpp_library_set_multiarch_sources(${lib} DEPENDS ${ARG_DEPENDS} SOURCES ${ARG_MULTIARCH_SOURCES})
  endif()

为每个架构生成不同的库文件,对于vnet,生成基础的vnet,以及vnet_skx和vnet_hsw。

macro(vpp_library_set_multiarch_sources lib)
  cmake_parse_arguments(ARG
    ""
    ""
    "SOURCES;DEPENDS;FORCE_ON"
    ${ARGN}
  )
  set(VARIANTS "${MARCH_VARIANTS}")

  foreach(V ${VARIANTS})
    list(GET V 0 VARIANT)
    list(GET V 1 VARIANT_FLAGS)
    set(l ${lib}_${VARIANT})
    add_library(${l} OBJECT ${ARG_SOURCES})
    set_target_properties(${l} PROPERTIES POSITION_INDEPENDENT_CODE ON)
    target_compile_definitions(${l} PUBLIC CLIB_MARCH_VARIANT=${VARIANT})
    separate_arguments(VARIANT_FLAGS)
    target_compile_options(${l} PUBLIC ${VARIANT_FLAGS})
    target_sources(${lib} PRIVATE $<TARGET_OBJECTS:${l}>)
  endforeach()
endmacro()

如下生成的不同架构的目标文件。vnet_objs.dir目录包括所有源文件的目标文件,而以后缀hsw和skx的目录仅包括需要多架构支持的源文件的目标文件。

$ ls build-root/build-vpp-native/vpp/CMakeFiles/vnet/CMakeFiles/vnet_objs.dir/ip/
ip_in_out_acl.c.o ip4_punt_drop.c.o   ip6_punt_drop.c.o       ip_path_mtu_node.c.o  reass
ip6_forward.c.o   ip4_forward.c.o     ip6_hop_by_hop.c.o      ip4_input.c.o     ip6_input.c.o                        
ip4_mtrie.c.o     ip6_link.c.o        ip_frag.c.o             ip_types.c.o      ...
$ 
$ ls build-root/build-vpp-native/vpp/CMakeFiles/vnet/CMakeFiles/vnet_hsw.dir/ip
ip4_forward.c.o  ip4_punt_drop.c.o  ip6_hop_by_hop.c.o  ip6_punt_drop.c.o  ip_path_mtu_node.c.o  reass
ip4_input.c.o    ip6_forward.c.o    ip6_input.c.o       ip_in_out_acl.c.o  punt_node.c.o
$
$ 
$ ls build-root/build-vpp-native/vpp/CMakeFiles/vnet/CMakeFiles/vnet_skx.dir/ip
ip4_forward.c.o  ip4_punt_drop.c.o  ip6_hop_by_hop.c.o  ip6_punt_drop.c.o  ip_path_mtu_node.c.o  reass
ip4_input.c.o    ip6_forward.c.o    ip6_input.c.o       ip_in_out_acl.c.o  punt_node.c.o

反编译,可看到如下的三个函数,分别为基础node函数ip4_lookup_node_fn,和skx以及hsw结构版本的函数。

0000000000002ad0 <ip4_lookup_node_fn>:
ip4_lookup_node_fn():
    2ad0:   41 57                   push   %r15

000000000000e5d0 <ip4_lookup_node_fn_skx>:
ip4_lookup_node_fn_skx():
    e5d0:   55                      push   %rbp

000000000000f010 <ip4_lookup_node_fn_hsw>:
ip4_lookup_node_fn_hsw():
    f010:   55                      push   %rbp

NODE节点宏VLIB_NODE_FN

之后用到的两个宏CLIB_MARCH_SFX和CLIB_MULTIARCH_FN等价。为node处理函数增加架构名称后缀,例如通过指定gcc选项-DCLIB_MARCH_VARIANT=hsw,node处理函数增加hsw后缀。

#ifdef CLIB_MARCH_VARIANT
#define __CLIB_MULTIARCH_FN(a,b) a##_##b
#define _CLIB_MULTIARCH_FN(a,b) __CLIB_MULTIARCH_FN(a,b)
#define CLIB_MULTIARCH_FN(fn) _CLIB_MULTIARCH_FN(fn,CLIB_MARCH_VARIANT)
#else
#define CLIB_MULTIARCH_FN(fn) fn
#endif

#define CLIB_MARCH_SFX CLIB_MULTIARCH_FN

节点函数定义宏VLIB_NODE_FN,node节点函数名称首先增加的是_fn字符的后缀,其次是以上的架构后缀(hsw)。__clib_constructor函数为编译器修饰符,指明此为构造函数,其在main函数之前运行。

对于hsw架构,定义了vlib_node_fn_registration_t结构: ( n o d e n a m e ) h s w 。定义了构造函数: (node name)_hsw。定义了构造函数: (nodename)hsw。定义了构造函数:(node name)_multiarch_register_hsw,注册到全局链表。

#define VLIB_NODE_FN(node)                                                         \
  uword CLIB_MARCH_SFX (node##_fn) ();                                             \
  static vlib_node_fn_registration_t CLIB_MARCH_SFX (node##_fn_registration) = {   \
    .function = &CLIB_MARCH_SFX (node##_fn),                                       \
  };                                                                               \
  static void __clib_constructor CLIB_MARCH_SFX (node##_multiarch_register) (void) \
  {                                                                                \
    extern vlib_node_registration_t node;                                          \
    vlib_node_fn_registration_t *r;                                                \
    r = &CLIB_MARCH_SFX (node##_fn_registration);                                  \
    r->march_variant = CLIB_MARCH_SFX (CLIB_MARCH_VARIANT_TYPE);                   \
    r->next_registration = node.node_fn_registrations;                             \
    node.node_fn_registrations = r;                                                \
  }                                                                                \
  uword CLIB_MARCH_SFX (node##_fn)

对于x86_64平台,其中march_variant可能得取值有以下几个:

#if defined(__x86_64__)
#define foreach_march_variant                                                 \
  _ (hsw, "Intel Haswell")                                                    \
  _ (trm, "Intel Tremont")                                                    \
  _ (skx, "Intel Skylake (server) / Cascade Lake")                            \
  _ (icl, "Intel Ice Lake")
#else
#define foreach_march_variant
#endif

typedef enum
{
  CLIB_MARCH_VARIANT_TYPE = 0,
#define _(s, n) CLIB_MARCH_VARIANT_TYPE_##s,
  foreach_march_variant
#undef _
    CLIB_MARCH_TYPE_N_VARIANTS
} clib_march_variant_type_t;

选择节点架构函数

函数vlib_node_get_preferred_node_fn_variant选择优先级最高的架构函数,priority值越大优先级越高。

vlib_node_function_t *
vlib_node_get_preferred_node_fn_variant (vlib_main_t *vm, vlib_node_fn_registration_t *regs)
{
  vlib_node_main_t *nm = &vm->node_main;
  vlib_node_fn_registration_t *r;
  vlib_node_fn_variant_t *v;
  vlib_node_function_t *fn = 0;
  ...

  r = regs;
  while (r) {
    v = vec_elt_at_index (nm->variants, r->march_variant);
    if (v->priority > priority) {
      priority = v->priority;
      fn = r->function;
    }
    r = r->next_registration;
  }

不同架构对应的优先级如下:

static inline int clib_cpu_march_priority_icl ()
{
  if (clib_cpu_supports_avx512_bitalg ())
    return 200;
  return -1;
}
static inline int clib_cpu_march_priority_skx ()
{
  if (clib_cpu_supports_avx512f ())
    return 100;
  return -1;
}

static inline int clib_cpu_march_priority_hsw ()
{
  if (clib_cpu_supports_avx2 ())
    return 50;
  return -1;
}

CLIB_MARCH_FN宏

与以上VLIB_NODE_FN类似,使用CLIB_MARCH_SFX定义多架构函数。不同之处在于使用CLIB_MARCH_FN_SELECT来选择运行时使用的函数。

#define CLIB_MARCH_FN(fn, rtype, _args...)                                    \
  static rtype CLIB_MARCH_SFX (fn##_ma) (_args);                              \
  extern rtype (*fn##_selected) (_args);                                      \
  extern int fn##_selected_priority;                                          \
  CLIB_MARCH_FN_CONSTRUCTOR (fn)                                              \
  static rtype CLIB_MARCH_SFX (fn##_ma) (_args)

#define CLIB_MARCH_FN_SELECT(fn) (* fn ## _selected)

如下fn ## _selected为最终选择的函数,fn ## _selected_priority为优先级数值。每个注册的架构都执行优先级判断,选出最优的函数。

#define CLIB_MARCH_FN_CONSTRUCTOR(fn)                   \
static void __clib_constructor                      \
CLIB_MARCH_SFX(fn ## _march_constructor) (void)             \
{                                   \
  if (CLIB_MARCH_FN_PRIORITY() > fn ## _selected_priority)      \
    {                                   \
      fn ## _selected = & CLIB_MARCH_SFX (fn ## _ma);           \
      fn ## _selected_priority = CLIB_MARCH_FN_PRIORITY();      \
    }                                   \
}                                   \
相关推荐
袭烽8 天前
基于windows环境使用nvm安装多版本nodejs
vue·nodejs·node·nvm·node版本管理
Json____11 天前
前端node环境安装:nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)
前端·windows·npm·node.js·node·nvm·cnpm
代码对我眨眼睛19 天前
`pnpm` 不是内部或外部命令,也不是可运行的程序或批处理文件(问题已解决,2024/12/3
node.js·nodejs·node·配置问题
程楠楠&M20 天前
koa中间件
前端·中间件·node.js·node·koa
mixboot25 天前
macOS安装nvm node
macos·node·nvm
水w1 个月前
Node.js windows版本 下载和安装(详细步骤)
开发语言·前端·windows·npm·node
getaxiosluo1 个月前
node对接ChatGpt的流式输出的配置
人工智能·ai·chatgpt·状态模式·node·数据流
程楠楠&M1 个月前
mongoDB的安装及使用
前端·数据库·mongodb·node
fukaiit1 个月前
vue项目npm run serve出现【- Network: unavailable】(从排查到放弃)
npm·vue·node·win11