DeepSeek总结的AI 如何改变 JIT 编译器的成本效益
原文https://malisper.me/how-ai-changes-the-economics-of-jit-compilers/AI 如何改变 JIT 编译器的成本效益从历史上看JIT 编译一直是一门黑魔法。要编写一个快速的 JIT 编译器你需要知道如何编写汇编代码。一个典型的例子是当今没有一个生产就绪的数据库拥有自己的 JIT 编译器。它们要么使用 LLVM要么生成 C/C 代码。这两种方案都存在编译时间长的问题这限制了它们的适用性。现在借助 AI 的使用通过直接生成汇编代码来编写一个编译速度快的 JIT 编译器比以往任何时候都更容易。这也是新数据库能够在旧数据库基础上进行改进的领域之一。在构建 pgrust 时我最初认为实现一个 JIT 编译器会非常困难。最后我发现由于 AI 的帮助它比我预期的要容易得多这也是 pgrust 速度如此之快的原因之一。在这篇文章中我将带你了解如何构建你自己的 JIT 编译器。我们将构建一个使用 JIT 编译的简单正则表达式引擎作为例子。为什么使用 JIT 编译JIT 编译是在运行时“即时”生成编译代码的做法。如果做得好它可以带来巨大的性能提升通常是 2-5 倍有时甚至更多。JIT 编译的主要用例是当你在运行时获得的信息会极大地改变程序行为时。这在编程语言解释器中尤其常见它们在运行时接收要执行的代码。JIT 编译器在编程语言之外的领域也很有用例如解析数据。有时你在运行时才知道要解析的数据的模式schemaJIT 可以帮助解决这个问题。首先让我们实现一个玩具正则表达式引擎。为了保持简单我们只支持两个特性字面量字符串和重复即正则表达式*。我们还将跳过解析器将正则表达式表示为已经解析好的 Rust 结构。这意味着我们将能够支持如下字符串applesb(an)*但不支持选择、后向查找或类似功能。在代码中这相当简单。我们将有 3 种类型的节点一个字面量字符串节点、一个重复节点和一个连接节点它是两个节点的组合。最终看起来像这样enumNode{Literal(staticstr),Concatenation(BoxNode,BoxNode),Repetition(BoxNode),}fnliteral(text:staticstr)-Node{Node::Literal(text)}fnconcatenation(left:Node,right:Node)-Node{Node::Concatenation(Box::new(left),Box::new(right))}fnrepetition(body:Node)-Node{Node::Repetition(Box::new(body))}为我们的正则表达式引擎编写一个解释器也很直接fnmatch_node(node:Node,input:[u8],pos:usize,next:dynFn(usize)-bool)-bool{matchnode{Node::Literal(text){letliteraltext.as_bytes();input[pos..].starts_with(literal)next(posliteral.len())}Node::Concatenation(left,right){match_node(left,input,pos,|left_end|{match_node(right,input,left_end,next)})}Node::Repetition(body){match_node(body,input,pos,|body_end|{match_node(node,input,body_end,next)})||next(pos)}}}fninterp_match(regex:Node,input:str)-bool{letbytesinput.as_bytes();match_node(regex,bytes,0,|pos|posbytes.len())}这个正则表达式引擎非常简单。它不到 20 行代码但让我们看看它在性能方面的表现。作为比较我们将此代码与专门为正则表达式实现的手写代码进行对比。我们的示例将使用正则表达式b(an)*。手写代码最终看起来像fnhandwritten_b_an_star(input:str)-bool{letbytesinput.as_bytes();letmutpos0;ifposbytes.len()||bytes[pos]!bb{returnfalse;}pos1;whileposbytes.len(){ifbytes[pos]!ba{returnfalse;}pos1;ifposbytes.len()||bytes[pos]!bn{returnfalse;}pos1;}true}还有一些方法可以优化这段代码并使其更快但出于我们的目的它作为一个很好的比较基准当我对几个例子进行基准测试时我得到手写版本比解释器快 10-20 倍。显然有很大的改进空间。现在让我们看看如何使用 JIT 编译来获得一个性能与手写版本相当的正则表达式引擎。如何进行 JIT 编译JIT 编译代码有两个步骤。首先为你想要运行的代码生成汇编代码。一旦你有了代码然后将汇编代码打包成一个函数你可以像调用程序中的任何其他代码一样调用它。为了生成汇编代码我们将使用一种称为“复制和修补”copy-and-patch的方法的变体。其思想是我们有针对不同操作的汇编模板系列。这些模板被称为“模版”stencils。当我们想要 JIT 编译一个操作时我们取关联的模版并根据操作的具体细节进行小的调整。非常类似于填充真正的模版。通过将几个这样的填充模版串在一起我们可以在运行时构建一个程序其性能与手写版本相似。以下是我们将采取的路径首先我们将查看我们想要为b(an)*生成的 ARM64 代码。然后我们将重复的指令序列转化为可重用的模版编写一个发射器emitter从正则表达式 AST 填充并组合这些模版最后将生成的指令复制到可执行内存中以便 Rust 可以像调用普通函数一样调用它们。为了让你了解这是如何工作的最简单的方法是从生成的代码开始然后反向推导到 JIT 编译器本身。再次强调我们正在处理正则表达式b(an)*。为了说明一些设计决策我们将使用一个栈进行回溯backtracking。栈将跟踪如果我们在正则表达式中遇到死路时应该转到的状态。我们匹配的字符串将以空字节NUL byte结尾。这意味着如果我们到达字符串的末尾我们的任何字符比较都会自动失败。这意味着我们在任何时候都不需要进行长度比较。对于程序状态我们将使用以下寄存器x0– 字符串中的当前位置和返回值x1– 用于回溯的栈顶x2– 用于回溯的栈底这是确定栈是否为空所必需的x9– 用作临时变量对于程序的输入我们将传入x0– 指向字符串开头的指针x1– 指向我们将用于栈的内存的指针生成的 ARM64 代码现在我们已经处理好了这些让我们逐部分地浏览生成的汇编代码。这专门针对 macOS 上的 ARM64。首先我们有 prologue它初始化程序。它所做的只是通过将栈顶和栈底设置为传入的值来初始化栈0: aa0103e2 mov x2, x1接下来我们有检查字符b的代码。如果它看到一个不是b的字符我们跳转到一个处理回退逻辑的代码块。否则我们推进字符串中的位置; CHAR b 4: 39400009 ldrb w9, [x0] ; 加载当前输入字节 8: 7101893f cmp w9, #0x62 ; 是 b 吗 c: 54000281 b.ne 0x5c ; 不是 - 回退块 10: 91000400 add x0, x0, #1 ; 是 - 推进输入接下来我们有重复(an)*。对于重复我们需要进行回溯。如果我们在这里回溯这意味着我们立即跳转到循环结束。这意味着我们需要将循环后的指令地址和我们在字符串中的位置都存储在栈上。14: d2800989 movz x9, #0x004c ; 构建恢复地址 18: f2a00009 movk x9, #0x0000, lsl #16 ; 0x1_0000_004c 1c: f2c00029 movk x9, #0x0001, lsl #32 ; (循环出口) 20: f2e00009 movk x9, #0x0000, lsl #48 ; 24: a8810029 stp x9, x0, [x1], #16 ; 将 (exit, pos) 压入栈有了这些我们现在可以执行循环体了。这将检查字符a和n如果看到它们就回到循环顶部但是是在一个新的字符串位置。; CHAR a 28: 39400009 ldrb w9, [x0] 2c: 7101853f cmp w9, #0x61 ; a? 30: 54000161 b.ne 0x5c ; 不是 - 回退块 34: 91000400 add x0, x0, #1 ; CHAR n 38: 39400009 ldrb w9, [x0] 3c: 7101b93f cmp w9, #0x6e ; n? 40: 540000e1 b.ne 0x5c ; 不是 - 回退块 44: 91000400 add x0, x0, #1 ; JMP 48: 17fffff3 b 0x14 ; 回到循环顶部现在我们过了循环。这是我们回溯时将跳转到的位置。一旦我们完成了重复就到了正则表达式的末尾。我们现在要做的只是检查我们是否在字符串的末尾。如果我们在末尾我们返回 1 表示成功。如果我们不在这意味着正则表达式匹配失败我们需要运行失败逻辑进行回退。4c: 39400009 ldrb w9, [x0] 50: 35000069 cbnz w9, 0x5c ; 不是 NUL - 回退块 54: d2800020 mov x0, #1 ; 成功 58: d65f03c0 ret最后我们有回退逻辑。这检查栈是否为空。如果是我们返回 0。如果不是我们从栈中弹出回退地址和回退字符串位置然后跳转到回退地址。5c: eb02003f cmp x1, x2 ; 还有帧吗 60: 54000060 b.eq 0x6c ; 没有 - 放弃 64: a9ff0029 ldp x9, x0, [x1, #-16]! ; 弹出 (resume, pos) 68: d61f0120 br x9 ; 跳转到那里 6c: d2800000 mov x0, #0 ; 不匹配 70: d65f03c0 ret构建模版现在你已经有机会看到编译后的代码你应该开始理解复制和修补编译器是如何工作的。我们有共同的指令集它们之间只有微小的差异。对于这些函数块中的每一个我们可以编写一个函数来生成相应的代码。每个函数将接收用于修改代码的值。例如stencil_char的参数之一将是正则表达式中要比较的字符。我们将该字符直接插入到机器码中。Prologue 很简单因为它只是一个代码块constPROLOGUE_WORDS:usize1;fnstencil_prologue()-[u32;PROLOGUE_WORDS]{[0xAA0103E2]// mov x2, x1}对于字符比较我们需要插入我们正在比较的字符以及跳转到回退逻辑的位置constCHAR_WORDS:usize4;fnstencil_char(byte:u8,stencil_pos:usize,fail_pos:usize)-[u32;CHAR_WORDS]{[0x39400009,// ldrb w9, [x0]0x7100013F|((byteasu32)10),// cmp w9, #byte0x54000001|cond_branch_offset(stencil_pos2,fail_pos),// b.ne fail0x91000400,// add x0, x0, #1]}对于重复我们有循环开始处压入栈的操作和跳转到循环结束的操作constSPLIT_WORDS:usize5;fnstencil_split(resume_addr:u64)-[u32;SPLIT_WORDS]{[0xD2800009|addr_bits(resume_addr,0),// movz x9, #addr[0..16]0xF2A00009|addr_bits(resume_addr,1),// movk x9, #addr[16..32], lsl 160xF2C00009|addr_bits(resume_addr,2),// movk x9, #addr[32..48], lsl 320xF2E00009|addr_bits(resume_addr,3),// movk x9, #addr[48..64], lsl 480xA8810029,// stp x9, x0, [x1], #16]}constJMP_WORDS:usize1;fnstencil_jmp(stencil_pos:usize,target_pos:usize)-[u32;JMP_WORDS]{[0x14000000|branch_offset(stencil_pos,target_pos)]// b target}然后我们有匹配和失败块它们相当简洁constMATCH_WORDS:usize4;fnstencil_match(stencil_pos:usize,fail_pos:usize)-[u32;MATCH_WORDS]{[0x39400009,// ldrb w9, [x0]0x35000009|cond_branch_offset(stencil_pos1,fail_pos),// cbnz w9, fail0xD2800020,// mov x0, #10xD65F03C0,// ret]}constFAIL_WORDS:usize6;fnstencil_fail()-[u32;FAIL_WORDS]{[0xEB02003F,// cmp x1, x20x54000060,// b.eq 3 (to the mov below)0xA9FF0029,// ldp x9, x0, [x1, #-16]!0xD61F0120,// br x90xD2800000,// mov x0, #00xD65F03C0,// ret]}为了完整性这里是我们使用的辅助函数它们只是帮助我们将特定数据插入到指令中// 计算条件分支b.ne / cbnz的分支偏移字段// 从分支到目标的指令数存储在位 5..24 中。fncond_branch_offset(branch_pos:usize,target_pos:usize)-u32{letinstr_counttarget_posasi64-branch_posasi64;// 可能为负(((instr_countasu64)0x7FFFF)5)asu32}// 计算无条件分支b的分支偏移字段// 思路相同但存储在位 0..26 中。fnbranch_offset(branch_pos:usize,target_pos:usize)-u32{letinstr_counttarget_posasi64-branch_posasi64;// 可能为负((instr_countasu64)0x3FF_FFFF)asu32}// 提取绝对地址的 16 位为 movz/movk 立即数定位。fnaddr_bits(addr:u64,part:usize)-u32{(((addr(16*part))0xFFFF)asu32)5}发射代码现在是驱动它的代码// 计算一个节点编译成多少条指令。fnnode_words(node:Node)-usize{matchnode{Node::Literal(text)text.len()*CHAR_WORDS,Node::Concatenation(left,right)node_words(left)node_words(right),Node::Repetition(body)SPLIT_WORDSnode_words(body)JMP_WORDS,}}structEmitter{code:Vecu32,fail:usize,// 共享失败块的偏移以字为单位base:u64,// code[0] 的运行时地址用于绝对地址空洞}implEmitter{// 返回下一条指令将被放置的偏移量。fnpos(self)-usize{self.code.len()}// 将填充的模版追加到代码缓冲区。fnemit(mutself,stencil:[u32]){self.code.extend_from_slice(stencil);}// 为一个节点发射代码递归处理子节点。fnemit_node(mutself,node:Node){matchnode{Node::Literal(text){forbyteintext.as_bytes(){self.emit(stencil_char(byte,self.pos(),self.fail));}}Node::Concatenation(left,right){self.emit_node(left);self.emit_node(right);}Node::Repetition(body){letsplit_atself.pos();letexitsplit_atSPLIT_WORDSnode_words(body)JMP_WORDS;self.emit(stencil_split(self.baseexitasu64*4));self.emit_node(body);self.emit(stencil_jmp(self.pos(),split_at));}}}}// 生成完整程序prologue、编译后的 AST、MATCH、失败块。fngenerate_code(regex:Node,base:u64)-Vecu32{letnwordsPROLOGUE_WORDSnode_words(regex)MATCH_WORDSFAIL_WORDS;letmutemitterEmitter{code:Vec::with_capacity(nwords),fail:nwords-FAIL_WORDS,base,};emitter.emit(stencil_prologue());emitter.emit_node(regex);letmatch_atemitter.pos();emitter.emit(stencil_match(match_at,emitter.fail));emitter.emit(stencil_fail());assert_eq!(emitter.pos(),nwords);emitter.code}这就是困难的部分就我个人而言编写汇编是我觉得 AI 最有帮助的地方。我对汇编的主要经验是完成 microcorruption CTF。我自己从未真正编写过汇编。我真的很难弄清楚需要的具体指令以及如何修改它们以获得我想要的输出。有了 AI我可以给我的编码代理coding agent一个 JIT 编译器工作方式的总体框架它就可以为我处理许多这些细节。加载机器码为了完成我们的编译器我们需要实际加载代码。为此我们将使用mmap分配一块可读、可写和可执行的内存。然后我们将代码复制到该内存中并将该内存块转换为一个函数然后调用它constBSTACK_MAX:usize4096;// 这些函数包含在 mac 系统库中unsafeexternC{fnpthread_jit_write_protect_np(enabled:libc::c_int);fnsys_icache_invalidate(start:*mutlibc::c_void,len:libc::size_t);}typeMatchFnunsafeexternCfn(input:*constu8,bstack:*mutu64)-u64;structJit{buf:*mutu32,nbytes:usize,bstack:Vecu64,}implJit{fncompile(regex:Node)-Jit{letnwordsPROLOGUE_WORDSnode_words(regex)MATCH_WORDSFAIL_WORDS;letnbytesnwords*4;unsafe{letbuflibc::mmap(std::ptr::null_mut(),nbytes,libc::PROT_READ|libc::PROT_WRITE|libc::PROT_EXEC,libc::MAP_PRIVATE|libc::MAP_ANON|libc::MAP_JIT,-1,0,)as*mutu32;assert!(bufas*mutlibc::c_void!libc::MAP_FAILED,mmap failed);letcodegenerate_code(regex,bufasu64);pthread_jit_write_protect_np(0);// 使区域可写Apple W^Xstd::slice::from_raw_parts_mut(buf,code.len()).copy_from_slice(code);pthread_jit_write_protect_np(1);// 恢复为可执行sys_icache_invalidate(bufas*mutlibc::c_void,nbytes);Jit{buf,nbytes,bstack:vec![0;BSTACK_MAX*2]}}}// 运行生成的代码。输入必须以 NUL 字节结尾。fnis_match(mutself,nul_terminated:[u8])-bool{debug_assert_eq!(nul_terminated.last(),Some(0));unsafe{letmatcher:MatchFnstd::mem::transmute(self.buf);matcher(nul_terminated.as_ptr(),self.bstack.as_mut_ptr())!0}}}implDropforJit{fndrop(mutself){unsafe{libc::munmap(self.bufas*mutlibc::c_void,self.nbytes);}}}结果完成所有这些后让我们比较我们构建的不同实现的性能输入长度解释器JIT手写JIT 加速比手写加速比945 纳秒3.8 纳秒3.8 纳秒11.7 倍11.9 倍33103 纳秒7.9 纳秒10.5 纳秒13.0 倍9.8 倍129597 纳秒30 纳秒32 纳秒19.7 倍18.6 倍5131,955 纳秒126 纳秒120 纳秒15.5 倍16.2 倍2,0498,301 纳秒470 纳秒393 纳秒17.7 倍21.1 倍所以 JIT 和手写实现基本上并驾齐驱。有时 JIT 版本更快有时手写版本更快。网上流传着一个梗说 AI 没什么用因为“代码从来都不是困难的部分”。我认为这在某些领域是对的但在其他领域编写代码绝对是困难的部分。JIT 编译器就是一个很好的例子。对于许多软件来说JIT 编译器会大大有助于加速代码。JIT 编译器的稀有性让我相信从历史上看实现 JIT 编译器太难了以至于不值得。LLM 降低了准入门槛使得编写 JIT 编译器变得容易得多。这就是 pgrust 背后的理念。数据库历来是最难构建的软件并因此受到限制。现在有了 AI我们可以对我们构建的软件类型更加雄心勃勃。感谢阅读如果你想支持这个项目支持 pgrust 的最好方式是在 GitHub 上给我们一个 star。如果你想持续关注GitHubDiscord邮件列表pgrust.com每周更新 pgrust包括关于 JIT 编译的后续内容。

相关新闻

2011-2024年各省绿色创新绩效综合测算数据集

2011-2024年各省绿色创新绩效综合测算数据集

本数据集围绕中国各省级区域的绿色创新绩效展开综合测算,时间跨度覆盖2011至2024年。该数据集以《科研管理》期刊刊载的研究方法为参照框架,可服务于区域绿色创新绩效时空演化规律的精确度量、环境规制与工业智能化政策效应的实证评估,以及绿…

2026/10/17 7:54:53 阅读更多 →
Java虚拟线程惰性加载机制剖析

Java虚拟线程惰性加载机制剖析

Java虚拟线程惰性加载机制剖析前言Java虚拟线程惰性加载机制1. 物理栈结构与 HotSpot 数据模型切片HotSpot 核心数据结构差异表2. Continuation 挂起机制 (continuationFreezeThaw.cpp) 源码剖析JNI Pinning 机制的 C 源码实现Panama Trivial Downcall 为何完全避免 Pinning3. …

2026/10/17 7:57:50 阅读更多 →
Linux下RAR文件处理全攻略:从安装到实战排错

Linux下RAR文件处理全攻略:从安装到实战排错

1. 从一次文件传输的“事故”说起前几天,一个做设计的朋友火急火燎地找我,说客户发来一个巨大的素材包,后缀是.rar,他在自己的 Mac 上折腾了半天,用系统自带的归档工具打不开,下了几个免费软件也报错&#…

2026/10/17 22:19:33 阅读更多 →

最新新闻

人形机器人产业链白皮书:制造业先行,算法决胜

人形机器人产业链白皮书:制造业先行,算法决胜

简介:2024人形机器人产业链白皮书是一份面向机器人领域研究者、产品经理、投资机构从业者的行业深度资料,内容系统覆盖宏观环境分析、产业链拆解、主流生产企业扫描、技术路线对比与市场投资建议等模块,能够帮助读者快速建立对人形机器人产业…

2026/10/19 2:23:38 阅读更多 →
AI漫剧零基础制作全流程:从剧本到成片的生产链路拆解

AI漫剧零基础制作全流程:从剧本到成片的生产链路拆解

这次我们来看一个非常具体的方向:AI 漫剧的完整生产链路。如果你经常刷短视频平台,应该已经看到过一批 AI 漫剧账号。它们的形态很统一:“动态漫画画面 角色配音 剧情文案”,画面用 Stable Diffusion 系列模型生成二次元风格图像…

2026/10/19 2:23:38 阅读更多 →
跑山零接管不等于自动驾驶:智驾山路场景的技术真相与边界

跑山零接管不等于自动驾驶:智驾山路场景的技术真相与边界

前阵子围绕智能驾驶有一个很有意思的争论:一边是“自动驾驶都是忽悠”的质疑,另一边是各种智驾跑山、城市领航的视频刷屏。小鹏 MONA M03 相关话题里,“跑山零接管登顶”成了一个关键词。单看标题,这像是营销事件;但如…

2026/10/19 2:23:38 阅读更多 →
6.Excel项目排期表

6.Excel项目排期表

文章目录案例讲解案例讲解 项目时长 比较简单&#xff0c;直接两个单元格相减 用结束时间-开始时间 距离结束还剩几天 IF(D22<TODAY(),0,IF(D22TODAY(),“仅剩今天”,D22-TODAY())) 解释&#xff1a;这里采用if去判断还剩几天&#xff0c;如果结束时间<今天&#xff…

2026/10/19 2:23:38 阅读更多 →
基于Dubins路径与候选集的无人机集群协同攻击仿真实现

基于Dubins路径与候选集的无人机集群协同攻击仿真实现

做无人机集群协同的仿真&#xff0c;难点从来不在单机控制&#xff0c;而在多机之间的“协作逻辑”。目标搜索、冲突避免、联盟组建、任务执行&#xff0c;这四个环节单拎出来任何一个&#xff0c;写个Demo都不难&#xff0c;但把它们串成一个闭环&#xff0c;再加进资源分配的…

2026/10/19 2:23:38 阅读更多 →
AI漫剧全流程制作:从剧本到成片的零基础技术路径

AI漫剧全流程制作:从剧本到成片的零基础技术路径

B站有一套 AI 漫剧教程&#xff0c;标题很夸张&#xff1a;全 666 集&#xff0c;198 小时&#xff0c;零基础入门&#xff0c;还允许白嫖。很多人第一反应是标题党&#xff0c;但仔细看它列出的内容板块——剧本、分镜、静帧、视频、配音、剪辑、保持人物一致——这正是目前 A…

2026/10/19 2:22:37 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

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

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/18 12:16:16 阅读更多 →