算能RISC-V通用云编译飞桨paddlepaddle@openKylin留档

尝试一在riscv里编译飞桨。

先总结:

下载飞桨代码,参照pr修改代码

然后编译

cmake ../ -DWITH_GPU=OFF -DWITH_RISCV=ON
make -j 16 TARGET=RISCV64_GENERIC

编译好后安装:

pip install paddlepaddle-0.0.0-cp38-cp38-linux_riscv64.whl -i https://mirror.baidu.com/pypi/simple

先git下载源码

git clone https://github.com/paddlepaddle/padle

配置环境

pip3 install protobuf

还有一些库等,具体可以参考编译pytorch的文档:算能RISC-V通用云开发空间编译pytorch @openKylin留档-CSDN博客

然后进入paddle目录编译

mkdir build

cd build

cmake ../

我的天,除了protobuf,竟然一下子编译完成了!

Automatic code generation for paddle/fluid/primitive succeed.
Automatic code generation for decomp interface succeed.
WITH_DLNNE:
-- Configuring done
-- Generating done
-- Build files have been written to: /root/github/paddle/build

是我肤浅了,后面还需要编译呢root@863c89a419ec:~/github/paddle/build# make

make -j 16

make -j 8 TARGET=RISCV64_GENERIC -dw 

如果碰到报错,根据报错进行处理,比如哪个第三方库编译失败,就删除那个目录,然后在third_party目录执行:git submodule update --init --recursive

然后再make 即可。

困了,让它编译去吧。

报错了

通过官网issue,发现cmake指令为:

cmake ../ -DWITH_GPU=OFF -WITH_RISCV=ON

make 指令为;

make -j 16 TARGET=RISCV64_GENERIC

发现cmake指令有问题

拼写错误,应该是:

cmake ../ -DWITH_GPU=OFF -DWITH_RISCV=ON

然后再make

看MakeFile文件里面没有RISCV选项,不知道是不是cmake编译器那边支持,先执行看看。(后来知道,人家这个RISCV是对应了专门的pr的,目前这个pr还没有合并,所以需要手工改密码)

cmake结束之后提示WITH_RISCV没有生效

手工添加飞桨对riscv的支持

修改CMakeFile文件,在WITH_ARM的后面加上:

if(WITH_RISCV)set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")set(WITH_XBYAKOFFCACHE STRING "Disable XBYAK when compiling WITH_RISCV=ON." FORCE)set(WITH_MKLOFFCACHE STRING "Disable MKL when compiling WITH_RISCV=ON." FORCE)set(WITH_AVXOFFCACHE STRING "Disable AVX when compiling WITH_AVX=OFF." FORCE)add_definitions(-DPADDLE_WITH_RISCV)
endif()

修改文件:cmake/flags.cmake

在ARM后面加上:AND NOT WITH_RISCV

同时找到了-m64的位置。因为加了这句,就不用手工删除-m64这句。

修改文件:paddle/fluid/operators/search_compute.h

一共需要修改四处,在四处加上defined(PADDLE_WITH_RISCV):

#pragma once#if !defined(PADDLE_WITH_ARM) && !defined(PADDLE_WITH_SW) && \!defined(PADDLE_WITH_MIPS) && !defined(PADDLE_WITH_LOONGARCH)!defined(PADDLE_WITH_MIPS) && !defined(PADDLE_WITH_LOONGARCH) && \!defined(PADDLE_WITH_RISCV)
#include <immintrin.h>
#endif
#include <cfloat>
@@ -103,7 +104,8 @@ void call_gemm_batched(const framework::ExecutionContext& ctx,
}#if !defined(PADDLE_WITH_ARM) && !defined(PADDLE_WITH_SW) && \!defined(PADDLE_WITH_MIPS) && !defined(PADDLE_WITH_LOONGARCH)!defined(PADDLE_WITH_MIPS) && !defined(PADDLE_WITH_LOONGARCH) && \!defined(PADDLE_WITH_RISCV)#define __m256x __m256@@ -144,7 +146,8 @@ inline void axpy(const T* x, T* y, size_t len, const T alpha) {_mm256_mul_px(mm_alpha, _mm256_load_px(x + jjj))));}
#elif defined(PADDLE_WITH_ARM) || defined(PADDLE_WITH_SW) || \defined(PADDLE_WITH_MIPS) || defined(PADDLE_WITH_LOONGARCH)defined(PADDLE_WITH_MIPS) || defined(PADDLE_WITH_LOONGARCH) || \defined(PADDLE_WITH_RISCV)PADDLE_THROW(platform::errors::Unimplemented("axpy is not supported"));
#elselll = len & ~SSE_CUT_LEN_MASK;
@@ -174,7 +177,8 @@ inline void axpy_noadd(const T* x, T* y, size_t len, const T alpha) {_mm256_store_px(y + jjj, _mm256_mul_px(mm_alpha, _mm256_load_px(x + jjj)));}
#elif defined(PADDLE_WITH_ARM) || defined(PADDLE_WITH_SW) || \defined(PADDLE_WITH_MIPS) || defined(PADDLE_WITH_LOONGARCH)defined(PADDLE_WITH_MIPS) || defined(PADDLE_WITH_LOONGARCH) || \defined(PADDLE_WITH_RISCV)PADDLE_THROW(platform::errors::Unimplemented("axpy_noadd is not supported"));
#elselll = len & ~SSE_CUT_LEN_MASK;

修改文件:paddle/fluid/platform/denormal.cc

33行if那句后面加上RISCV,变成这样:

#if !defined(GCC_WITHOUT_INTRINSICS) && !defined(PADDLE_WITH_ARM) && \!defined(PADDLE_WITH_SW) && !defined(PADDLE_WITH_MIPS) &&        \!defined(_WIN32) && !defined(PADDLE_WITH_LOONGARCH) &&           \!defined(PADDLE_WITH_RISCV)
#define DENORM_USE_INTRINSICS

修改文件paddle/phi/backends/cpu/cpu_info.cc

  } else {
#if !defined(WITH_NV_JETSON) && !defined(PADDLE_WITH_ARM) &&  \!defined(PADDLE_WITH_SW) && !defined(PADDLE_WITH_MIPS) && \!defined(PADDLE_WITH_LOONGARCH) && !defined(PADDLE_WITH_RISCV)std::array<int, 4> reg;cpuid(reg.data(), 0);int nIds = reg[0];

修改文件paddle/phi/backends/cpu/cpu_info.h

#define cpuid(reg, x) __cpuidex(reg, x, 0)
#else
#if !defined(WITH_NV_JETSON) && !defined(PADDLE_WITH_ARM) &&  \!defined(PADDLE_WITH_SW) && !defined(PADDLE_WITH_MIPS) && \!defined(PADDLE_WITH_LOONGARCH) && !defined(PADDLE_WITH_RISCV)
#include <cpuid.h>
inline void cpuid(int reg[4], int x) {

感觉离曙光很近了。

重新cmake和make,希望不会碰到“pmmintrin.h”文件找不到的错误。

发现少修改一个文件

修改文件cmake/third_party.cmake

if(WITH_POCKETFFT)include(external/pocketfft)list(APPEND third_party_deps extern_pocketfft)add_definitions(-DPADDLE_WITH_POCKETFFT)
if(WITH_RISCV)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-maybe-uninitialized") # Warnings in pocketfft_hdronly.h
endif()
endif()

11点开始编译,计时开始!15:37分编译完成,耗时4小时37分钟!

哈哈,它静静的躺在那里:

root@863c89a419ec:~/github/paddle/build/python# ls dist/
paddlepaddle-0.0.0-cp38-cp38-linux_riscv64.whl
root@863c89a419ec:~/github/paddle/build/python# pip3 install dist/paddlepaddle-0.0.0-cp38-cp38-linux_riscv64.whl  -i https://mirror.baidu.com/pypi/simple
Processing ./dist/paddlepaddle-0.0.0-cp38-cp38-linux_riscv64.whl

安装:

pip install paddlepaddle-0.0.0-cp38-cp38-linux_riscv64.whl

安装后执行import paddle 报错。

总结:

目前已编译成功。

目前import paddle报错。

调试

报错:Could NOT find PY_google.protobuf (missing: PY_GOOGLE.PROTOBUF)

Could NOT find PY_google.protobuf (missing: PY_GOOGLE.PROTOBUF)

安装protobuf

pip3 install protobuf

编译时到100%报错

[ 97%] Building CXX object gloo/CMakeFiles/gloo.dir/transport/tcp/unbound_buffer.cc.o
[100%] Linking CXX static library libgloo.a
[100%] Built target gloo
[  3%] Performing install step for 'extern_gloo'
[  3%] Completed 'extern_gloo'
[  3%] Built target extern_gloo
make: *** [Makefile:136: all] Error 2
再重新编译,到了protobuf这里报错:

-- Installing: /root/github/paddle/build/third_party/install/protobuf/lib/cmake/protobuf/protobuf-config.cmake
[  4%] Completed 'extern_protobuf'
[  4%] Built target extern_protobuf
make: *** [Makefile:136: all] Error 2

再重新编译,发现这里报错:

报错:cc1: error: '-march=loongson3a': ISA string must begin with rv32 or rv64

cc1: error: requested ABI requires '-march' to subsume the 'D' extension
cc1: error: ABI requires '-march=rv64'
make[4]: *** [Makefile:737: isamin.o] Error 1
cc1: error: '-march=loongson3a': ISA string must begin with rv32 or rv64
make[4]: *** [Makefile:611: sasum.o] Error 1
cc1: error: '-march=loongson3a': ISA string must begin with rv32 or rv64
cc1: error: '-march=loongson3a': ISA string must begin with rv32 or rv64
cc1: error: requested ABI requires '-march' to subsume the 'D' extension
cc1: error: requested ABI requires '-march' to subsume the 'D' extension
cc1: error: ABI requires '-march=rv64'
cc1: error: ABI requires '-march=rv64'
cc1: error: requested ABI requires '-march' to subsume the 'D' extension
cc1: error: ABI requires '-march=rv64'
make[4]: *** [Makefile:647: snrm2.o] Error 1
make[4]: *** [Makefile:629: ssum.o] Error 1
make[4]: *** [Makefile:701: smax.o] Error 1
make[4]: *** [Makefile:665: samax.o] Error 1
make[4]: *** [Makefile:755: ismax.o] Error 1
make[4]: *** [Makefile:773: sdsdot.o] Error 1
make[3]: *** [Makefile:164: libs] Error 1
make[2]: *** [CMakeFiles/extern_openblas.dir/build.make:86: third_party/openblas/src/extern_openblas-stamp/extern_openblas-build] Error 2
make[1]: *** [CMakeFiles/Makefile2:4001: CMakeFiles/extern_openblas.dir/all] Error 2
[  5%] Built target eager_python_c_codegen
[  5%] Built target op_map_codegen
[  5%] Built target eager_codegen
make: *** [Makefile:136: all] Error 2

参考这里重新来过:

如何在RISC-V平台编译paddle - 知乎

编译报错:

Live child 0x2af978fd70 (paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen) PID 115530
Reaping winning child 0x2af978fd70 PID 115530
Removing child 0x2af978fd70 PID 115530 from chain.
Considering target file 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build'.
 File 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build' does not exist.
  Considering target file 'eager_codegen'.
   File 'eager_codegen' does not exist.
    Considering target file 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen'.
    File 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen' was considered already.
    Considering target file 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build.make'.
    File 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build.make' was considered already.
   Finished prerequisites of target file 'eager_codegen'.
  Must remake target 'eager_codegen'.
  Successfully remade target file 'eager_codegen'.
 Finished prerequisites of target file 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build'.
Must remake target 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build'.
Successfully remade target file 'paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/build'.
make[2]: Leaving directory '/root/github/paddle/build'
Reaping winning child 0x2b00eb0510 PID 115266
Live child 0x2b00eb0510 (paddle/fluid/eager/auto_code_generator/generator/CMakeFiles/eager_codegen.dir/all) PID 115532
[  4%] Built target eager_codegen
Reaping winning child 0x2b00eb0510 PID 115532
Removing child 0x2b00eb0510 PID 115532 from chain.
make[1]: Leaving directory '/root/github/paddle/build'
Reaping losing child 0x2ad05e7c50 PID 115118
make: *** [Makefile:136: all] Error 2
Removing child 0x2ad05e7c50 PID 115118 from chain.
make: Leaving directory '/root/github/paddle/build'

在paddle/build/third_party删除eigen3目录,然后执行:git submodule update --init --recursive

问题解决。

问题没有解决,还是4%这里报错:

[  4%] Built target eager_codegen
Reaping winning child 0x2b167efa70 PID 117670
Removing child 0x2b167efa70 PID 117670 from chain.
make[1]: Leaving directory '/root/github/paddle/build'
Reaping losing child 0x2b0332dc20 PID 117206
make: *** [Makefile:136: all] Error 2
Removing child 0x2b0332dc20 PID 117206 from chain.
make: Leaving directory '/root/github/paddle/build'

重新设置make 后,cmake ../ -DWITH_GPU=OFF -WITH_RISCV=ON 编译报错

报错c++: error: unrecognized command line option '-m64'

按照文档,应该“查找makefile文件的生成逻辑,最终发现在Paddle.cmake文件中有这样一段逻辑”,将该文件中的-64去掉。

但是没有找到这个文件。找到了,在这里:

cmake/flags.cmake

  if(NOT WITH_NV_JETSON
     AND NOT WITH_ARM
     AND NOT WITH_RISCV
     AND NOT WITH_SW
     AND NOT WITH_MIPS
     AND NOT WITH_LOONGARCH)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64")
  endif()

后期通过修改源码,添加RISCV选项的方法,使代码支持RISCV,并同时使用-m64失效

在cmake的时候加上DWITH_RISCV提示没有生效

WITH_DLNNE:
-- Configuring done
-- Generating done
CMake Warning:
  Manually-specified variables were not used by the project:

    WITH_RISCV

按照飞桨官网pr,修改飞桨make相关文件。

编译报错没有 pmmintrin.h文件

[  7%] Building CXX object paddle/fluid/platform/CMakeFiles/denormal.dir/denormal.cc.o
/root/github/paddle/paddle/fluid/platform/denormal.cc:38:10: fatal error: pmmintrin.h: No such file or directory
   38 | #include <pmmintrin.h>
      |          ^~~~~~~~~~~~~
compilation terminated.
make[2]: *** [paddle/fluid/platform/CMakeFiles/denormal.dir/build.make:76: paddle/fluid/platform/CMakeFiles/denormal.dir/denormal.cc.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:4787: paddle/fluid/platform/CMakeFiles/denormal.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....

这个对应的就是paddle/fluid/platform/denormal.cc 这个文件,已经修改。

编译安装好后,

import paddle报错

oot@863c89a419ec:~/github/paddle/build# python3
Python 3.8.2 (default, Jan 18 2024, 07:05:37)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import paddle
Error: Can not import paddle core while this file exists: /usr/local/lib/python3.8/dist-packages/paddle/base/libpaddle.so
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/paddle/base/core.py", line 267, in <module>
    from . import libpaddle
ImportError: /usr/local/lib/python3.8/dist-packages/paddle/base/libpaddle.so: undefined symbol: __atomic_exchange_1

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/paddle/__init__.py", line 30, in <module>
    from .base import core  # noqa: F401
  File "/usr/local/lib/python3.8/dist-packages/paddle/base/__init__.py", line 38, in <module>
    from . import (  # noqa: F401
  File "/usr/local/lib/python3.8/dist-packages/paddle/base/backward.py", line 25, in <module>
    from . import core, framework, log_helper, unique_name
  File "/usr/local/lib/python3.8/dist-packages/paddle/base/core.py", line 377, in <module>
    if not avx_supported() and libpaddle.is_compiled_with_avx():
NameError: name 'libpaddle' is not defined

看文档,有人说是patchelf版本低的缘故,升级patchelf,再重新编译,问题依旧

https://github.com/PaddlePaddle/Paddle/issues/51536

已提issue :https://github.com/PaddlePaddle/Paddle/issues/62037

学习官网issue

risc-v芯片上编译paddle报错 · Issue #61770 · PaddlePaddle/Paddle · GitHub

学习官网改risc-v的pr

Adding a compile option to Paddle for Risc-V · PaddlePaddle/Paddle@d3db383 · GitHub

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

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

相关文章

Opencv(C++)学习 ARM上引用opencv报相关头文件找不到

简单问题记录&#xff0c;C 与C互相引用时应该多注意类似问题。 问题描述&#xff1a;在项目中&#xff0c;建立了一个interface.h提供了一个C语言兼容的接口void work()&#xff0c;并在对应的interface.cpp中使用OpenCV完成相关处理实现。在PC端测试时&#xff0c;main.cpp成…

【HTML/CSS/JavaScript-编程指南】

HTML/CSS/JavaScript-编程指南 ■ HTML/CSS/JavaScript简介■ HTML/CSS/JavaScript学习网站■ VScode■ VSCode编写HTML■ VSCode编写CSS■ VSCode编写JavaScript ■ 语法■ HTML语法■ CSS语法■ JavaScript 语法 ■ HTML/CSS/JavaScript简介 HTML&#xff08;全称 Hypertext…

小波变换模拟

小波变换是一种信号处理技术&#xff0c;通过在时间-频率域中使用基于小波的函数进行信号分析。小波变换在处理非平稳信号和图像时特别有用&#xff0c;可以将信号分解为不同频率的成分。它在数据压缩、去噪、特征提取等领域有广泛应用。 MATLAB中提供了用于二维离散小波变换的…

css4浮动+清除浮动

浮动 一.常见网页布局1.三种布局方式2.布局准则 二.浮动&#xff08;float&#xff09;1.好处2.概念3.三大特性4.使用5.常见网页布局模板6.注意点 三.清除浮动1.why2.本质3.语法4.四种way&#xff08;后三个都是给父级添加&#xff09;清除浮动总结 一.常见网页布局 1.三种布局…

终端启动jupyter notebook更换端口

一、问题描述 如果尝试在端口 8889 上启动 Jupyter Notebook 但最终启动在了 8890 端口&#xff0c;这通常意味着 8889 端口已经被占用。要解决这个问题&#xff0c;可以尝试以下几种方法来关闭占用 8889 端口的进程。 1. 查找并终止占用端口的进程 首先&#xff0c;需要找出…

课时45:表达式_表达式_字符串表达式

3.2.3 字符串表达式 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习。 基础知识 简介 所谓的字符串表达式&#xff0c;主要是判断 比较运算符 两侧的值的内容是否一致&#xff0c;由于bash属于弱类型语言&#xff0c;所以&#xff0c;默认情况…

【课程作业】提取图中苹果的面积、周长和最小外接矩形的python、matlab和c++代码

提取图中苹果的面积、周长和最小外接矩形 在图像处理中&#xff0c;提取对象的关键属性是常见的任务之一。本文将演示如何使用三种流行的编程语言——Python、Matlab和C&#xff0c;利用相应的图像处理库&#xff08;OpenCV或Matlab内置函数&#xff09;来提取图像中苹果的面积…

Java8 Stream API 详解:流式编程进行数据处理

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

切比雪夫(最小区域法)平面拟合算法

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 本期话题&#xff1a;切比雪夫&#xff08;最小区域法&#xff09;平面拟合算法 相关背景和理论 点击前往 主要介绍了应用背景和如何转化成线性规划问题 平拟合输入和…

Vue2:多级路由案例

一、情景说明 上一节&#xff0c;我们学习了Vue中的路由功能 但是&#xff0c;只是基础的一级路由 在实际生产中&#xff0c;路径不可能只有一级&#xff0c;一般都有3,4层级 二、案例 1、修改路由器文件 index.js 新增两个组件 这里实现二级路由配置 关键配置&#xff1a;…

命题逻辑|析取、合取和蕴含到底什么意思

如是我闻&#xff1a;在逻辑学中&#xff0c;“析取”、“合取”和“蕴含”这些术语的中文翻译是有其逻辑和哲学基础的&#xff0c;它们准确地反映了这些逻辑操作的本质。虽然他们被翻译的很高级&#xff0c;但并不能让人一下子就明白。 析取 (Disjunction) 原理&#xff1a;…

【C++精简版回顾】8.const

1.const数据成员 &#xff08;1&#xff09;const数据成员必须使用初始化参数列表 &#xff08;2&#xff09;不能修改 &#xff08;3&#xff09;不能修改必须初始化 class MM { public:MM() {}MM(int age, string name) :age(age), name(name) {}~MM() {cout << "…

SpringBoot和ApiFox整合快速上手

前置&#xff1a;IDEA版本IntelliJ IDEA 2023.2.4&#xff0c;Apifox 2.5.6 安装插件&#xff1a;Apifox Helper1.2.1 目录 1.文档生成 2.提取登录接口token 1.文档生成 把密钥配置到 导入成功:文档就会出现 2.提取登录接口token 之后我们再使用的时候&#xff0c;只需要配置…

面试整理(昆明)去面试就更新

1.MyBatis与MyBatis-Plus的区别&#xff1f; MyBatis和MyBatis-Plus都是Java语言中非常常用的ORM框架&#xff0c;二者有以下区别&#xff1a; 1.实现方式不同 MyBatis是基于XML或注解方式进行数据库操作的持久化框架&#xff0c;它提供了简单的CRUD操作及动态SQL生成等功能。…

五个使用Delphi语言进行开发的案例

案例一&#xff1a;学生信息管理系统 某学校需要开发一个学生信息管理系统&#xff0c;用于记录学生的基本信息、成绩和考勤情况等。开发者使用Delphi语言进行开发&#xff0c;设计了一个包含多个窗体的应用程序。主窗体用于展示学生的列表和基本信息&#xff0c;其他窗体则用…

2024.2.25 -ElasticSearch 进阶

倒排索引 Elasticsearch的倒排索引机制是通过将文档中出现的词汇与它们所在的文档ID关联起来&#xff0c;实现快速查找包含特定词汇的文档。下面是一个具体的例子来说明倒排索引的工作原理&#xff1a; 假设我们有一个简单的文章集合&#xff0c;包含以下三篇文章&#xff1a…

Java学习——泛型

Java泛型是Java语言中的一个特性&#xff0c;它允许你在类、接口和方法上定义类型参数。使用泛型可以使代码更加通用&#xff0c;减少代码重复&#xff0c;并在编译时提供更强的类型检查。下面分别介绍泛型类、泛型方法和泛型接口。 泛型类 泛型类是在类名后添加类型参数声明…

ap和ac的工作原理

让我们一步步解释无线网络中访问点 (AP) 和无线控制器 (AC) 的工作原理&#xff1a; 1. 访问点 (AP)&#xff1a; 访问点是无线局域网络 (WLAN) 中的关键组件之一&#xff0c;它充当无线设备&#xff08;如笔记本电脑、智能手机等&#xff09;和有线网络之间的桥梁。其工作原理…

Oracle开发和应用——PL/SQL语法2(游标及集合)

6.4.6. 游标 这里的游标(cursor),是指数据库开发中的游标,而且,这里所指的是显式定义的游标。因为,除了显式定义的游标,我们每条SQL语句也会隐式的定义、打开和关闭一个游标,其实质是一个带有指针的结果集。当我们按照顺序取出结果时,这个指针会按照从前到后的顺序移…

990-09产品经理:How project management benefits different teams 项目管理如何使不同的团队受益

Project management methods and tools can be deployed across all teams and industries to help improve efficiency and drive results. In this chapter, we’ll provide an overview of how PM benefits construction, IT, marketing, and operations teams. 项目管理方法…