☰
HPC性能可移植性实战:从CUDA到SYCL,七种框架在GPU科学计算中的性能对决
2026/10/19 22:15:25 网站建设 项目流程

1. 项目概述:当科学计算遇上硬件“方言”困境

如果你在HPC(高性能计算)领域摸爬滚打过几年,大概率会和我有同样的感受:每次拿到一台新机器,尤其是配备了新型号GPU的集群,心情总是喜忧参半。喜的是算力又上了一个台阶,忧的是手头那些为上一代硬件精心调优的代码,很可能又要经历一轮痛苦的移植和重写。从CUDA到HIP,从特定架构的汇编优化到应对不同内存层次,我们仿佛成了硬件“方言”的翻译官,大量精力耗费在让同一套算法说不同的“机器语言”上。

这就是“性能可移植性”问题最真实的写照。它不是一个纸上谈兵的理论概念,而是直接关系到我们科研进度和工程成本的现实瓶颈。简单来说,它衡量的是一个应用程序在一组不同的硬件平台(比如NVIDIA A100、H100、AMD MI250X)上,不修改源代码或仅做最小改动,就能获得接近该硬件峰值性能的能力。理想很丰满,但现实是,为了压榨出最后一点性能,我们往往不得不使用CUDA、HIP这类与硬件深度绑定的“方言”进行编程,结果就是代码被锁死在一家厂商的生态里。

最近,我们团队在将欧洲空间局盖亚任务(Gaia)中的一个核心求解器——AVU-GSR——移植到多种GPU平台时,系统性地踩了一遍这个坑。这个求解器负责处理银河系约10亿颗恒星的天体测量参数,核心是求解一个超大规模稀疏线性方程组,算法基于经典的LSQR迭代法。它本质上是一个由六个稀疏矩阵-向量乘(SpMV)内核构成的内存受限型应用,对内存带宽极其敏感。我们最初只有一个高度优化的CUDA版本,但在项目需要扩展到包含AMD GPU的异构集群时,移植成本成了不可承受之重。

于是,我们决定以这个真实、复杂的科学计算应用为试验场,进行一次彻底的性能可移植性“大比武”。我们将其移植到了七种主流的并行编程框架中:包括低层、需要手动管理内存和内核的CUDA、HIP、SYCL;通过编译制导语句实现并行的OpenMP和OpenACC;以及通过高级抽象库实现硬件无关的C++ PSTL和KOKKOS。目标很明确:第一,量化评估这些框架在多种GPU(NVIDIA T4, V100, A100, H100及AMD MI250X)上的实际性能表现和可移植性得分;第二,通过屋顶线模型深入剖析性能瓶颈,弄清楚在什么情况下该选择什么框架;第三,为社区贡献一组基于真实应用的、可复现的基准测试案例和优化经验。

2. 性能可移植性的核心挑战与评估体系

在深入我们的案例之前,有必要先拆解清楚“性能可移植性”到底在衡量什么,以及为什么它如此棘手。很多人会把“可移植性”和“性能可移植性”混淆。前者只关心代码能不能正确跑起来,后者则苛刻得多,它要求代码不仅要能跑,还要跑得快,在不同硬件上都能发挥出相当比例的峰值性能。

2.1 为什么性能可移植性难以实现?

其根本原因在于硬件架构的异构性和复杂性日益加深。以我们测试的几款GPU为例:

  • 内存层次与带宽:NVIDIA的HBM(高带宽内存)与AMD的Infinity Cache设计不同,访存模式优化的策略自然有异。
  • 计算核心与指令集:CUDA Core与AMD CDNA架构的Stream Processor对线程调度、指令吞吐的处理方式存在差异。
  • 原子操作与缓存一致性:不同GPU对全局内存原子操作的支持粒度(如fine-grained vs. coarse-grained)和性能影响天差地别,这直接影响了我们a2Att和a2Instr内核的设计。
  • 编译器和运行时:即使使用像SYCL这样的开放标准,不同编译器后端(如AdaptiveCpp的CUDA/HIP后端与Intel的DPC++)生成的代码质量和优化策略也可能大相径庭。

这些底层差异意味着,一段为NVIDIA GPU高度优化的CUDA代码,如果简单地通过HIPIFY工具转成HIP跑在AMD GPU上,性能往往不尽如人意。你需要根据目标架构的“脾气”,重新调整线程块大小、共享内存使用、访存合并等参数。

2.2 量化评估:从应用到架构的双重视角

为了客观比较,我们采用了Pennycook等人提出的性能可移植性度量公式。这个公式的精妙之处在于它使用调和平均数来惩罚那些在不同平台上性能表现差异巨大的实现。公式如下:

对于一个给定的应用a、问题p和硬件平台集合H,其性能可移植性PP定义为:PP(a, p, H) = |H| / (Σ_{i∈H} (1 / e_i(a, p)))其中,e_i(a, p)是应用在平台i上的效率。

关键在于这个“效率”e如何定义。在我们的研究中,我们计算了两种效率,从而得到两个维度的PP分数:

  1. 应用效率:这是最直观的衡量。我们在同一个硬件平台上,运行所有框架版本的代码,取其中运行时间最短的作为基准(效率为1.0),其他框架版本的效率就是其运行时间与这个最短时间的比值。PP_App就是这些效率值跨平台的调和平均数。它回答的是:“在给定平台上,这个框架的实现离最佳实现有多远?”

  2. 架构效率:这个视角更底层,也更具挑战性。它衡量的是代码对硬件理论峰值性能的利用程度。我们通过屋顶线模型来计算每个计算内核的架构效率。具体步骤是:

    • 确定理论峰值:根据GPU的内存带宽(BW_max)和计算核心的FP64峰值算力(P_peak_fp64),在“算术强度-性能”图上画出屋顶线。
    • 计算算术强度:精确统计每个内核执行的浮点操作次数(FLOP)和字节访问量,算出其算术强度(AI)。
    • 定位与比较:将该内核的AI和实测性能(FLOP/实际时间)绘制在屋顶线图中。由于我们的SpMV内核AI很低,它们都位于内存带宽限制的区域。架构效率就是实测性能与屋顶线在该AI点上所允许的理论最大性能的比值。
    • 加权平均:最后,根据每个内核在总迭代时间中的占比进行加权,得到整个应用的平均架构效率,再代入PP公式计算PP_Arch。它回答的是:“这个实现是否充分榨干了这块硬件的潜力?”

提示:PP_Arch通常比PP_App低得多,这很正常。因为PP_App是“矮子里的将军”,而PP_Arch是“与理论峰值的距离”。一个PP_Arch高的实现,意味着它在不同硬件上都能稳定地逼近该硬件的物理极限,这是性能可移植性的终极体现。

3. Gaia AVU-GSR求解器:一个典型的内存受限型HPC案例

我们的“测试样本”AVU-GSR求解器,是盖亚数据处理流水线中的核心计算模块。它的科学目标是以前所未有的10-100微角秒的精度,确定银河系中近10亿颗主要恒星的位置、自行和视差,同时解算卫星的姿态和仪器参数。从计算角度看,这归结为求解一个规模极其庞大、系数矩阵高度稀疏的线性最小二乘问题。

3.1 问题规模与数据结构挑战

这个线性系统的系数矩阵A的维度大约是10^11 x 10^8,如果以双精度格式完整存储,需要约80EB(艾字节)的内存,这显然是不现实的。幸运的是,矩阵中超过99%的元素是零。因此,我们的核心优化策略是只存储非零元素,将矩阵压缩为稠密矩阵Ar,大小降至约19TB。即便如此,单节点仍无法处理,我们使用MPI在节点间进行数据划分。

矩阵A在结构上可垂直分为三个部分,这直接影响了我们的并行化策略和性能:

  1. 天体测量部分:占未知数的90%。其非零元素呈规则的块对角���构,访存模式非常规整,是性能优化的“理想国”。
  2. 姿态部分:非零元素以三个四元素块的形式出现,块间有固定步长,具有一定的规律性。
  3. 仪器部分:非零元素的分布完全无规律,是典型的随机稀疏访存,对缓存最不友好。

为了高效存储和访问,我们为每个部分设计了不同的索引方案。对于规则的天体测量和姿态部分,我们只存储每行第一个非零元素的列索引,通过计算推导出其他位置。对于不规则的仪器部分,则必须存储每行所有非零元素的列索引数组。这种差异化的数据结构,是后续内核性能表现迥异的根源。

3.2 计算核心:LSQR算法与六个SpMV内核

求解器95%以上的时间都花在LSQR迭代算法上,而该算法的核心是两组稀疏矩阵-向量乘:

  • aprod_1: 计算b += A * x,用于迭代更新已知向量。
  • aprod_2: 计算x += A^T * b,用于迭代更新解向量。

由于矩阵A被分为三个部分,因此每组操作对应三个内核,总共六个内核:a1Astro,a1Att,a1Instr,a2Astro,a2Att,a2Instr。其中,a1*内核是标准的稀疏矩阵行与向量相乘,易于并行化。而a2*内核由于涉及矩阵转置乘,在姿态和仪器部分会导致输出向量的不同位置被多个线程同时写入,从而产生数据竞争,必须引入原子操作,这成为了主要的性能瓶颈。

实操心得:面对复杂稀疏矩阵,不要试图用一种数据结构通吃所有部分。根据非零元素的分布模式(规则块对角、固定模式、完全随机),设计混合存储格式(如CSR + 自定义索引),并在内核中分别处理,虽然增加了代码复杂度,但往往是获得高性能的唯一途径。

4. 七种编程框架的移植与优化实战

我们将原始CUDA代码移植到其他六种框架,并非简单的“翻译”,而是根据各框架的特性和硬件情况进行了针对性的优化。下表概括了各框架的关键特性和我们的实现要点:

框架类别框架名称核心特点编译器/后端关键实现决策与优化点
特定语言/低层CUDANVIDIA原生,控制粒度最细,性能潜力最大。nvcc使用cudaHostMalloc分配固定内存,利用CUDA流实现内核执行与数据传输重叠。矩阵数据在迭代前一次性传入GPU,避免反复拷贝。
HIPAMD原生,语法与CUDA高度相似。hipcc(AMD)通过HIPIFY工具转换后手动优化。关键点:使用hipMemAdvise设置粗粒度一致性,避免原子操作导致的细粒度一致性性能下降。
SYCL开放标准,支持多种后端(CUDA, HIP, OpenCL)。AdaptiveCpp, Intel DPC++使用malloc_device直接在GPU分配内存,parallel_for与nd_range精细控制线程层次。测试发现atomic_ref的scope参数对DPC++性能影响显著。
编译制导OpenMP指令集,便于增量式并行化。LLVM (clang), 厂商 (nvc++,amdclang++)使用target指令族进行GPU卸载。通过num_teams和thread_limit调节执行配置。厂商编译器通常生成更优代码。
OpenACC类似OpenMP的指令集,更早专注于加速器。nvc++(NVIDIA)使用parallel loop gang vector指令。通过num_gangs和vector_length调优。在NVIDIA GPU上表现优异,但对AMD支持有限。
C++抽象库C++ PSTLC++17标准库的一部分,无需额外依赖。AdaptiveCpp, 厂商编译器完全依赖编译器自动并行化,无法手动调优线程块大小。在AMD GPU上配合hipMemAdvise使用。
KOKKOS抽象层,提供“一次编写,多处运行”的能力。KOKKOS自带工具链使用Kokkos::View管理数据,deep_copy进行数据传输,parallel_for表达并行。代码最简洁,但控制力最弱。

4.1 内核级优化:以a2Astro为例的差异化策略

性能可移植性的挑战,在优化a2Astro这个内核时体现得淋漓尽致。这个内核计算A_astro^T * b。由于天体测量部分的块对角结构,我们可以避免使用原子操作,但如何并行化以最大化内存带宽利用,不同框架给出了不同的答案。

对于低层框架(CUDA/HIP/SYCL):我们采用了共享内存优化策略。思路是将一个恒星的所有观测数据(对应矩阵中的一组连续行)加载到一个线程块的共享内存中,让块内的线程协作完成这个恒星对应的所有向量点积运算。这样做有两个巨大好处:第一,对全局内存的访问是合并的(coalesced),一次性读取连续地址,极大提高了带宽利用率;第二,减少了线程块的数量,降低了调度开销。这是手动内存管理带来的性能红利。

对于高层框架(OpenMP/OpenACC/PSTL/KOKKOS):我们无法显式控制共享内存。因此,策略退化为每个线程处理一个恒星。虽然也避免了原子操作,但每个线程需要独立读取多行不连续的数据,导致访存无法合并,缓存利用率低。这是为了可移植性付出的性能代价。

实测结果差距显著:在NVIDIA A100上,优化后的CUDA版本a2Astro内核的屋顶线效率达到0.82,而OpenMP版本只有约0.45。这清晰地表明,当算法存在重要的优化机会(如使用共享内存改善数据局部性)时,低层框架能将其转化为实实在在的性能优势,而高层框架则受限于抽象,无法触及这些优化点。

4.2 原子操作的性能陷阱与应对

在a2Att和a2Instr内核中,原子操作是无法避免的噩梦。我们的观察是:

  • 性能影响巨大:即使经过调优,这些内核的屋顶线效率也仅在0.16左右,远低于其他内核。
  • 框架实现差异:SYCL的atomic_ref允许指定内存序和范围。我们发现,在使用Intel DPC++编译器时,将scope设置为work_group(工作组内同步)在部分NVIDIA GPU上会导致相比device(全局同步)高达30%的性能下降,而在AMD GPU上下降约20%。AdaptiveCpp编译器则对不同设置不敏感。这提示我们,即使是标准语法,不同编译器的实现也可能有“坑”。
  • 硬件差异:不同GPU架构对原子操作(尤其是双精度浮点原子加)的硬件支持程度不同,这也是导致跨平台性能波动的一个因素。

避坑指南:如果你的内核中必须使用原子操作,第一,尽量减少原子操作的频率,尝试通过算法重构(如使用归约后再原子加)来合并更新;第二,在不同框架和编译器上,务必对原子操作的参数(如内存序)进行性能测试;第三,接受其性能代价,在架构设计上考虑将含原子操作的内核与其他计算重叠,以隐藏延迟。

5. 跨平台性能评估与屋顶线深度分析

我们在五款GPU(NVIDIA T4, V100, A100, H100 和 AMD MI250X)上,针对10GB、30GB、60GB三种问题规模,运行了所有七个框架的实现。以下是核心发现的数据总结与解读。

5.1 性能可移植性得分全景

下表汇总了在“所有平台”(NVIDIA+AMD)和“仅NVIDIA平台”两种场景下,各框架的PP_App和PP_Arch得分(以60GB问题规模为例):

框架类别框架PP_App (所有平台)PP_Arch (所有平台)PP_App (仅NVIDIA)PP_Arch (仅NVIDIA)关键解读
低层框架CUDA0.0% (不兼容AMD)0.0% (不兼容AMD)~100%~41.6%NVIDIA生态的王者,性能基准,但可移植性为零。
HIP~99.0%~22.4%~98.8%~41.6%在NVIDIA和AMD上均接近CUDA性能,是兼顾性能与跨厂商可移植性的优秀选择。
SYCL (A)~96.9%~23.9%~94.8%~40.0%真正的“一次编写,处处运行”,通过后端适配支持最广,性能损失很小。
编译制导OpenACC~58.2% (AMD差)~12.1% (AMD差)~95.5%~35.0%在NVIDIA上表现强劲,语法简单,但AMD生态支持是短板。
OpenMP (V)~85.7%~18.5%~92.3%~32.1%平衡性好,两大厂商编译器支持均较成熟,是增量式移植的稳妥选择。
抽象库KOKKOS~88.9%~17.8%~90.1%~28.5%代码最简洁,可维护性高,但性能开销明显,尤其在小规模问题上。
C++ PSTL (A)~89.5%~18.0%~88.7%~27.8%作为C++标准,前途光明,但当前编译器优化和功能成熟度有待提升。

核心结论一:性能与可移植性的权衡清晰可见。

  • 低层框架(CUDA/HIP/SYCL)提供了最高的绝对性能和PP_Arch,这意味着它们能最有效地榨取硬件潜力。HIP和SYCL在保持高性能的同时,获得了优秀的跨平台PP_App。
  • 编译制导框架(OpenMP/OpenACC)取得了良好的平衡。它们牺牲了一点峰值性能(更低的PP_Arch),但换来了更高的开发效率和相当不错的跨平台PP_App。OpenMP因其更广泛的编译器支持,在可移植性上略胜OpenACC一筹。
  • 抽象库框架(KOKKOS/PSTL)的PP_Arch得分最低,表明其自动生成的代码在利用硬件特性方面效率不足。然而,它们的PP_App并不低,说明在“跨平台表现一致性”上做得不错,只是这个一致性的水平是“中等性能”。

核心结论二:问题规模越大,性能可移植性通常越好。随着问题规模从10GB增加到60GB,几乎所有框架的PP_Arch在NVIDIA平台上都呈现上升趋势。这是因为更大的数据量有助于更好地掩盖内存延迟,提高GPU的占用率(Occupancy),使得硬件利用率更充分。这对于内存受限型应用是一个积极信号:在可用的内存范围内,尽量增大单次计算的问题规模,有助于提升跨平台的性能稳定性。

5.2 屋顶线模型揭示的微观差异

屋顶线分析让我们能像“解剖”一样看清每个内核在每个框架下的效率瓶颈。下图示意了在AMD MI250X上,10GB问题规模时,六个内核的屋顶线分布(所有框架的结果混合展示):

[此处为文字描述屋顶线图] Y轴(对数): 实测性能 (GFLOP/s) X轴(对数): 算术强度 (FLOP/Byte) 图中有一条从原点出发的斜线(内存带宽屋顶)和一条水平线(计算峰值屋顶)。 所有内核的数据点都密集分布在斜线附近,证实了应用是严格内存受限的。 a1Astro内核的点最靠近斜线,效率最高;a2Att和a2Instr的点离斜线最远,效率最低。 不同框架的同名内核点沿斜线上下分布,显示了不同实现带来的性能差异。

从屋顶线中我们读出了什么?

  1. 内存访问模式决定上限:a1Astro(规则访存)在所有框架下的效率都最高(平均~0.77),且不同框架间差异很小(<5%)。这说明当内存访问连续、规整时,即使是高层抽象框架,编译器也能生成不错的代码,性能可移植性很高。
  2. 调优对不规则访存至关重要:a1Att和a1Instr(不规则访存)的表现对线程块大小等参数极度敏感。低层和编译制导框架因为允许手动调优(我们设为16线程/块),其效率(~0.43)显著高于使用默认配置(128或256线程/块)的C++抽象库框架。后者因为线程块过大,导致L1缓存命中率下降,性能受损。
  3. 原子操作是性能“杀手”:a2Att和a2Instr内核由于必须使用原子操作,其屋顶线效率暴跌至~0.16左右。无论采用何种框架,原子操作带来的序列化和全局内存同步开销都是难以避免的沉重负担。这是算法特性带来的固有瓶颈,框架选择的影响相对变小。
  4. 编译器的影响不容忽视:以OpenMP为例,使用厂商编译器(nvc++,amdclang++)通常比使用LLVM编译器(clang)获得更高的性能。LLVM编译器有时会限制线程块数量或使用更多寄存器,增加寄存器压力,从而影响性能。

6. 框架选型指南与工程实践建议

经过这一轮全面的评估,我们可以为面临类似HPC应用移植挑战的开发者提供一些更具操作性的建议。选择框架没有银弹,关键看你的优先级。

6.1 根据项目目标选择框架

  • 场景一:追求极限性能,且目标硬件明确(如仅NVIDIA)首选:CUDA。它提供了最底层的控制、最丰富的性能分析工具和最成熟的生态。你可以精细调控每一个内核,实现极致的优化。PP_App接近100%就是证明。代价是代码被锁定在NVIDIA生态中。

  • 场景二:需要在NVIDIA和AMD GPU上获得接近原生的高性能首选:HIP 或 SYCL(AdaptiveCpp后端)。

    • HIP:如果你的代码库已经是CUDA,HIP是最平滑的迁移路径。语法几乎一致,移植成本低,且在两家硬件上都能获得顶尖性能(PP_Arch>40%)。你需要为AMD平台做一些针对性调优(如一致性模式)。
    • SYCL:如果你从零开始,或者需要支持更广泛的硬件(包括Intel GPU),SYCL是更面向未来的选择。它基于现代C++,是开放标准。虽然学习曲线稍陡,但“一次编写,多处编译”的潜力最大。性能上稍逊于HIP/CUDA,但差距很小。
  • 场景三:平衡开发效率与性能,进行增量式GPU移植首选:OpenMPtarget指令。你可以在现有的C/C++/Fortran代码中,通过添加#pragma omp target等指令,逐步将热点循环卸载到GPU。它得到了LLVM和各大厂商编译器的良好支持,性能表现稳健(PP_Arch~30%),代码改动相对较小,是大型遗留代码库GPU化的实用选择。

  • 场景四:快速原型开发,或需要支持极其多样的硬件后端首选:KOKKOS。当你需要代码在CPU、GPU乃至其他加速器上都能运行时,KOKKOS的抽象价值就凸显出来了。它用一套代码描述了并行模式,由后端库负责映射到具体硬件。虽然绝对性能有损失(PP_Arch较低),但大大降低了维护多份代码的成本。适合算法探索阶段或硬件环境经常变动的项目。

  • 场景五:探索C++标准并行化的未来可以尝试:C++ PSTL。它最大的优势是“零依赖”,仅需C++17标准库。对于简单的、规整的并行模式(如a1Astro),它已经能产生不错的代码。但目前编译器优化还不够成熟,对复杂内核(如需要共享内存、原子操作)的支持和性能都不理想。将其用于生产环境仍需时日,但值得保持关注。

6.2 性能可移植性优化清单

无论选择哪种框架,以下几点优化原则是通用的:

  1. 最大化数据局部性,规整访存模式:这是提升所有框架下性能的基石。像我们案例中那样,根据数据特点设计混合存储格式,将规则部分和不规则部分分开处理。
  2. 谨慎使用原子操作,并评估其代价:原子操作是性能的大敌。如果无法避免,务必在不同框架和目标硬件上对其性能影响进行量化评估。考虑用线程块内的共享内存归约来减少原子操作次数。
  3. 针对不同框架进行参数调优:线程块大小、循环展开因子等参数没有普适最优值。对于低层和编译制导框架,必须在每个目标架构上进行调优。对于抽象库,了解其默认策略���尝试通过策略对象进行调整。
  4. 利用框架特有的性能特性:如果选择了低层框架,不要浪费其能力。积极使用共享内存来合并访存、利用常量内存、尝试流水线操作等。在HIP中,注意hipMemAdvise的合理使用。
  5. 用屋顶线模型指导优化:花时间建立你关键内核的屋顶线模型。它能清晰告诉你,你的内核是受限于内存带宽还是计算能力。对于像SpMV这样的内存受限型内核,任何优化都应围绕减少字节访问、提高缓存命中率展开。
  6. 大规模测试验证:性能可移植性不是跑通一两个平台就能下结论的。需要在不同问题规模、不同硬件配置上进行充分测试。我们研究中从10GB到60GB的性能变化就说明了问题规模的影响。

7. 总结与展望

这次以Gaia AVU-GSR求解器为对象的深度研究,像一次对现代HPC编程生态的“压力测试”。我们得到的最核心结论是:性能可移植性不是一个可以完全自动实现的“魔法”,而是在编程抽象、开发成本和运行性能之间进行谨慎权衡的结果。

低层框架(CUDA/HIP/SYCL)给了我们驾驭硬件的缰绳,能驶向性能的巅峰,但要求我们是经验丰富的“骑手”。高层抽象框架(KOKKOS/PSTL)提供了更平稳的座驾,降低了驾驶门槛,保证了路途的平稳(可移植性),但极限速度有所妥协。编译制导框架(OpenMP/OpenACC)则是一条不错的中间道路。

对于像我们这样需要长期维护、并在未来多种Exascale级超算上运行的科学计算应用,没有单一的最佳答案。我们的策略正在转向“分层设计”:对于计算密集、模式规整的核心内核,继续用HIP或SYCL进行深度优化,以保障跨平台的性能底线;对于外围逻辑或性能不敏感的部分,采用OpenMP或KOKKOS来提高开发效率和代码可维护性。

未来,随着SYCL等开放标准的成熟,以及编译器技术的进步,我们有望在更高层次的抽象上获得更优的性能。但在此之前,理解硬件特性、剖析应用瓶颈、并明智地选择与混合使用编程模型,仍然是HPC开发者构建高效、可持续科学软件的核心技能。这项工作开源的代码和详尽的数据,希望能为同行们在选择自己的“性能可移植性”之路时,提供一份扎实的参考地图。毕竟,在通往Exascale计算的道路上,让软件跟上硬件创新的步伐,是我们共同面临的挑战。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询