AMDGPU 基于DRM SVM框架的新SVM功能实现 :attr_range 与 svm_range 的对应关系分析

news2026/3/25 19:05:24
AMD 正在使用 drm svm框架重构SVM的实现看来drm svm框架要进入大范围应用了。下面是在kernel社区上由AMD的开发人员提交的POC 验证版本的patches的技术方案实现。这里快速总结了实现以飨读者。因是POC版本设计可能会变动读者们慎重使用。本文仅用来跟踪前沿驱动技术的迭代发展现状。1. 结论amdgpu_svm_attr_range属性层与amdgpu_svm_range映射层不是一一对应的而是N:M 的解耦关系。两者有不同的拆分标准、不同的粒度、不同的生命周期通过amdgpu_svm_range_apply_attr_change()桥接。2. 两层架构概览┌─────────────────────────────────────────────────────────────────┐ │ 用户空间 ioctl │ │ amdgpu_svm_attr_set(start, size, attrs) │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 属性层 (Attribute Layer) │ │ │ │ amdgpu_svm_attr_range: 存储在 attr_tree-tree (interval tree) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ range A │ │ range B │ │ range C │ ← 按属性边界拆分 │ │ │flagsRW │ │flagsRO │ │flagsRW │ │ │ │accessEN │ │accessEN │ │accessNO │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ 职责记录用户设置的属性元数据纯数据无 GPU 映射状态 │ └────────────────────────────┬────────────────────────────────────┘ │ attr_change → trigger ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 映射层 (Mapping Layer) │ │ │ │ amdgpu_svm_range (wraps drm_gpusvm_range): │ │ 存储在 drm_gpusvm_notifier 内的 interval tree │ │ ┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐ │ │ │ r0 ││ r1 ││ r2 ││ r3 ││ r4 ││ r5 ││ r6 ││ r7 │ ← chunk对齐 │ │ └────┘└────┘└────┘└────┘└────┘└────┘└────┘└────┘ │ │ 职责持有实际 GPU 页表映射、DMA 状态、pages │ └─────────────────────────────────────────────────────────────────┘3. 两层对比维度amdgpu_svm_attr_range(属性层)amdgpu_svm_range/drm_gpusvm_range(映射层)存储位置attr_tree-tree(interval tree)drm_gpusvm_notifier内的 interval tree职责记录用户设置的属性元数据持有实际 GPU 页表映射、DMA 状态、pages拆分依据用户 ioctl 设置的属性边界chunk_size 对齐、VMA 边界、notifier 边界创建时机amdgpu_svm_attr_set_range()— ioctl 驱动drm_gpusvm_range_find_or_insert()— fault/map 驱动持有内容preferred_loc,flags,access,granularitygpu_mapped,pte_flags, pages, DMA mapping粒度任意大如用户对 1GB 区间设置属性 → 一个 attr_range较小受 chunk_sizes (4K/64K/2M)、VMA、notifier 约束生命周期由用户 ioctl 创建attr_clear_pages或进程退出时销毁由 fault/map 创建mmu_notifier invalidate 时可回收4. 拆分标准的差异4.1 attr_range 的拆分属性边界驱动amdgpu_svm_attr_set_existing()在用户对一个已有 attr_range 的子区间设置新属性时会 split 出 left/right 残留段初始状态: attr_range: [ A (flagsRW, accessENABLE) ] 0x1000 0x5000 用户 ioctl: 设置 [0x2000, 0x3000] flagsRO 拆分后: attr_range: [ A (RW) ][ B (RO) ][ A (RW) ] 0x1000 0x1FFF 0x2000 0x3000 0x3001 0x5000关键代码amdgpu_svm_attr_set_existing/* split head */if(startrange_start){leftattr_alloc_range(range_start,start-1,old_attrs);}/* split tail */if(lastrange_last){rightattr_alloc_range(last1,range_last,old_attrs);}4.2 svm_range 的拆分硬件约束驱动drm_gpusvm_range_chunk_size()根据以下约束确定每个 svm_range 的大小for(;igpusvm-num_chunks;i){startALIGN_DOWN(fault_addr,gpusvm-chunk_sizes[i]);endALIGN(fault_addr1,gpusvm-chunk_sizes[i]);if(startvas-vm_startendvas-vm_end// VMA 边界startdrm_gpusvm_notifier_start(notifier)// notifier 边界enddrm_gpusvm_notifier_end(notifier)startgpuva_startendgpuva_end)// GPUVA 边界break;}约束包括chunk_sizes 对齐预定义的 2 的幂数组如 2M, 64K, 4K从大到小尝试VMA 边界不能跨越 CPU VMAnotifier 边界不能跨越 mmu_interval_notifier已有 range 边界不能与已存在的 svm_range 重叠5. N:M 对应关系详解5.1 一个 attr_range 跨多个 svm_range1:N这是最常见的情况。用户通过 ioctl 设置大范围属性产生一个大的 attr_range而 GPU 映射时按 chunk_size 对齐创建多个小的 svm_rangeattr_range: [ 0x1000 - 0x10000 (accessenable) ] svm_ranges: [--64K--][--64K--][--64K--][--64K--][--64K--]... (chunk-aligned) 由 drm_gpusvm_range_find_or_insert() 逐个创建调用路径amdgpu_svm_attr_apply_change() → amdgpu_svm_range_map_interval() → amdgpu_svm_range_map() → while (addr end): drm_gpusvm_range_find_or_insert() ← 每次创建/复用一个 chunk 对齐的 svm_range drm_gpusvm_range_get_pages() amdgpu_svm_range_update_gpu_range()5.2 一个 svm_range 跨多个 attr_rangeM:1设计上会处理如果一个 64K 的 svm_range 恰好覆盖了两个不同属性的 attr_range 区域restore 路径中会按 attr 边界分段处理attr_ranges: [ A (RW) ][ B (RO) ] 0x1000 0x17FF 0x1800 0x1FFF svm_range: [ 64K ] 0x1000 0x1FFFamdgpu_svm_range_map_attr_ranges()通过amdgpu_svm_attr_lookup_page_locked()逐段查询属性按 attr 边界分段映射// amdgpu_svm_range_map_attr_ranges()while(cursorlast_page){// 查询当前页的属性及属性范围的结束位置mutex_lock(attr_tree-lock);amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);mutex_unlock(attr_tree-lock);seg_lastmin(seg_last,last_page);if(range_has_access(attrs.access)){retamdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);}cursorseg_last1;}5.3 attr_range 空洞默认属性区域在 attr_tree 中不存在 attr_range 的区域使用默认属性。amdgpu_svm_attr_lookup_page_locked()在查询空洞时返回默认属性// 查询命中空洞时attr_set_default(attr_tree-svm,attrs);*range_lastULONG_MAX;// 查找下一个 attr_range 的起始位置来确定空洞的结束nodeinterval_tree_iter_first(attr_tree-tree,page1,ULONG_MAX);if(node){rangecontainer_of(node,structamdgpu_svm_attr_range,it_node);if(range-it_node.startpage)*range_lastrange-it_node.start-1;}6. 桥接机制属性变更如何传递到映射层6.1 完整调用链amdgpu_svm_attr_set() ← 用户 ioctl 入口 │ ├─ 验证 VMA 检查 │ └→ amdgpu_svm_attr_set_range() ← 遍历 [start, last]按 attr 段处理 │ │ while (cursor last): │ ┌─ cursor 命中已有 attr_range? │ │ YES → amdgpu_svm_attr_set_existing() ← 可能 split left/right │ │ NO → amdgpu_svm_attr_set_hole() ← 空洞区域创建新 range │ │ │ └→ 产生 attr_set_ctx { start, last, trigger, prev_attrs, new_attrs } │ └→ amdgpu_svm_attr_apply_change() ← 桥接属性层 → 映射层 │ │ 根据 trigger 类型决定操作 │ ├─ ACCESS_CHANGE new_access: │ → amdgpu_svm_range_map_interval() ← remap GPU 页表 │ ├─ PTE_FLAG_CHANGE / MAPPING_FLAG_CHANGE: │ → amdgpu_svm_range_map_interval() ← remap GPU 页表 │ └─ LOCATION_CHANGE: → amdgpu_svm_range_rebuild_locked() ← destroy recreate svm_ranges │ ├─ amdgpu_svm_range_remove_overlaps() ← 删除旧 svm_ranges └─ amdgpu_svm_range_map_attr_ranges() ← 按 attr 边界重建6.2 trigger 类型与映射层操作的对应trigger 类型映射层操作原因ATTR_TRIGGER_ACCESS_CHANGE(enable)map_interval→ remap需要建立新的 GPU 映射ATTR_TRIGGER_ACCESS_CHANGE(disable)无操作 (align with KFD)TODO: 应 unmapATTR_TRIGGER_PTE_FLAG_CHANGEmap_interval→ remapPTE flags 变了需要更新页表ATTR_TRIGGER_MAPPING_FLAG_CHANGEmap_interval→ remap映射策略变了需要更新ATTR_TRIGGER_LOCATION_CHANGErebuild_locked→ destroy recreatemigrate_devmemflag 是不可变的必须重建 rangeATTR_TRIGGER_GRANULARITY_CHANGE无操作粒度变化只影响后续 range 创建ATTR_TRIGGER_ATTR_ONLY无操作无实质变化6.3 LOCATION_CHANGE 需要 rebuild 的原因drm_gpusvm_range的migrate_devmemflag 在创建时就确定了来自gpusvm_ctx.devmem_possible之后不可变。如果用户改变了preferred_loc/prefetch_loc旧的 svm_range 仍然带着过时的 flagmigration 不会发生。因此必须删除旧的 svm_rangesamdgpu_svm_range_remove_overlaps重新创建amdgpu_svm_range_map_attr_ranges新 range 会从当前 attr 读取最新的devmem_possible7. svm_range 不直接存储属性amdgpu_svm_range只缓存属性的结果不存储属性本身structamdgpu_svm_range{structdrm_gpusvm_rangebase;bool gpu_mapped;// 是否已映射到 GPUuint64_tpte_flags;// 当前 GPU PTE flags属性的结果uint32_tattr_flags;// 当前属性 flags 的快照// ... 没有 preferred_loc, access 等属性字段};当 svm_range 需要 restore如 eviction 后恢复映射时会回查 attr_tree获取最新属性// amdgpu_svm_range_map_attr_ranges() — restore 路径amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);// 用查到的 attrs 重新映射amdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);这确保了即使属性在 eviction 期间被修改restore 时也能使用最新的属性。8. 锁的协调两层使用不同的锁且有特定的获取顺序amdgpu_svm_attr_set_range() 中的锁序 mutex_lock(attr_tree-lock); ← 属性层锁 // 修改 attr_tree mutex_unlock(attr_tree-lock); down_write(svm-svm_lock); ← 映射层锁 amdgpu_svm_attr_apply_change(); // 操作 svm_ranges up_write(svm-svm_lock);不能同时持有两把锁因为amdgpu_svm_range_map_interval()内部需要获取mmap_read_lock如果持有attr_tree-lock可能导致死锁。因此设计上先释放 attr 锁再获取 svm 锁。9. 图示总结用户视角 (ioctl): [ 设置 accessENABLE, flagsRO ] start_page last_page 属性层 (attr_tree): [hole][ attr_A ][ hole ][ attr_B ][hole][attr_C] ↑ default attrs ↑ default ↑ default 各段按属性边界划分与 ioctl 历史相关 映射层 (gpusvm): [r0][r1] [r2][r3][r4] [r5][r6][r7][r8] [r9][r10] ↑ 按 chunk_size 对齐受 VMA/notifier 边界约束 各段独立持有 GPU 页表映射状态 对应关系: N : M 无固定对应通过 attr_lookup 动态查询桥接概念说明attr_range“这段 VA 有什么属性”面向用户ioctl 驱动纯元数据svm_range“这段 VA 有什么 GPU 映射”面向硬件fault/map 驱动持有页表状态对应关系 N:M属性边界 ≠ 映射边界两者独立拆分桥接 trigger → range remap/rebuildattr 层通知 range 层属性变了请更新映射这种解耦设计的好处是属性管理用户策略和 GPU 映射管理硬件约束各自独立演进互不干扰。10. 复杂性代价分析N:M 解耦设计引入了明显的复杂性需要客观评估其代价与收益。10.1 引入的具体复杂性(1) 锁序窗口期与 retry 机制因为两层各有独立的锁amdgpu_svm_attr_set_range()不得不采用释放 attr 锁 → 获取 svm 锁的模式mutex_lock(attr_tree-lock);// 修改 attr_tree属性已变mutex_unlock(attr_tree-lock);← 窗口期属性已改映射未更新down_write(svm-svm_lock);amdgpu_svm_attr_apply_change();// 更新映射up_write(svm-svm_lock);在这个窗口期内如果另一个线程读取 attr 并触发 restore可能看到属性与映射不一致的状态。代码中的-EAGAINretry 机制正是为了应对这种竞争// amdgpu_svm_attr_set() 中retry:ramdgpu_svm_attr_set_range(...);if(r-EAGAIN){amdgpu_svm_range_flush(svm);cond_resched();gotoretry;}如果是单层设计只需一把锁保护原子操作不需要 retry。(2) restore 路径必须逐段查属性因为svm_range不存储完整属性每次 restore 都要调用amdgpu_svm_attr_lookup_page_locked()逐段查询 attr_tree// restore 路径 — 每个 svm_range 都要回查 attr_treewhile(cursorlast_page){mutex_lock(attr_tree-lock);amdgpu_svm_attr_lookup_page_locked(attr_tree,cursor,attrs,seg_last);mutex_unlock(attr_tree-lock);amdgpu_svm_range_map_interval(svm,cursor,seg_last,attrs);cursorseg_last1;}如果是 1:1 对应svm_range直接内嵌属性即可restore 零查询开销。(3) LOCATION_CHANGE 的 destroy recreate 开销因为drm_gpusvm_range的migrate_devmemflag 在创建时不可变location 属性变更时必须销毁再重建所有重叠的 svm_range。这意味着丢弃已有的 GPU 映射和 DMA 状态重新走一遍完整的 map 流程。如果属性直接存在 svm_range 上且 flag 可变只需修改 flag 重新 migrate无需 destroy但这受限于drm_gpusvm框架设计。(4) 两棵独立 interval tree 的维护负担内存开销两棵树各自维护节点代码复杂度边界不对齐时的交叉查询逻辑容易出错调试难度排查问题需要同时 dump 两棵树的状态才能理清全貌10.2 为什么仍然采用这种设计核心原因是两层受不同的外部约束无法统一约束来源attr_rangesvm_range边界由谁决定用户 ioctl任意地址范围drm_gpusvm框架chunk 对齐 VMA notifier生命周期持久存在直到用户清除或进程退出可能被 mmu_notifier 随时回收再重建可控性amdgpu 驱动完全控制drm_gpusvm是 DRM 公共框架xe、amdgpu 共用不能改其 range 结构关键限制drm_gpusvm_range是 DRM 子系统的公共框架它的 range 拆分逻辑和生命周期不能为 amdgpu 的属性需求定制。所以 amdgpu 只能在外面再加一层属性管理。10.3 如果要简化的方向理论上如果drm_gpusvm_range能支持以下能力就可以去掉 attr_tree只维护一棵树需要的框架改动当前限制简化效果range 内嵌驱动私有属性drm_gpusvm_range无属性字段无需外部 attr_treemigrate_devmem可变 / 延迟决定创建时确定不可变无需 destroy recreaterange 拆分时感知属性边界chunk_size 纯硬件对齐减少 N:M 不对齐情况但这需要修改 DRM 公共框架影响面涉及 xe 等其他驱动短期内不现实。10.4 小结复杂性代价 设计收益 ───────────── ───────── 锁序窗口 retry 机制 属性层与映射层独立演进 restore 逐段查属性开销 svm_range 被回收后属性仍然保留 LOCATION_CHANGE destroyrecreate 不侵入 drm_gpusvm 公共框架 两棵 interval tree 维护成本 适配 KFD 属性语义兼容用户空间 API总结复杂性是真实的代价根本原因是 amdgpu 的属性管理需求与drm_gpusvm公共框架的 range 管理模型不匹配被迫用两层解耦来适配。如果未来drm_gpusvm框架能扩展属性支持能力这层复杂性可以消除。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…