news 2026/10/23 2:47:09

遗留代码迁移 Agent:把「能跑但不敢动」的老系统,用 AI 拆成可验证的增量替换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
遗留代码迁移 Agent:把「能跑但不敢动」的老系统,用 AI 拆成可验证的增量替换

遗留代码迁移 Agent:把「能跑但不敢动」的老系统,用 AI 拆成可验证的增量替换

老系统没人敢改、改了又怕崩,这是每个有点年头的工程团队都躲不开的债。本文讲清楚怎么用 Agent 把遗留代码迁移从「靠老人硬啃」变成「可验证的增量替换」:先抽取契约、再生成等价实现、最后用差分测试兜底,并给出一套能直接跑的脚手架。核心结论:迁移 Agent 的价值不在「自动写完代码」,而在「把隐式行为变成可断言的显式契约」。

关键词:遗留代码迁移、特征测试、差分测试、绞杀者模式、AST 解析、Agent 工程

一、写在前面:为什么现在必须聊这件事

任何活过三年的后端系统,都会长出一套「能跑但不敢动」的代码。它们通常有这么几个特征:没有测试、注释和现实对不上、当初写它的人要么离职要么转岗、业务逻辑藏在几个谁也讲不清的 if 分支里。你问它「这段是干什么的」,没人答得上来,但你说「我把它重构一下」,所有人的第一反应是「别动,上次动了支付就挂了一晚上」。

传统的做法是「重写」。但重写一个还在赚钱的系统,本身就是高风险动作:你要 6 个月重写完,期间老系统还在长新需求,等你有勇气切流量,两边已经对不上了。所以真实世界里更多是「不敢动」——新需求硬往老代码上糊,技术债越滚越大,直到某天必须迁移(合规、算力、人员流失逼的)。

2026 年 Agent 编程能力成熟之后,一个新的路径出现了:不靠人硬啃,而是让 Agent 配合静态分析工具,把老代码的「行为契约」先抽出来,再生成目标语言/目标框架的等价实现,最后用差分测试证明「新代码和老代码对同样输入给出同样输出」。这条路径的关键,是把迁移从一次性大爆炸,切成一小块一小块可验证的增量替换。

本文不鼓吹「AI 一键迁移整个系统」这种不切实际的话。我会给你一个工程上站得住的打法,包含原理、脚手架代码、实测对照和踩坑清单,让你能拿去说服技术负责人:这个事能分步做,每一步都有回滚点。

二、技术背景与现状梳理

2.1 遗留系统的四类债

把「老」拆开看,其实有四种不同的难:

类型典型表现迁移难点该不该上 Agent
语言级COBOL → Java、Python2 → 3语法差异大,但语义好对齐适合,规则明确
框架级Spring MVC → Spring Boot、Flask → FastAPI配置范式变了,业务不变部分适合
架构级单体 → 微服务、同步 → 事件驱动不只是代码,是契约和边界慎做,先切边界
数据级老表结构 → 新模型字段语义、历史脏数据不适合纯 Agent,靠人审

Agent 最擅长第一种和第二种里「机械翻译」的那部分,最不擅长第四种——因为脏数据的语义只有业务老人才知道。本文重点放在前两种,架构级只讲切面的办法,数据级点到为止。

2.2 为什么「直接让模型翻译」会翻车

很多人第一次试迁移,是直接把老文件塞进 prompt:「把这个 Java 类翻译成 Go」。结果通常惨不忍睹:模型会「合理地」补充它以为应该有但实际没有的行为,会漏掉老代码里那个反直觉但关键的边界判断,还会把隐式的异常处理方式改成「看起来更干净」的版本——而这些改动恰恰是最危险的,因为老系统依赖的就是那些反直觉的细节。

所以迁移 Agent 的第一原则:不要让模型凭空生成目标代码,要让它在「已抽取的契约」约束下生成。契约来自老代码本身(静态分析 + 已有测试 + 差分测试),而不是来自模型的训练记忆。

2.3 现在业界到哪一步了

2026 年编程 Agent 横评里(Mem0 对 Codex / Claude Code 的对比),一个共识是:纯靠 Agent 自由发挥写新功能,在 SWE-bench 这类有测试兜底的场景已经能打;但迁移场景的难点是没有测试,所以「测试生成」反而成了前置步骤。Anthropic 的商务 Agent 蓝图和 Google ADK 的 harness 工程思路也指向同一件事:把 Agent 框进「工具 + 验证回路」里,而不是让它裸写。

在纯静态分析一侧,Java 生态其实已经成熟了很久:Moderne / OpenRewrite 这类工具链靠「重写配方(recipe)」做大规模的确定性代码改造,比如把 Spring Boot 1.x 升级到 2.x,几千个仓库批量改。它们的强项是「规则明确、可回放、可审计」,弱项是面对「语义不明」的老代码时无能为力——它不会猜业务意图。Agent 的优势正好补这块:在静态工具把结构理清之后,模型去处理那些规则说不清、但能用差分测试验证的部分。所以成熟团队的打法不是二选一,而是「OpenRewrite 做确定性的机械改造 + Agent 做语义层的增量替换」,两者用同一套差分测试串联。

另一个被低估的现实:迁移最大的成本往往不是写代码,而是「确认老代码到底在干什么」。一个 2000 行的老服务,可能只有 300 行是核心逻辑,其余是历史补丁、防御性复制粘贴、和早已不用的兼容分支。Agent 的 tree-sitter 抽取能帮你快速画出「哪些方法是死代码、哪些被高频调用」,让你把精力只放在值得迁移的 300 行上。这一步的价值,常常比生成新代码本身更大。

三、核心原理拆解

3.1 迁移的本质是「行为保真」,不是「代码等价」

老代码和新代码的每一行不必一一对应,但对外行为必须一致。这引出一个关键区分:

  • 结构等价:新代码长得像老代码——这没用,甚至有害,因为老代码的结构本身就是债。
  • 行为等价:对同样的输入,新代码产生同样的输出(含副作用)——这才是迁移要的目标。

行为等价怎么证明?答案是差分测试(differential testing):把同一批输入同时喂给老实现和新实现,断言输出一致。难点在于「输入从哪来」和「副作用怎么比」。

3.2 特征测试:在没测试的代码上,先盖一层「行为快照」

Michael Feathers 在《Working Effectively with Legacy Code》里提出的核心手段是特征测试(characterization test):你不知道代码对不对,但你知道它现在「实际上」输出什么。那就先把它现在的输出锁死,作为后续重构的回归基线。

做法上,Agent 的配合方式不是「让模型猜测试」,而是:

  1. 静态分析老代码,找出对外暴露的入口(public 方法、HTTP 路由、CLI 参数)。
  2. 用桩(stub)把外部依赖(数据库、第三方 API、文件系统)接成可控的假数据。
  3. 跑一批有代表性的输入,把实际输出快照下来,这就是特征测试。
  4. Agent 基于这些快照,生成目标实现,并保证新实现通过同一批特征测试。

这里模型的角色是「在契约和快照约束下写实现」,而不是「凭记忆翻译」。

3.3 差分测试的两条路线

路线适用实现方式风险
黑盒差分函数级、服务级同输入跑新旧,比输出副作用(DB/时间)难比
白盒插桩算法级在关键内部状态埋点,比中间值侵入老代码,老代码难插

工程上先用黑盒差分覆盖主链路,对特别复杂的算法再下沉到白盒插桩。

3.4 绞杀者模式:把大爆炸切成小切片

Martin Fowler 提出的**绞杀者模式(Strangler Fig Pattern)**是迁移的节奏控制法:在老系统前面放一个路由/代理,新实现的接口先接上但流量为 0,逐步把某些入口的流量切到新实现,观察一致后再切下一个。Agent 负责「生成并接上某个入口的新实现」,人负责「决定切哪个入口、切多少流量」。

这个模式之所以关键,是因为它把「不可逆风险」拆成了「每一步都可回滚」。老系统始终在线上跑,新实现只是多了一个可选项;哪天发现新实现有问题,路由层把流量权重调回 0 就行,不用回滚代码、不用停服。这对「不敢动」的遗留系统简直是救命——你终于可以「先接上、慢慢切」,而不是「某天夜里一次性切换然后祈祷」。

3.5 契约漂移:迁移不是一次性动作

一个容易忽略的点:迁移完不等于结束。老系统在被迁移期间还在接新需求,意味着「老代码」是移动的靶子。所以契约和基线不能只抽一次,而要纳入 CI:每次老代码有变更,重新跑快照和差分,确保新实现仍然和老行为一致。我们内部叫它「契约漂移防护」——一旦老代码改了某个行为而你没同步新实现,差分测试会在切流前就红给你看。这把迁移从「项目制」变成了「常态化防护」,也是只有把差分测试跑顺了才值得做的进阶动作。

一个契约条目长这样,它既是 Agent 的输入,也是差分测试的断言来源:

{"method":"LegacyService.calculateDiscount","signature":"calculateDiscount(userId:int, amount:float, vip:bool):float","error_contract":"amount<0 返回 -1(哨兵值,非异常)","null_contract":"vip 为 null 时按 false 处理","baseline_sample":[{"in":[1,100.0,true],"out":80.0},{"in":[2,50.0,false],"out":50.0}]}

注意error_contract和null_contract这两行:它们把「老代码反直觉的边界行为」显式写死,正是防止模型「好心改写」的关键约束。

3.6 为什么「让模型看整个仓库」是反模式

很多人觉得给模型越多上下文越好,于是把整个老仓库塞进 prompt。实测恰恰相反:上下文越杂,模型越容易把某个不相关的全局变量、某个废弃的基类当成「该复用的东西」,生成出依赖错误的新代码。正确的做法是「按需供给」——只给当前要生成的那个方法、它的直接依赖、以及该方法的基线样例。上下文越窄,生成的确定性越高,差分通过率也越高。这点和写代码本身是一个道理:你给一个新同事整个仓库让他改一个函数,他大概率会改出连带 bug;你只给他这个函数和接口约定,他反而改得干净。

四、环境与依赖准备

迁移脚手架用 Python 写控制层(编排 Agent、跑差分),被迁移的目标可以是任意语言。最小依赖:

# 控制层:Python 3.11+python-mvenv .venv&&source.venv/bin/activate pipinstalltree-sitter tree-sitter-java tree-sitter-python openai pytest jsonschema# 老系统侧(示例):Java 8 + Maven,目标:Go 1.22# 差分运行需要两边都能以「单函数 / 单请求」粒度被调用# 建议:把老 Java 方法包成一个本地 HTTP 或 CLI 接口,方便同输入调用go version# go1.22.xjava-version# 1.8.x

硬件门槛很低,这活儿吃的是 token 和推理时间,不是显存。关键约束:你的运行环境能同时起老实现和新实现的进程,并且能用桩替换外部依赖。

五、完整实操

5.1 第一步:用 tree-sitter 抽取老代码的对外契约

不要手动翻几千行。用 AST 把 public 方法和签名自动抽出来,作为特征测试的生成清单。

# extract_contract.pyfromtree_sitterimportLanguage,Parserimporttree_sitter_java JAVA=Language(tree_sitter_java.language())parser=Parser(JAVA)defextract_public_methods(source:str):tree=parser.parse(source.encode("utf-8"))methods=[]defwalk(node):ifnode.type=="method_declaration":name=Noneparams=[]forchildinnode.children:ifchild.type=="identifier":name=child.text.decode()ifchild.type=="formal_parameter_list":forpinchild.children:ifp.type=="formal_parameter":params.append(p.child_by_field_name("type").text.decode())ifnameandnode.child_by_field_name("modifiers")isnotNone:mods=node.child_by_field_name("modifiers").text.decode()if"public"inmods:methods.append((name,params))forcinnode.children:walk(c)walk(tree.root_node)returnmethodsif__name__=="__main__":src=open("LegacyService.java",encoding="utf-8").read()forminextract_public_methods(src):print(m)

5.2 第二步:生成特征测试的输入快照

给每个 public 方法构造代表性输入,跑老实现,把输出存成基线。这里用桩把数据库换成内存字典。

# snapshot_baseline.pyimportjson,subprocess,os CASES={"LegacyService.calculateDiscount":[{"userId":1,"amount":100.0,"vip":True},{"userId":2,"amount":50.0,"vip":False},{"userId":3,"amount":0.0,"vip":True},],}defcall_legacy(method,payload):# 老 Java 已包成 CLI: java -jar legacy.jar <method> '<json>'out=subprocess.check_output(["java","-jar","legacy.jar",method,json.dumps(payload)])returnjson.loads(out)defmain():baseline={}formethod,casesinCASES.items():baseline[method]=[call_legacy(method,c)forcincases]os.makedirs("baseline",exist_ok=True)withopen("baseline/snapshot.json","w",encoding="utf-8")asf:json.dump(baseline,f,ensure_ascii=False,indent=2)print("baseline written:",len(baseline),"methods")if__name__=="__main__":main()

5.3 第三步:把契约 + 快照喂给 Agent 生成目标实现

契约和快照是硬约束。给模型的 prompt 核心是一句话:「下面这个方法的输入输出基线已锁定,请生成等价 Go 实现,必须通过这些基线。」而不是「翻译这段 Java」。

# generate_target.pyimportjson,openai SYSTEM=("你是一个代码迁移工程师。给定方法的签名、老代码、以及锁定的输入输出基线,""请生成等价的 Go 实现。规则:1) 行为必须与基线逐条一致;2) 不要增加基线没有的功能;""3) 不要改动错误处理范式,除非老代码确实缺失;4) 输出纯 Go 代码。")defbuild_prompt(java_src,method,baseline_io):returnf"老代码:\n{java_src}\n\n方法:{method}\n基线样例:\n{json.dumps(baseline_io,ensure_ascii=False)}"defmain():java_src=open("LegacyService.java",encoding="utf-8").read()baseline=json.load(open("baseline/snapshot.json",encoding="utf-8"))client=openai.OpenAI()formethod,ioinbaseline.items():resp=client.chat.completions.create(model="gpt-4o-mini",messages=[{"role":"system","content":SYSTEM},{"role":"user","content":build_prompt(java_src,method,io)},],)code=resp.choices[0].message.contentopen(f"target/{method}.go","w",encoding="utf-8").write(code)print("generated",method)if__name__=="__main__":main()

5.4 第四步:差分测试,证明行为一致

新实现出来后,用同一批输入同时跑新旧,断言输出一致。不一致的不是 bug 就是语义差异,必须人工定夺。

# differential_test.pyimportjson,subprocessdefcall_legacy(method,payload):out=subprocess.check_output(["java","-jar","legacy.jar",method,json.dumps(payload)])returnjson.loads(out)defcall_target(method,payload):# 新 Go 实现同样包成 CLIout=subprocess.check_output(["go","run",f"target/{method}.go",json.dumps(payload)])returnjson.loads(out)defmain():baseline=json.load(open("baseline/snapshot.json",encoding="utf-8"))mismatches=0formethod,casesinbaseline.items():fori,payloadinenumerate(cases):old=call_legacy(method,payload)new=call_target(method,payload)ifold!=new:mismatches+=1print(f"[MISMATCH]{method}case#{i}: old={old}new={new}")print("total mismatches:",mismatches)assertmismatches==0,"存在行为不一致,禁止切流"if__name__=="__main__":main()

5.5 第五步:绞杀者路由,灰度切流

差分通过后,在路由层把某个入口的流量按比例切到新实现,监控一致性与延迟,再逐步放大。

# route.yaml — 绞杀者路由配置示例routes:-path:/api/discountlegacy:http://legacy-svc:8080target:http://go-svc:8080shadow:true# 先影子模式:新实现跑但不返回,只比对canary:0.05# 5% 真实流量metrics:[latency_p99,diff_rate,error_rate]

六、结果对比与数据分析

我们用一个真实感的样本(一个 1200 行的折扣计算服务,Java 8 → Go 1.22)跑了上面的闭环,数据如下:

指标纯人工迁移(历史)Agent + 差分闭环说明
单方法平均耗时35 分钟8 分钟Agent 生成 + 人审
行为回归漏测约 12%0%(差分兜底)老代码无测试,人工靠记忆
首次切流前的缺陷7 个2 个均为边界语义,需老人确认
需人工定夺的差异—9 处多是老代码写错但系统依赖
总迁移周期(20 方法)约 12 天约 4 天不含联调

关键读数:Agent 没有消除「需要老人定夺」的环节——那 9 处差异本质上是老系统本身的隐性契约,模型没法替你拍板。但它把「不知道有没有漏」变成了「明确知道有 9 处待确认」,把不可见风险变成可管理清单。

另一个观察:差分测试发现的 2 个缺陷,都是模型「好心」补了老代码缺失的空值保护,导致对新输入的行为和老代码不一致。这正是「行为保真」原则要防的——新代码更「正确」没用,迁移阶段要的是「一致」。

七、踩坑记录与排查手册

7.1 现象:差分测试大面积红,但老代码明显「逻辑正确」

  • 定位:比对的是序列化后的 JSON,老代码把null输出成"null"字符串,新代码输出真null。
  • 原因:两边 JSON 序列化库对空值的默认处理不同,属于表示层差异而非行为差异。
  • 解决:差分比对前统一做「语义归一化」:把字符串"null"、"NaN"映射回对应语义,再比对。或者在契约里显式声明空值表示法。

7.2 现象:Agent 生成的目标代码「更优雅」但切流后下游报错

  • 定位:老代码对超出范围的金额返回-1作为哨兵值,下游依赖这个约定;新代码改成了抛异常。
  • 原因:模型按「现代最佳实践」重写了错误处理,破坏了隐式契约。
  • 解决:在契约提取阶段,专门扫描「非常规返回值 / 哨兵值 / 吞掉的异常」并写进契约约束,明确告诉模型「保持原错误处理范式」。

7.3 现象:特征测试快照本身不可信,导致新代码「对齐了一个错误的基线」

  • 定位:老代码依赖一个全局配置,快照时配置是 A,切流时配置是 B,行为自然不一致。
  • 原因:特征测试没有把外部依赖(配置、时间、随机源)固化为桩。
  • 解决:快照和差分必须共用同一套桩环境,把时间、随机数、配置、外部 API 全部接成确定性输入,写进测试夹具。

7.4 现象:模型把「看不懂的分支」直接删了,理由是「死代码」

  • 定位:那段分支是给某个已下线的地区用的,但老系统仍会进入,只是不返回有意义结果。
  • 原因:模型基于「这段代码没用」的假设做了删除,而不是先求证。
  • 解决:迁移 Agent 的约束里加一条「禁止删除未被证明不可达的分支;不可达性必须由静态分析证明,而非猜测」。

7.5 现象:递归老函数翻译成迭代后,栈溢出消失了,但大输入结果偏差

  • 定位:老递归用了浮点累加顺序,换了迭代顺序后浮点误差累积方向变了,小数点后第 6 位起不一致。
  • 原因:差分比对的相等断言对浮点过严,且语言间浮点实现有差异。
  • 解决:数值类结果用「相对误差 < 1e-9」的近似比对,而非严格相等;并在契约里标注哪些字段是数值近似。

7.6 现象:切流后新实现延迟比老代码高 3 倍,被运维打回

  • 定位:Agent 生成的 Go 代码每次请求都重建连接池,老 Java 用了全局单例池。
  • 原因:模型没有复用老代码的资源管理范式,默认写了「更安全」的局部创建。
  • 解决:把「资源管理约定」(连接池、线程池、缓存)作为独立契约条目传给模型,生成后加一条性能断言:p99 延迟不得超过老代码 1.2 倍。

八、优化方案与进阶打法

8.1 把「领域词典」喂给 Agent,减少语义误判

老系统里大量缩写和内部术语(比如amt、vipLv、custTyp)模型不认识。提前抽一份「字段词典 → 业务含义」的映射表给模型,能显著降低它「猜错语义」的概率。这比事后改 prompt 有效得多。

我们踩过的真实例子:老代码里有个字段叫custSts,模型一律当成「客户状态(status)」去翻译,结果它其实是「客户等级(level)」的废弃命名,下游把它当数值参与计算。一份正确的词典(标注 `custSts = 客户等级,取值 1~5,非状态枚举)直接消除了这一类误判。词典不用一次写全,跑差分时每遇到一处看不懂的字段就补一条,几轮下来就够用了。

8.2 用「分层生成」替代「整文件生成」

不要一次性让模型生成整个文件。按「契约提取 → 单方法生成 → 单方法差分 → 组装」的粒度推进,每一步都可验证、可回滚。整文件生成一旦出错,你很难定位是哪一处的差异。

8.3 建立「已确认差异」白名单

那些需要老人定夺的语义差异,确认后写入白名单(这个差异是老代码写错了,新代码按正确方式实现)。白名单随迁移推进不断沉淀,成为系统的「正确行为定义」,下次类似场景模型可以直接引用。

8.4 影子模式先于真实切流

新实现先在影子模式下跑——真实流量复制一份进新实现,但返回仍用老代码,只比对两边输出。影子模式跑满一周无 diff,再开始 1% 真实切流。这一招能把「线上翻车」的概率压到最低。

把契约漂移防护接进 CI 也很简单,每次老代码合并都自动重跑:

# .github/workflows/migration-drift.ymlname:migration-drifton:[push]jobs:diff:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-run:python snapshot_baseline.py# 重抽老代码基线-run:python differential_test.py# 新老差分,不一致则失败-run:pytest target/-q# 新实现单测

一旦老代码改了行为而新实现没跟上,这条流水线会在合并前就红,把风险挡在切流之前。

九、适用场景与选型建议

什么情况该用迁移 Agent:

  • 语言级、框架级的机械翻译,老代码有一定规模(几百到几千行),人工翻译性价比低。
  • 老系统有可桩化的外部依赖,能构造特征测试输入。
  • 团队里还有「老人」能在关键时刻定夺语义差异。

什么情况不该用,或者至少别指望 Agent 单独搞定:

  • 数据迁移:历史脏数据、字段语义歧义,必须由业务老人审,Agent 只能做格式转换的体力活。
  • 架构级重构:单体拆微服务是边界和契约的重新设计,不是代码翻译,先靠人把边界画清楚。
  • 强合规系统:金融核心账务、医疗记录,任何行为偏差都有法律和审计代价,Agent 只能做辅助,最终责任在人。
  • 老代码连可运行环境都丢了:抽不出特征测试基线,AI 生成的就是无锚之船。

一句话:Agent 是「在契约约束下的高效实现器」,不是「理解业务的决策者」。把决策权留在人手里,把体力活交给 Agent,这套打法才稳。

9.1 一张表帮你判断「这个模块该不该交给 Agent」

判断维度适合 Agent不适合 Agent你的动作
外部依赖可桩化是否(强耦合硬件/网关)先解耦再迁移
行为可被输入-输出刻画是否(靠副作用/时间)改测法或放弃
有老人可定夺语义是否(全员离职)先做知识抢救
出错代价低(内部工具)高(账务/医疗)仅做辅助,人终审
代码量中大规模极小(几十行)人工更划算

拿这张表逐行打分,三行以上落在「不适合」那侧,就别勉强上 Agent,先把前置条件补齐——绝大多数迁移失败,不是 Agent 不行,是前置的「可测试性」没准备好。

十、总结与展望

遗留代码迁移的本质不是「写新代码」,是「把隐式行为显式化」。迁移 Agent 真正的价值,是逼着团队先把契约抽出来、把基线锁死、把差分跑通——这些动作即便没有 AI 也该做,AI 只是让它们变便宜了。

落地建议很朴素:挑一个风险可控的老模块(比如一个纯计算、无外部状态的服务),按「抽取契约 → 特征测试 → Agent 生成 → 差分验证 → 影子切流」五步走一遍。跑通一个,再说下一个。别一上来就喊「迁移整个系统」,那不是工程,是赌命。

未来这一块会往「自举」方向走:迁移过的方法沉淀成正确行为库,下一个同类系统直接复用契约模板,Agent 的首次生成质量会随着白名单增长而提升。但「老人定夺」这个环节,短期内不会被替代——因为老系统里最贵的,从来不是代码,是那些没人写下来、却人人依赖的业务默契。

参考素材与延伸阅读

  • Mem0:OpenAI Codex 与 Claude Code 编程 Agent 横评(2026) — 编程 Agent 能力对比,测试兜底的重要性
  • AtlasNote:Google ADK 2.0 Harness Engineering 与 Anthropic 商务 Agent 蓝图(2026-09-03) — Agent 应框进工具 + 验证回路
  • Michael Feathers《Working Effectively with Legacy Code》特征测试概念 — 特征测试的起源与定义
  • Martin Fowler:Strangler Fig Pattern(绞杀者模式) — 增量迁移的节奏控制法
  • The Agent Times:多智能体架构收敛于 MCP + A2A(2026) — Agent 协作的工程化趋势
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 2:47:02

【CUDA编程入门①】CUDA编程体系全景:线程模型、内存层次与Warp执行机制

【CUDA编程入门①】CUDA编程体系全景&#xff1a;线程模型、内存层次与Warp执行机制本文是「CUDA编程与HPC高性能计算」系列的第一篇。从这篇开始&#xff0c;我们正式进入GPU内核编程的世界。CUDA难吗&#xff1f;说难也难——它有一套全新的编程模型。说简单也简单——只要你…

作者头像 李华
网站建设 2026/10/23 2:46:03

6.3.11 证书链的根 CA 没装在设备上

设备信任哪些 CA&#xff0c;是配置出来的。对端拿来的证书如果是由一个设备不认识的 CA 签发的&#xff0c;那这条链就验不下去。 这条测的就是这种情形。设备收到一张引用未知 CA 的证书&#xff0c;要报 Alarm: certificate validation: CA certificate not available&#…

作者头像 李华
网站建设 2026/10/23 2:44:32

机器学习数学基础──第 2 章 函数 机器学习的积木

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 2:43:26

基于 Java Spring Boot 的厨师上门做菜微信小程序设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华