VPP多架构处理器支持

news2024/12/26 20:47:05

对于转发层面的关键节点(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();      \
    }                                   \
}                                   \

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于HTML和CSS的校园网页设计与实现

摘要 随着计算机、互联网与通信技术的进步&#xff0c;Internet在人们的学习、工作和生活中的地位也变得越来越高&#xff0c;校园网站已经成为学校与学生&#xff0c;学生与学生之间交流沟通的重要平台&#xff0c;对同学了解学校内发生的各种事情起到了重要的作用。学校网站…

Secured Finance 推出 TVL 激励计划以及基于 FIL 的稳定币

Secured Finance 是新一代 DeFi 2.0 协议&#xff0c;其正在推出基于 FIL 的稳定币、固定收益市场以及具有吸引力的 TVL 激励计划&#xff0c;以助力 Filecoin 构建更强大的去中心化金融生态体系&#xff0c;并为 2025 年初 Secured Finance 协议代币的推出铺平道路。Secure Fi…

WebRover :一个功能强大的 Python 库,用于从 Web 内容生成高质量的数据集,专为训练大型语言模型和 AI 应用程序而设计。

2024-11-30 &#xff0c;由Area-25团队开发的一个专门用于生成高质量网络内容数据集的Python库。该数据集旨在为大型语言模型&#xff08;LLM&#xff09;和人工智能应用的训练提供丰富的数据资源。 数据集地址&#xff1a;WebRover Dataset|自然语言处理数据集|AI模型训练数据…

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器(模块化编写)

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器&#xff08;模块化编写&#xff09; 1. 实验要求2. 功能分析3. 模块设计4. 波形图4.1 按键消抖模块4.2 按键控制蜂鸣器模块 5.代码编写5.1 rtl代码5.2 测试代码 6. 代码仿真7. 添加约束文件并分析综合 在上期的内容中&…

Android 分词的两种方式

前言&#xff1a; 本文分别介绍了原生和三方(Jieba)两种分词方式的使用和注意事项 1、安卓原生BreakIterator分词 比较简单&#xff0c;但是效果不太行 /*** 功能&#xff1a;原生分词* 参数&#xff1a;text&#xff1a;需要分词的语句* 返回值&#xff1a;return&#xf…

python之Django连接数据库

文章目录 连接Mysql数据库安装Mysql驱动配置数据库信息明确连接驱动定义模型在模型下的models.py中定义表对象在settings.py 中找到INSTALLED_APPS添加创建的模型 测试testdb.py中写增删改查操作urls.py添加请求路径启动项目进行测试 连接Mysql数据库 安装Mysql驱动 pip inst…

JavaWeb学习(1)(同步或异步请求、依赖jQuery简单实现Ajax技术)

目录 一、Web的基本流程与页面局部刷新。 &#xff08;1&#xff09;web开发时基本流程。 &#xff08;2&#xff09;页面的"全局刷新"与"局部刷新"。 二、Ajax技术。 &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;基本特点。 1、与服务…

spark sql 环境安装,java 默认路径和 安装配置!

yum安装java 查看默认路径 update-alternatives --config java # Java 环境变量 export JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jreexport PATH$JAVA_HOME/bin:$PATH# Spark 环境变量 export SPARK_HOME/home/vagrant/soft/sparkexport PATH…

网络层总结

网络层任务&#xff1a; 分组 从源主机 经多个网络/多段链路 传输到目的主机 两种重要的功能&#xff1a; 分组转发、 路由选择 网络层向其上层提供的两种服务 —— 面向连接的虚电路服务、无连接的数据报服务 面向连接的虚电路服务&#xff1a; 可靠通…

python学习笔记15 python中的类

上一篇我们介绍了python中的库 &#xff0c;学习了一些常见的内置库。详细内容可点击–>python学习笔记14 python中的库&#xff0c;常见的内置库&#xff08;random、hashlib、json、时间、os&#xff09; 这一篇我们来看一下python中的类 创建一个类 class 类的名称():de…

MySQL数据集成到广东省追溯平台的销售信息同步方案

销售信息同步--外购上报流程2&#xff1a;MySQL数据集成到广东省特殊食品电子追溯平台 在现代数据驱动的业务环境中&#xff0c;确保销售信息的准确性和及时性至关重要。本文将分享一个具体的技术案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将MySQL中的销售信…

【推荐算法】推荐系统的评估

这篇文章是笔者阅读《深度学习推荐系统》第五章推荐系统的评估的学习笔记&#xff0c;在原文的基础上增加了自己的理解以及内容的补充&#xff0c;在未来的日子里会不断完善这篇文章的相关工作。 文章目录 离线评估划分数据集方法客观评价指标P-R曲线ROC/AUCmAPNDCG A/B 测试分…

移植NIOS10.1工程,NIOS10.1路径修改

移植NIOS10.1工程&#xff0c;NIOS10.1路径修改 因工程的需要&#xff0c;使用的NIOS10.1&#xff0c;比较老&#xff0c;这个版本的路径是使用的绝对路径&#xff0c;导致移植工程市回报路径的错误&#xff0c;在13.1之后改为了相对路径&#xff0c;不存在这个问题。 需要修…

WPF+LibVLC开发播放器-LibVLC播放控制

接上一篇&#xff1a; LibVLC在C#中的使用 实现LibVLC播放器播放控制 界面 界面上添加一个Button按钮用于控制播放 <ButtonGrid.Row"1"Width"88"Height"24"Margin"10,0,0,0"HorizontalAlignment"Left"VerticalAlignme…

iOS与Windows间传文件

想用数据线从 windows 手提电脑传文件入 iPhone&#xff0c;有点迂回。 参考 [1]&#xff0c;要在 windows 装 Apple Devices。装完、打开、插线之后会检测到手机&#xff0c;界面&#xff1a; 点左侧栏「文件」&#xff0c;不是就直接可以传&#xff0c;而是要通过某个应用传…

两个畸变矩阵相乘后还是一个2*2的矩阵,有四个畸变元素。1、畸变矩阵吸收了法拉第矩阵。2、畸变矩阵也给法拉第旋转角带来模糊(求解有多种可能)

角度一&#xff1b;恢复畸变的时候也把法拉第旋转恢复了 角度二&#xff1a;求解法拉第旋转角的时候 前面乘的复系数的不同也会带来法拉第旋转角和畸变的不同解 注意&#xff1a;无论多少个畸变矩阵相乘&#xff0c;结果都是2*2的矩阵&#xff0c;也就是畸变参数可以减少…

【Linux】基础IO_文件系统IO_“一切皆文件”_缓冲区

目录 1. 理解"⽂件" 1-1 狭义理解 1-2 ⼴义理解 1-3 ⽂件操作的归类认知 1-4 系统⻆度 访问文件&#xff0c;需要先打开文件&#xff01;那么是由谁打开文件&#xff1f;&#xff1f;&#xff1f; 操作系统要不要把被打开的文件管理起来&#xff1f; 2. 回顾…

【LeetCode】498.对角线遍历

无论何时何地&#xff0c;我都认为对于一道编程题&#xff0c;思考解法的时间用于是实际动手解决问题的2倍&#xff01;如果敲键盘编码需要5min&#xff0c;那么思考解法的过程至少就需要10分钟。 1. 题目 2. 思想 其实这就是一道模拟题&#xff0c;难度中等。做这种题的关键就…

Jupyter Lab打印日志

有时候在 jupyter 中执行运行时间较长的程序&#xff0c;且需要一直信息&#xff0c;但是程序执行到某些时候就不再打印了。 可以开启 日志控制台&#xff0c;将日志信息记录在控制台中。 参考&#xff1a;https://www.autodl.com/docs/jupyterlab/

Hbase整合Mapreduce案例1 hdfs数据上传至hbase中——wordcount

目录 整合结构准备java API 编写pom.xmlMain.javaMap.javaReduce 运行 整合结构 准备 上传hdfs data.txt数据 data.txt I am wunaiieq QAQ 123456 Who I am In todays interconnected world the role of technology cannot be overstated It has revolutionized the way we …