Halide 调度指南(第 15 章):Storage Levels —— 用 `store_at` / `store_root` 与滑动窗口优化内存与带宽
编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载导读本篇技术指南围绕 Halide 调度体系中最关键也最容易被忽视的一环展开存储层级Storage Levels。在 Halide 中在哪里计算compute level与在哪里分配缓冲区store level本是两件事而store_at、store_root正是把二者拆开的核心指令。读完本文你将掌握store_at/store_root/hoist_storage/store_in四个指令的语义与用法理解 Halide 滑动窗口sliding window优化背后的内存复用机制并能结合print_loop_nest()输出的store节点判断调度是否生效。本文是 Halide 调度指南 系列中 第 15 章 Storage Levels 的深度展开相关实操模式可配合 第 10 章 Recipes 一起阅读。一、从 compute level 到 store level把计算位置与存储位置解耦在 Halide 中一个 Func 的调度层级placement同时决定了两件事在哪里计算它compute level以及在哪里分配它的存储store level即缓冲区分配的循环层级。默认情况下store level 与 compute level 是重合的——例如f.compute_at(g, var)会在g的var循环内既计算f又分配f的存储。但这两件事本质上是可以分离的。f.store_at(g, v)把f的存储分配放在g的v循环处而f.store_root()把存储放到最外层root。这两种指令只改变存储分配的位置不移动任何计算这正是 Halide 滑动窗口优化的基础。从源码看这些指令在 src/Func.h 中均有对应的重载与文档Func store_at(const Func f, const Var var); // 在 f 的 var 循环内分配存储 Func store_at(const Func f, const RVar var); // 支持以 RDom 维度为站点 Func store_at(LoopLevel loop_level); // 以 LoopLevel 形式指定 Func store_root(); // 分配到最外层循环注意store_at的调用形式与compute_at完全对称第一个参数是宿主 Func第二个参数是宿主 Func 的某个Var或RVar。你也可以用LoopLevel形式精确指定站点。二、为什么要拆分滑动窗口的内存折叠拆分 compute/store 的动机非常朴素在内层循环计算、在外层循环存储可以让内层某次迭代算出的值在后续迭代中被复用。这就是 Halide 的滑动窗口优化——它让缓冲区从整块完整分配折叠成小而滚动的形态。这带来两个直接效果减少了重复计算相邻迭代共享的中间结果只计算一次大幅缩小了缓冲区尺寸以行扫描为例原本要保存整幅图像的中间数据现在只需保存少数几行。需要强调的是store_at改变的是哪些值被重算、缓冲区有多大它并不改变 produce/consume/for的整体结构。从print_loop_nest()的角度看它唯一的可见效果是多出一个store节点。三、读懂print_loop_nest()中的store节点print_loop_nest()是验证调度是否生效的首要工具详见 第 12 章 Reading a Loop Nest。关于store节点的规则仅当f的 store level 与 compute level 不一致时才会打印store f:当二者一致默认情况也包括store_root().compute_root()的组合时没有store行一旦出现store f:位于 store level其下包裹着从该层到f的produce/consumecompute level之间的所有内容produce/consume以及每一个for循环的位置与单独使用compute_at时完全一致store_at只是在其外层额外加上store g:这一层。来看原文档中的经典示例g在output的内层循环x处计算但存储分配在output的外层循环y处Var x(x), y(y); ImageParam in(type_ofuint8_t(), 2, in); Func g(g); g(x, y) in(x, y); Func output(output); output(x, y) g(x, y) g(x 1, y) g(x, y 1) g(x 1, y 1); g.compute_at(output, x).store_at(output, y); output.print_loop_nest();打印结果produce output: for y: store g: # 位于 store leveloutput 的 y 循环 for x: # store level 与 compute level 之间的循环 produce g: # 位于 compute leveloutput 的 x 循环 for y: for x: g(...) ... consume g: output(...) ...几个细节值得注意store g:出现在for y:内部、for x:外部表示缓冲区在整个y循环内只分配一次store节点与produce/consume一样按 site-func 的每个 stage 分别跟随只出现在实际计算f的 stage 中store_root()则把节点放在最外层包裹整个 pipeline 主体。四、滑动窗口实战可分离模糊separable blurstore/compute 拆分是 stencil 滑动窗口的引擎。以可分离模糊为例把生产者producer存储在 strip 层每次迭代只计算一条扫描线producer.store_at(out, yo) // 每个 strip 只分配一次存储 .compute_at(out, yi); // 每次迭代只计算一条扫描线Halide 会为每个 strip 维护一个小型滚动缓冲区rolling buffer of scanlines。每个新的yi迭代只计算新需要的行旧行在缓冲区中滚动复用。若想强制使用固定大小的环形缓冲区追加.fold_storage(y, K)其中K为槽位数量——对应 src/Func.h 中的声明Func fold_storage(const Var dim, const Expr extent, bool fold_forward true);完整的滑动窗口 分块 并行 向量化配方见 第 10 章 Recipesconst int vec natural_vector_sizefloat(); Var yo, yi; out.split(y, yo, yi, 32).parallel(yo).vectorize(x, vec); producer.store_at(out, yo) // 在 strip 层分配 .compute_at(out, yi) // 每次计算一条扫描线 .vectorize(x, vec);滑动窗口的实测证据仓库中的 test/correctness/sliding_window.cpp 对滑动窗口做了大量行为验证。例如最基础的一维滑动f(x) call_counter(x, 0)g(x) f(x) f(x - 1)调度为f.store_root().compute_at(g, x)对 100 个像素 realize 后断言f只被调用101 次而不是每个输出点都重算见该文件第 26-44 行f(x) call_counter(x, 0); g(x) f(x) f(x - 1); f.store_root().compute_at(g, x).store_in(store_in); // ... Bufferint im g.realize({100}); // f should be able to tell that it only needs to compute each value once if (count ! 101) { ... }该测试还覆盖了多生产者共享消费者、两级滑动窗口级联、含归约reduction的滑动、多维滑动、矢量化的滑动窗口、固定 footprint 时只在首次迭代计算全部值count 6、每三次迭代才需要新值count 34等场景——它们共同证明了 store/compute 拆分确实只重算必要的新值。一个易错点同一 var 上的store_at(f, v).compute_at(f, v)等于什么都没做store_at(f, v).compute_at(f, v)站点 var 相同与单独compute_at(f, v)完全等价。store_at只有在严格外于compute_at的循环时才有意义。五、hoist_storage只调整分配位置不开启滑动窗口第三个层级是hoist-storage level提升存储层级由f.hoist_storage(g, v)或f.hoist_storage_root()设置。它与store_at的本质区别见 src/Func.h 的注释hoist_storage simply moves an actual allocation to a given loop level and doesnt trigger any of the optimizations such as sliding window.即store_at把存储分配外移并开启滑动窗口式的值复用hoist_storage只把实际的内存分配动作alloc/free进一步外移以跳过在循环内部反复重新分配不开启滑动窗口复用默认情况下 hoist-storage level 与 store level 重合。打印输出中不可见hoist_storage在print_loop_nest()的输出中是不可见的——带hoist_storage的调度打印出的 loop nest 与不带它的完全一样。它只影响分配位置与缓冲区尺寸。典型用法想获得细粒度compute_at的新鲜感局部性又不想每轮迭代都付出一次 alloc/free 的开销f.compute_at(g, inner).hoist_storage(g, parallel_var);并行循环的红色警戒线只能把存储提升到包含并行循环的那层循环绝不能越过它。如果g是parallel(yo)则分配必须留在yo内部保证每个线程拥有自己的缓冲区一旦把分配提升到并行循环之上缓冲区就变成了所有线程的共享状态产生数据竞争race。因此hoist_storage_root()只有在没有任何外层循环是 parallel时才是安全的从源码角度看这一约束体现在 test/correctness/hoist_storage.cpp 的大量子场景中。该测试通过自定义custom_malloc/custom_free精确断言 malloc 次数与总大小例如f.compute_at(g, x).hoist_storage(g, Var::outermost())对 128×128 输出仅分配1 次、大小为3*3*sizeof(int32_t)第 28-70 行证明分配被提升到了最外层而把hoist_storage设在yo分块外层时malloc 次数变为8第 213-257 行——即每个 tile 一行各分配一次lifting after sliding window场景第 466-511 行证明store_at与hoist_storage可以叠加使用分配大小被折叠为4*3*sizeof(int32_t)。这些断言清晰地展示了 hoist 层级对分配次数、分配大小的量化影响。六、合法性规则Legalitystore_at、hoist_storage相关调度有明确的合法性约束违反会在 realize/compile 时抛错store level 必须包含enclosecompute level二者可以是同一循环或 store 在外层把存储放在 compute 循环内部是非法调度。设置了 store level 的 Func 必须有一个非内联的 compute level对内联inlineFunc 调用store_at、store_root或hoist_storage是非法的必须先给它显式的compute_at或compute_root。仓库中的 test/correctness/store_at_without_compute_at.cpp 正是这条规则的回归测试其期望的错误信息为Func g is scheduled store_at(), but is inlined. Funcs that use store_at must also call compute_at.见该文件第 8-19 行的error_test用法。hoist-storage level 必须包含 store levelstore level 必须包含 compute level把存储提升到 compute 循环内部的循环是非法的。store level与 compute level 一样必须包含f的所有使用点use这与 第 14 章 中 compute level 的所选层级必须包含对f的每一次读取这一合法性总原则一致。七、存储位置store_in与 MemoryTypef.store_in(MemoryType::Stack)把存储分配在栈上src/Func.h。它的特点速度快但只适用于小尺寸的定长分配这是**存储类型memory type**的选择与 store level 相互独立它不会改变print_loop_nest()的输出。仓库测试中常见的取值还有MemoryType::Heap堆与MemoryType::Register寄存器例如 sliding_window.cpp 对两种类型分别验证滑动窗口行为在寄存器滑动场景下两级滑动级联的期望调用次数为 103 而非 102见该文件第 78-83 行。MemoryType::Auto则由编译器自动决策。完整的调度指令速查表见 第 20 章 Directive Reference。八、小结三层存储语义速查指令作用影响 loop nest 输出是否开启滑动窗口f.store_at(g, v)把f的存储分配放到g的v循环增加store f:节点是f.store_root()把存储分配到最外层增加最外层的store f:是f.hoist_storage(g, v)/f.hoist_storage_root()只外移实际 alloc/free不改计算不可见否f.fold_storage(var, K)固定 K 槽的环形缓冲区不改变结构配合使用f.store_in(MemoryType::Stack)栈上分配小定长不可见否一句话总结compute level 决定算多少、何时算store level 决定存多久、存多大hoist-storage level 决定在哪 alloc/free。把三者分开调度正是 Halide 在保持局部性的同时压缩内存带宽占用的核心手段。下一步可以继续阅读 第 16 章 Reshaping Loops 与 第 17 章 Loop Types把存储层级与循环形状、循环类型parallel/vectorized组合出完整的实战调度。赞分享编译器图像处理编程语言高性能计算【免费下载链接】Halidea language for fast, portable>项目地址https://gitcode.com/gh_mirrors/ha/Halide点击查看免费下载相关推荐pydictor工具集详解合并、去重、比较、统计与筛选工具pydictor工具集详解合并、去重、比较、统计与筛选工具 pydictor是一款强大的黑客字典生成工具专为暴力破解攻击设计。本文将详细介绍其工具集中的合并isle-portable性能调优内存带宽与缓存优化技巧isle portable性能调优内存带宽与缓存优化技巧 在LEGO Island 1997 的现代化项目isle portable中内存带宽和缓存优化是提突破同步瓶颈Syncthing内存与带宽深度优化指南突破同步瓶颈Syncthing内存与带宽深度优化指南 引言同步困境与性能瓶颈 你是否遇到过Syncthing同步大型文件夹时内存占用飙升至GB级别是否在家网络通信存储上一篇如何快速上手 PilotDeck从白皮书生成到小游戏开发的4个实战案例下一篇Lorien 版本演进深度解析从 v0.1.0 到 v0.7.0 的无限画布应用迭代全记录创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Lorien 版本演进深度解析:从 v0.1.0 到 v0.7.0 的无限画布应用迭代全记录

Lorien 版本演进深度解析:从 v0.1.0 到 v0.7.0 的无限画布应用迭代全记录

桌面应用图形学 【免费下载链接】Lorien Infinite canvas drawing/whiteboarding app for Windows, Linux and macOS. Made with Godot. 项目地址: https://gitcode.com/gh_mirrors/lo/Lorien 点击查看 免费下载 本文以 Lorien 官方 docs/changelog.md 为核心骨架&…

2026/10/12 23:14:51 阅读更多 →
运算符重载该写成成员还是非成员

运算符重载该写成成员还是非成员

运算符重载最容易被忽略的一个决策是:这个函数写在类里还是写在类外。看起来只是风格问题,实际上它直接决定了表达式的对称性:同一个 ,写成成员时 2.0 v 编译不过,写成非成员就两边都能用。这篇把「谁该写在哪」讲到底…

2026/10/12 23:14:42 阅读更多 →
Pixie 的 Arcanist Linter 扩展:基于 .arclint 的多语言代码检查体系配置指南

Pixie 的 Arcanist Linter 扩展:基于 .arclint 的多语言代码检查体系配置指南

可观测性云原生 【免费下载链接】pixie Instant Kubernetes-Native Application Observability 项目地址: https://gitcode.com/gh_mirrors/pixie/pixie 点击查看 免费下载 导读 本指南以仓库 tools/arc_addons/js/README.md 为骨架,系统讲解 Pixie 仓…

2026/10/12 23:13:58 阅读更多 →

最新新闻

React脚手架原理与选型指南:从Vite到工程化落地

React脚手架原理与选型指南:从Vite到工程化落地

聊到React项目的起步阶段,脚手架这几个字几乎每次都会被摆上台面。它不是一个简单的配置文件,而是把一整套前端工程化底座直接端到你面前:开发服务器、热更新、构建打包、代码规范、环境变量管理,这些原本要折腾一周的事情&#x…

2026/10/13 21:58:43 阅读更多 →
Agent Skill实战指南:从零构建可复用的AI助手能力模块

Agent Skill实战指南:从零构建可复用的AI助手能力模块

1. 从"每次都要重新教"说起:Agent Skill到底在解决什么如果你用过任何一款AI助手,大概率经历过这样的场景:你花十分钟跟它解释"帮我整理会议纪要时,要按项目分组,每个项目下列出待办事项、负责人和截止…

2026/10/13 21:58:43 阅读更多 →
React脚手架深度解析:从CRA到Vite的工程化进阶与踩坑指南

React脚手架深度解析:从CRA到Vite的工程化进阶与踩坑指南

做前端的,几乎没人能绕开脚手架上手这道坎。最近带了个新人,第一天问他环境起来没有,他说“起好了,但不知道起好了啥”。这个回答其实特别真实,因为现在跑一条npx create-react-app my-app,几十秒后一个能跑…

2026/10/13 21:58:43 阅读更多 →
基于Spring Boot的交通事故档案管理系统设计与实现

基于Spring Boot的交通事故档案管理系统设计与实现

1. 项目定位与整体架构设计拆解1.1 为什么选Java Springboot做档案管理系统先聊点实在的。很多同学做毕业设计,第一反应是选个看起来“高级”的技术栈,比如Python的Django、Flask,或者Node.js的Express。但我个人见过太多案例,最…

2026/10/13 21:58:43 阅读更多 →
英语学习真的需要思考:从机械记忆到深度认知的进阶方法

英语学习真的需要思考:从机械记忆到深度认知的进阶方法

我们先说句实话:我见过太多人把英语学成了“体力活”。单词书翻烂了三本,语法题刷了上千道,朗读打卡坚持了一年,结果遇到老外还是张不开嘴,读文章还是逐字翻译。问题出在哪?不是不努力,而是努力…

2026/10/13 21:58:43 阅读更多 →
从AtCoder一题看浮点数精度陷阱与整数解法

从AtCoder一题看浮点数精度陷阱与整数解法

做题时间长了以后,我越来越确信一件事:真正让人当场翻车的,往往不是那些看半天都没思路的难题,而是所有步骤都“看起来没问题”的简单题。AtCoder Beginner Contest 169 的 C 题 Multiplication 3,就是一个摆在台面上的…

2026/10/13 21:57:43 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →