HIP的应用可移植性

Application portability with HIP — ROCm Blogs (amd.com)

许多科学应用程序在配备AMD的计算平台和超级计算机上运行,包括Frontier,这是世界上第一台Exascale系统。这些来自不同科学领域的应用程序通过使用Heterogeneous-compute Interface for Portability(HIP)抽象层被移植到AMD GPU上运行。HIP使得这些高性能计算(HPC)设施能够将他们的CUDA代码转换为可在最新AMD GPU上运行并充分利用其优势的代码。将这些科学应用程序进行移植的工作量从几个小时到几周不等,这主要取决于原始源代码的复杂性。图1展示了几个已移植的应用示例以及相应的移植工作量。

在本文中,我们将介绍HIP可移植性层、AMD ROCm™堆栈中可用于自动将CUDA代码转换为HIP的工具,并展示如何使用可移植的HIP构建系统在AMD和NVIDIA GPU上运行相同的代码。

图1:通过HIP将科学应用程序移植到支持AMD Instinct™ GPU的平台上

HIP API

Heterogeneous-compute Interface for Portability(HIP)是一个C++运行时API和内核语言,它使开发人员能够设计可以在AMD和NVIDIA GPU上运行的平台无关GPU程序。HIP的接口和语法与CUDA非常相似,这方便了GPU程序员的使用,并实现了CUDA API调用的快速转换。通过简单地将cuda替换为hip,大部分这样的调用可以在原地进行转换,因为HIP支持CUDA运行时功能的强大子集。此外,如图2所示,HIP代码提供了一种方便性,即维护一个单一的代码库,该代码库可以在AMD和NVIDIA平台上运行。

图2:HIP到设备的流程图,展示了平台无关的抽象层。

图3展示了可用于在GPU上加速代码的各种编程范式和工具,以及系统堆栈中相应的抽象级别。HIP与其他GPU编程抽象层相比更接近硬件,因此它能够以最小的开销加速代码。这一特性使得HIP代码在NVIDIA加速器上的运行性能与原始CUDA代码相似。此外,AMD和NVIDIA都采用主机-设备架构,其中CPU是主机,GPU是设备。主机支持C++、C、Fortran和Python。C++是最常见且支持最好的语言,入口点是main()函数。主机运行HIP API和HIP函数调用,这些调用映射到CUDA或HIP。内核在设备上运行,支持类似C的语法。最后,HIP API提供了许多用于设备和内存管理以及错误处理的有用函数。HIP是开源的,您可以为其做出贡献。

图3: GPU编程抽象层级。

将CUDA应用程序转换为HIP

手动将大型且复杂的现有CUDA代码项目转换为HIP是一个容易出错且耗时的过程。鉴于HIP和CUDA之间的语法相似性,可以构建自动化转换工具来将CUDA代码转换为可移植的HIP C++。AMD ROCm™堆栈提供了转换工具和脚本,这些工具和脚本大大加快了转换过程。这些工具可以单独使用,也可以作为迭代过程的一部分来移植更大、更复杂的CUDA应用程序,从而减少了在基于AMD的系统上部署CUDA应用程序所需的手动工作量和时间。

使用什么工具?

将CUDA转换为HIP的最终工具或策略选择取决于多个因素,包括代码复杂性和开发者的设计选择。为了阐明这一点,我们鼓励开发者使用以下问卷作为模板来收集更多关于他们项目的信息:

代码结构有多复杂?

是否使用了面向对象编程?

是否依赖于模板类?

是否依赖其他库和包?

是否有特定于设备的代码?

设计考虑因素是什么?

我们是否应该为CUDA和HIP维护单独的后端?

代码库是否处于积极开发中?

更新的频率是多少?

开发工作是否专注于某个特定功能?

一旦完成了需求和目标的评估,开发者可以选择以下策略之一来将他们的CUDA代码转换为HIP。

统一的封装器/头文件

在不需要为CUDA和HIP维护单独代码库的场景中,且应用程序没有任何特定于设备的代码时,可以创建一个带有宏定义的头文件,为HIP API调用创建别名并将其链接到现有的CUDA API。以下是一个示例片段:

...
#define cudaFree hipFree
#define cudaFreeArray hipFreeArray
#define cudaFreeHost hipHostFree
#define cudaMalloc hipMalloc
#define cudaMallocArray hipMallocArray
#define cudaMallocHost hipHostMalloc
#define cudaMallocManaged hipMallocManaged
#define cudaMemcpy hipMemcpy
...

这个头文件,可以作为您移植项目的起点。

另外,您还可以使用宏定义来构建一个统一的封装,如以下代码片段所示。根据代码编译的架构,封装框架在底层调用相应的CUDA或HIP API。

...
#ifdef _CUDA_ENABLED
using deviceStream_t = cudaStream_t;
#elif _HIP_ENABLED
using deviceStream_t = hipStream_t;
#endif

...
#ifdef _CUDA_ENABLEDusing deviceStream_t = cudaStream_t;
#elif _HIP_ENABLEDusing deviceStream_t = hipStream_t;
#endif
...

优点

  • 易于维护
  • 高度可移植
  • 易于添加新功能

缺点

  • 链接所有CUDA API可能需要多次迭代
  • CUDA中现有的优化可能不适用于HIP
  • 在CUDA API没有HIP等效项的情况下需要手动干预

一般性建议:

• 通常,从NVIDIA GPU开始进行代码移植是最简单的方法,因为您可以逐步将代码部分转换为HIP,同时其余部分保留为CUDA代码。请记住,在NVIDIA GPU上,HIP只是CUDA的一个薄层,因此这两种代码类型可以在_nvcc_平台上互操作。此外,HIP移植可以与原始CUDA代码进行功能和性能比较。
• 一旦CUDA代码被移植到HIP并且可以在NVIDIA GPU上运行,就使用AMD GPU上的HIP编译器编译HIP代码。

Hipify 工具

AMD的ROCm™软件堆栈包含了一些实用工具,这些工具可以帮助将CUDA API转换为HIP API。你可以找到以下两个工具:

hipify-clang:一个在HIP/Clang编译器工具链中运行的预处理器,它在编译过程中作为初步步骤转换代码。

hipify-perl:一个基于Perl的脚本,它依赖正则表达式来进行转换。

Hipify工具可以扫描代码来识别任何不支持的CUDA函数。你可以在ROCm的HIPIFY文档网站上找到支持的CUDA API的列表。

hipify-clang

hipify-clang 是一个预处理器,它使用Clang编译器来解析CUDA代码并进行语义转换。它将CUDA源代码转换为抽象语法树,然后由转换匹配器遍历。应用所有匹配器后,产生输出的HIP源码。

优点:
- 基于Clang的转换器,因此即使是复杂的结构也能成功解析
- 支持Clang选项,如 -I、`-D`、`--cuda-path`等。
- 对新版本的CUDA有无缝支持,因为这是Clang的责任。

缺点:
- 输入的CUDA代码需要是正确的,错误的代码不会被转换为HIP。
- 必须安装CUDA,并且在存在多个安装的情况下需要通过`--cuda-path`选项提供。
- 必须提供所有的包含文件和定义才能成功地转换代码。

使用hipify-clang的一般方式是:

hipify-clang [选项] <source0> [... <sourceN>]

可以通过使用命令来识别可用的选项:

hipify-clang --help

考虑一个简单的
vectorAdd 示例,其中原始的CUDA代码取两个向量A和B,进行逐个元素的加法,并将值存储在一个新的向量C中:

C[i] = A[i] + B[i],                其中 i=0,1,.....,N-1

要将CUDA代码转换为HIP,可以按以下方式使用hipify-clang:

hipify-clang --cuda-path=/your-path/to/cuda -I /your-path/to/cuda/include -o /your-path/to/desired-output-dir/vectorAdd_hip.cpp vectorAdd.cu

翻译后的HIP代码 vectorAdd_hip.cpp 如下所示:

#include <hip/hip_runtime.h>    
#include <stdio.h>// 用于检查CUDA API调用错误的宏
#define cudaErrorCheck(call)                                                             \
do{                                                                                       \hipError_t cuErr = call;                                                             \if(hipSuccess != cuErr){                                                             \printf("CUDA Error - %s:%d: '%s'\n", __FILE__, __LINE__, hipGetErrorString(cuErr));\exit(0);                                                                            \}                                                                                     \
}while(0)// 数组的大小
#define N 1048576// 内核
__global__ void add_vectors_cuda(double *a, double *b, double *c)
{int id = blockDim.x * blockIdx.x + threadIdx.x;if(id < N) c[id] = a[id] + b[id];
}// 主程序
int main()
{// 为N个双精度浮点数分配的字节数size_t bytes = N*sizeof(double);// 在主机上为数组A、B和C分配内存double *A = (double*)malloc(bytes);double *B = (double*)malloc(bytes);double *C = (double*)malloc(bytes);// 在设备上为数组d_A、d_B和d_C分配内存double *d_A, *d_B, *d_C;cudaErrorCheck( hipMalloc(&d_A, bytes) );cudaErrorCheck( hipMalloc(&d_B, bytes) );cudaErrorCheck( hipMalloc(&d_C, bytes) );...

对翻译后的代码快速检查显示:
1. HIP运行时头文件已在顶部自动引入。
2. CUDA类型和API,如`cudaError_t`、`cudaMalloc`等,已被替换为HIP对应项。
3. 用户定义、函数名和变量名保持不变。
4. 从ROCm 5.3开始,默认的HIP内核启动语法与CUDA中的相同。以前的`hipLaunchKernelGGL`语法继续得到支持,可以通过指定`--hip-kernel-execution-syntax`选项给hipify-clang来使用。

Hipify-perl 

Hipify-perl 脚本通过使用一系列简单的字符串替换直接修改 CUDA 源代码。
优点:
- 易于使用
- 不会检查输入源 CUDA 代码的正确性
- 不依赖第三方工具,包括 CUDA

缺点:
- 当前无法转换以下构造:宏展开、命名空间、一些模板、主机/设备函数调用、复杂参数列表解析
- 可能需要多次迭代和手动干预
- 没有编译器支持检查是否存在包含文件和定义

hipify-perl 的一般用法如下:

hipify-perl [OPTIONS] INPUT_FILE

其中,可用选项可以通过以下命令识别:

hipify-perl --help

使用相同的 vectorAdd示例,我们可以使用 hipify-perl 将 CUDA 代码转换为 HIP,如下所示:

$ hipify-perl -o=your-path/to/desired-output-dir/vectorAdd_hip.cpp vectorAdd.cu warning: vectorAdd.cu:#4 : #define cudaErrorCheck(call)                                                              \warning: vectorAdd.cu:#36 :     cudaErrorCheck( hipMalloc(&d_A, bytes) );warning: vectorAdd.cu:#37 :     cudaErrorCheck( hipMalloc(&d_B, bytes) );warning: vectorAdd.cu:#38 :     cudaErrorCheck( hipMalloc(&d_C, bytes) );warning: vectorAdd.cu:#49 :     cudaErrorCheck( hipMemcpy(d_A, A, bytes, hipMemcpyHostToDevice) );warning: vectorAdd.cu:#50 :     cudaErrorCheck( hipMemcpy(d_B, B, bytes, hipMemcpyHostToDevice) );warning: vectorAdd.cu:#71 :     cudaErrorCheck( hipMemcpy(C, d_C, bytes, hipMemcpyDeviceToHost) );warning: vectorAdd.cu:#90 :     cudaErrorCheck( hipFree(d_A) );warning: vectorAdd.cu:#91 :     cudaErrorCheck( hipFree(d_B) );warning: vectorAdd.cu:#92 :     cudaErrorCheck( hipFree(d_C) );

与 hipify-clang 不同,运行 hipify-perl 脚本会生成许多警告。因为 hipify-perl 使用字符串匹配和替换来转换支持的 CUDA API 至对应的 HIP API,它无法找到用户定义的 cudaErrorCheck() 函数的替换项,并因该函数调用中的每一行打印出警告。快速检查指定输出目录中存储的转换后代码表明:

1. 由 hipify-perl 生成的转换代码与 hipify-clang 转换的代码相同。
2. 用户定义的函数、宏和变量没有改变。
3. 从 ROCm v5.3 起,默认的 HIP 核心启动语法与 CUDA 中的相同。以前的 hipLaunchKernelGGL 语法继续得到支持,可以通过指定 --hip-kernel-execution-syntax 选项给 hipify-perl 使用。

Hipify工具的一般使用建议:

• hipify-perl 较为简单易用,不依赖CUDA等第三方库。
• 使用 hipify-perl 翻译代码可能需要多次迭代。首先,使用 hipcc 构建代码。修正任何编译器错误或警告,然后再次编译。持续此循环直到得到可工作的HIP代码。
• 也可以使用其他预打包的实用程序来帮助收集有关CUDA到HIP代码翻译的信息。例如,您可以使用:
    ◦ hipexamine-perl.sh 工具扫描源目录,确定哪些文件包含CUDA代码,以及有多少代码能够自动转换为HIP。
    ◦ hipconvertinplace-perl.sh 脚本来执行指定目录中所有代码文件的就地转换。
• “就地”转换并不总是正确的选择,尤其是如果您希望同时保留CUDA和HIP代码。
• 对于这里考虑的简单例子,hipify-perl 和 hipify-clang 翻译出的代码的相似性可能成立。然而,考虑到 hipify-perl 已知的限制,使用时应谨慎。

可移植的HIP构建系统

HIP的最强大特性之一是,如果原始代码使用的是[支持HIP的CUDA API](Supported CUDA APIs — HIPIFY Documentation),那么经过HIP转换的代码能够同时在AMD和NVIDIA的GPU上运行。目前,许多旨在支持这两个平台的应用程序都分别维护着HIP和CUDA的双重代码仓库以及构建系统。使用ROCm™,我们可以拥有一个可移植的HIP构建系统,以避免为同一个项目维护两个独立的代码基础。

在一个可移植的HIP构建系统中,可以选择运行在`amd` 或 nvidia 平台上。通过设置`HIP_PLATFORM`环境变量,我们可以[选择hipcc目标的路径](Frequently asked questions — HIP 6.1.40092 Documentation)。如果`HIP_PLATFORM=amd`,那么`hipcc`将会调用clang编译器和[ROCclr运行时](Frequently asked questions — HIP 6.1.40092 Documentation)来为AMD GPU编译代码。如果`HIP_PLATFORM=nvidia`,则`hipcc`将调用`nvcc`,即[NVIDIA的CUDA编误器驱动](NVIDIA CUDA Compiler Driver),来为NVIDIA GPU编译代码。平台选择也决定了包含哪些头文件以及链接使用哪些库。

本节演示如何使用两种众所周知的构建系统——Make和CMake——实现可移植性。

可移植的Make构建系统

在下面的Makefile示例中,我们可以通过简单地将`HIP_PLATFORM`设置为所需的默认设备`amd`或`nvidia`来选择为AMD或NVIDIA GPU构建代码。设置`-x`标志为`cu`或`hip`将指示构建系统编译到所需的设备,不管文件扩展名是什么。但是,应该注意的是,最终这两种编译器都映射到LLVM。

EXECUTABLE = vectoraddall: $(EXECUTABLE) test
.PHONY: testSOURCE = vectorAdd_hip.cpp
CXXFLAGS = -g -O2 -fPIC
HIPCC_FLAGS = -O2 -g
HIP_PLATFORM ?= amd
HIP_PATH ?= $(shell hipconfig --path)ifeq ($(HIP_PLATFORM), nvidia)HIPCC_FLAGS += -x cu -I${HIP_PATH}/include/LDFLAGS = -lcudadevrt -lcudart_static -lrt
endififeq ($(HIP_PLATFORM), amd)HIPCC_FLAGS += -x hipLDFLAGS = -L${ROCM_PATH}/hip/lib -lamdhip64
endif$(EXECUTABLE):hipcc $(HIPCC_FLAGS) $(LDFLAGS) -o $(EXECUTABLE) $(SOURCE)
test:./$(EXECUTABLE)
clean:rm -f $(EXECUTABLE)

对于将在AMD平台上运行的代码,我们需要安装ROCm™,并设置`CXX`变量为推荐的`clang++, 例如 export CXX=${ROCM_PATH}/llvm/bin/clang++,然后使用make`构建,并运行与上一节中相同的vectorAdd应用程序。

make
./vectoradd

对于将在NVIDIA平台上运行的代码,我们需要安装CUDA和提供HIP可移植层的ROCm™, 并设置`HIP_PLATFORM=nvidia`来覆盖默认设置,从而为NVIDIA GPU编译代码。

HIP_PLATFORM=nvidia
make
./vectoradd

可移植的CMake构建系统

类似于之前的Make示例,目的是有一个构建系统,允许用户在两个GPU运行时HIP和CUDA之间切换。以下代码展示了如何在`CMakeLists.txt`中实现切换:

...
if (NOT CMAKE_GPU_RUNTIME)set(GPU_RUNTIME "HIP" CACHE STRING "Switches between HIP and CUDA")
else (NOT CMAKE_GPU_RUNTIME)set(GPU_RUNTIME "${CMAKE_GPU_RUNTIME}" CACHE STRING "Switches between HIP and CUDA")
endif (NOT CMAKE_GPU_RUNTIME)

接下来,要在AMD和NVIDIA系统上构建HIP代码,需要在CMake中启用HIP语言支持,并设置`hipcc`作为编译器,以及相应的编译设备标志:

enable_language(HIP)if (${GPU_RUNTIME} MATCHES "HIP")set (VECTORADD_CXX_FLAGS "-fPIC")
elseif (${GPU_RUNTIME} MATCHES "CUDA")set (VECTORADD_CXX_FLAGS "-I $ENV{ROCM_PATH}/include")
else ()message (FATAL_ERROR "GPU runtime not supported!")
endif ()set(CMAKE_CXX_COMPILER hipcc)set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${VECTORADD_CXX_FLAGS}")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${VECTORADD_CXX_FLAGS}")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${VECTORADD_CXX_FLAGS} -ggdb")

最后,创建一个可执行文件,并且必须在AMD和NVIDIA系统上用相应的运行时链接目标:

set (SOURCE vectorAdd_hip.cpp)add_executable(vectoradd ${SOURCE})
if (${GPU_RUNTIME} MATCHES "HIP")target_link_libraries (vectoradd "-L$ENV{ROCM_PATH}/lib" amdhip64)
elseif (${GPU_RUNTIME} MATCHES "CUDA")target_link_libraries (vectoradd cudadevrt cudart_static rt)   
endif ()

假设已经安装了ROCm™和CMake,可以使用`cmake`配置代码在AMD GPUs上运行,然后通过运行以下命令构建和启动可执行文件:

mkdir build && cd build
cmake ..
make
./vectoradd

对于要在NVIDIA GPU上运行的代码,除了CMake之外,还必须安装CUDA和ROCm™堆栈。与Make示例类似,必须设置`HIP_PLATFORM`,并使用`CUDA` GPU运行时配置代码:

mkdir build && cd build
export HIP_PLATFORM=nvidia
cmake -DCMAKE_GPU_RUNTIME=CUDA ..
make
./vectoradd

如果在NVIDIA系统上`HIP_PLATFORM`没有正确设置,CMake和Make仍然会配置和构建代码,然而,可能会观察到运行时错误,比如这样的:

./vectoradd
CUDA Error - vectorAdd_hip.cpp:38: 'invalid device ordinal'

在AMD和NVIDIA GPU上,CMake将自动检测和构建基础架构的GPU目标,然而,在需要为不同架构构建目标的情况下,用户可以显式指定`CMAKE_HIP_ARCHITECTURES`或`CMAKE_CUDA_ARCHITECTURES`。有关更多详细信息,请参阅CMake documentation。 

额外的移植注意事项

在移植过程中,重要的是检查内联PTX汇编代码、CUDA内建函数、硬编码的依赖关系、不受支持的函数、任何限制NVIDIA硬件寄存器文件大小的代码,以及hipify工具无法转换的任何其他构造。由于hipify工具不运行应用程序,因此必须手动更改任何硬编码的构造,例如将warp大小设置为32。因此,建议避免硬编码warp大小,而是依赖WarpSize设备定义、#define WARPSIZE size或props.warpSize从运行时获取正确的值。hipify工具也不转换构建脚本。设置适当的标志和路径以构建新转换的HIP代码必须手动完成。

更多将 CUDA 代码转换为 HIP 和相关便携式构建系统的代码示例可以在 HIP 培训系列仓库 中找到。

结论

我们展示了开发人员可以利用的各种 ROCm™ 工具来将他们的代码从 CUDA 转换为 HIP。这些工具大大加快并简化了转换过程。通过展示用 Make 和 CMake 都可移植的构建系统的示例,我们还展示了 HIP 的一个最强大的特性,即它能够在 AMD 和 NVIDIA GPU 上运行。

与许多其他 GPU 编程范式不同,HIP API 是一个接近硬件的薄层 API,使得 HIP 代码能够以与其 NVIDIA GPU 上的对应代码类似的性能运行。

下一次

请继续关注,因为我们将发布这个系列的后续文章,涵盖更高级的主题。如果您有任何问题或评论,可以通过 GitHub Discussions 与我们联系。

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

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

相关文章

Socket编程学习笔记之TCP与UDP

Socket&#xff1a; Socket是什么呢&#xff1f; 是一套用于不同主机间通讯的API&#xff0c;是应用层与TCP/IP协议族通信的中间软件抽象层。 是一组接口。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议族隐藏在Socket接口后面&#…

【Python报错】已解决ModuleNotFoundError: No module named ‘xxx‘ in Jupyter Notebook

解决Python报错&#xff1a;ModuleNotFoundError: No module named ‘xxx’ in Jupyter Notebook 在使用Jupyter Notebook进行数据分析或科学计算时&#xff0c;我们经常需要导入各种Python模块。如果你遇到了ModuleNotFoundError: No module named xxx的错误&#xff0c;这通常…

STM32F103C8T6基于HAL库移植uC/OS-III

文章目录 一、建立STM32CubeMX工程二、移植1、 uC/OS-III源码2、移植过程 三、配置相关代码1、bsp.c和bsp.h2、main.c3、修改启动代码4、修改app_cfg.h文件5、修改includes.h文件6、修改lib_cfg.h文件 四、编译与烧录总结参考资料 学习嵌入式实时操作系统&#xff08;RTOS&…

Django 里实现表格内容上传

先看效果图&#xff1a; 当没有添加数据&#xff0c;就按 提交 键就会出现报错 下面是操作步骤 1. 先在 views.py 文件里做添加 # 在 views.py class AssetModelForm(forms.ModelForm):#newField forms.CharField()class Meta:model models.AssetSet fields [name, pri…

基于zyyo主页与無名の主页合并二改,一款适合新手的个人主页

pengzi主页&#x1f64b; 项目地址 简洁的布局&#xff1a;主页应该有清晰的布局&#xff0c;包括一个简洁的导航菜单和易于浏览的内容区域。避免使用过多的花哨效果&#xff0c;保持页面简洁明了。 个人资料介绍&#xff1a;在主页上展示一段简短的个人介绍&#xff0c;包括…

电机专用32位MCU PY32MD310,Arm® Cortex-M0+内核

PY32MD310是一颗专为电机控制设计的MCU&#xff0c;非常适合用做三相/单相 BLDC/PMSM 的主控芯片。芯片采用了高性能的 32 位 ARM Cortex-M0 内核&#xff0c;QFN32封装。内置最大 64 Kbytes flash 和 8 Kbytes SRAM 存储器&#xff0c;最高48 MHz工作频率&#xff0c;多达 16 …

C++全栈聊天项目(21) 滚动聊天布局设计

滚动聊天布局设计 我们的聊天布局如下图 最外层的是一个chatview&#xff08;黑色&#xff09;&#xff0c; chatview内部在添加一个MainLayout&#xff08;蓝色&#xff09;&#xff0c;MainLayout内部添加一个scrollarea(红色)&#xff0c;scrollarea内部包含一个widget&…

西米支付:刷卡手续费进入高费率时代! 十多家支付机构公布最新收费标准

《非银行支付机构监督管理条例》自5月1日施行以来&#xff0c;越来越多支付机构落实收费透明化。 支付界注意到&#xff0c;日前&#xff0c;拉卡拉、银联商务两家持牌支付公司公布了新的收单业务收费标准。 拉卡拉在其官网公布了最新的“收费项目及收费标准公示”&#xff0…

GSS7000卫星导航模拟器结合RTKLIB 接收NTRIP网络RTCM数据以输出RS232

本文聚焦&#xff0c;使用GSS7000仿真GNSS NTRIP&#xff0c;利用开源工具RTKLIB 作为NTRIP Client 接受GSS7000仿真的RTCM数据&#xff0c; 并通过STRSVR将收到的RTCM数据通过USB-RS232数据线吐出&#xff0c;并转给DUT&#xff0c;让其获得RTK -FIXED 固定解。 废话不多说&a…

独享IP VS 原生IP,二者的区别与定义详解

原生IP&#xff1a;原生IP是指由Internet服务提供商&#xff08;ISP&#xff09;直接分配给用户的IP地址&#xff0c;这些IP地址通常反映了用户的实际地理位置和网络连接。原生IP是用户在其所在地区或国家使用的真实IP地址&#xff0c;与用户的物理位置直接相关。在跨境电商中&…

2024教资认定报名流程,点赞收藏!

2024年要进行教资认定的宝子们提早准备 &#x1f525;教资认定网上报名流程概览 一、进入教资认定网报入口 二、进行实名核验 三、申请网报时间查询 四、个人信息维护 五、认定申请报名 &#x1f525;教资认定所需材料 1⃣️身份证 2⃣️户口本&#xff0f;居住证&#xff0f;学…

基本 MOSFET 恒流源

恒流源在电路分析练习和网络定理中占有重要地位&#xff0c;然后它们似乎或多或少消失了。。。除非你是IC设计师。尽管在典型 PCB 设计中很少遇到&#xff0c;但电流源在模拟 IC 领域却无处不在。这是因为它们 1) 用于偏置&#xff0c;2) 作为有源负载。 偏置&#xff1a; 用作…

Docker搭建可道云

Docker搭建可道云&#xff08;存储&#xff09; 文章目录 Docker搭建可道云&#xff08;存储&#xff09;介绍资源列表基础环境一、安装Docker二、配置Docker加速器三、搭建可道云私有云盘3.1、编写Dockerfile3.2、上传资源到指定目录3.3、查看目录下所有资源 四、构建镜像五、…

【学术小白成长之路】01三方演化博弈(基于复制动态方程) -基础概念与模型构建

1.演化博弈基础知识 经典博弈论起源于1944年Von Neumann和Morgenstern合著的《博弈论与经济学行为》&#xff0c;是研究理性决策者之间竞争和合作关系的数学方法。 博弈论主要研究完全理性的博弈个体为实现利益最大化而做的策略选择&#xff0c;在过去几十年取得了极大发展&am…

计算机网络--物理层

计算机网络--计算机网络概念 计算机网络--物理层 计算机网络--数据链路层 计算机网络--网络层 计算机网络--传输层 计算机网络--应用层 1. 基本概念 物理层的概念&#xff1a;物理层解决如何在在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输…

Python 如何判断一组数呈上升还是下降趋势

在数据分析和统计处理中&#xff0c;我们经常需要判断一组数的趋势是上升还是下降。这在金融市场分析、销售数据监控以及科学研究中都十分常见。本文将介绍如何使用Python来判断一组数的趋势&#xff0c;并结合实际案例进行详细阐述。 一、基本方法 判断一组数的趋势主要有以…

【CS.DB】从零到精通:这可能是全网最全面最强大的SQL入门教程

文章目录 1. 什么是SQL&#xff1f;1.1 SQL的历史1.1.1 SQL的标准化过程 2. SQL基础语法2.1 数据库操作2.1.1 创建数据库2.1.2 删除数据库 2.2 表操作2.2.1 创建表2.2.2 删除表2.2.3 修改表 2.3 数据操作2.3.1 插入数据2.3.2 更新数据2.3.3 删除数据 2.4 查询数据2.4.1 基本查询…

读取文件

自学python如何成为大佬(目录):自学python如何成为大佬(目录)_利用python语言智能手机的默认语言实战一-CSDN博客 在Python中打开文件后&#xff0c;除了可以向其写入或追加内容&#xff0c;还可以读取文件中的内容。读取文件内容主要分为以下几种情况&#xff1a; 1 读取指…

react 基础样式的控制(行内和className)

import ./index.cssconst style{color:red,font-size:150px }function App() {return (<div className"App"><h1>行内样式控制</h1><h1 style{{color:red,font-size:150px}} >asd </h1><span style{style} >asd </span>&l…

2024年跨平台应用解决方法

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 很久没有写这类high-level的文章了,本身这类框架就一直层出不穷,但是其中历久弥坚,坚韧不拔的框架又有多少呢? 首先考虑到学习成本以及掌握一些编程语言在工作、学习生态上的价值,给这些东西适用生态划分一下. Reac…