news 2026/9/17 6:08:57

Highway 示例教程:从数组求和到运行时分发,hwy/examples 实战样本库全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Highway 示例教程:从数组求和到运行时分发,hwy/examples 实战样本库全解

Highway 示例教程:从数组求和到运行时分发,hwy/examples 实战样本库全解

【免费下载链接】highwayPerformance-portable, length-agnostic SIMD with runtime dispatch项目地址: https://gitcode.com/GitHub_Trending/hi/highway

Highway 是一个「性能可移植、长度无关(length-agnostic)的 SIMD 库」,而hwy/examples/目录正是学习它的最佳入口:本仓库用一组由浅入深的可编译示例,覆盖了ScalableTag/FixedTag、循环展开、掩码、交错存储、Gather/Scatter、多线程以及动态分发骨架等全部核心手法。读完本文并跑通这些示例后,你将掌握编写高性能 SIMD 模块的完整套路——从最小求和函数到可复用的 per-target 分发架构,以及如何用 Bazel、CMake 或单个编译器命令编译运行它们。

示例库总览:三级难度的目录结构

hwy/examples/README.md 将目录下的示例分为三类,难度与主题层层递进:

类别文件定位
Introductory(入门)sum_array_simple.ccsum_array_advanced.ccdot_product_unroll.ccdot_product_mixed_precision.ccmandelbrot.ccbaker_mix.ccmatrix_transpose_scatter_gather.ccstream_triad.ccgame_of_life.cc逐个演示一类 SIMD 技巧,适合作为第一份 Highway 代码
Infrastructure(基础设施)benchmark.ccprofiler_example.ccskeleton*skeleton.h/skeleton.cc/skeleton-inl.h/skeleton_test.cc基准测试、内置 profiler、以及带运行时分发的完整模块骨架
Challenge(挑战)masks_and_logic.ccctf_aes.ccASCII 艺术渲染器、AES 暴力破解,综合运用掩码逻辑与硬件加速指令

此外目录中还包含crc64.ccfloat_distribution.ccsum_hex.cc等未在 README 中逐项展开的补充示例。所有示例都遵循 Highway 的统一组织方式:SIMD 代码放在HWY_NAMESPACE内、通过HWY_BEFORE_NAMESPACE()/HWY_AFTER_NAMESPACE()包裹,再用HWY_DYNAMIC_DISPATCHHWY_STATIC_DISPATCHmain中调用。

入门示例一:sum_array_simple —— 最小可用模板

sum_array_simple.cc 是整个目录中最小的完整示例:对一个float数组求和,余数部分用简单的标量循环回退。它浓缩了 Highway 的「标准三段式」——定义描述符 D、标签 d、向量类型 V:

float SumArraySIMD(const float* HWY_RESTRICT array, size_t count) { const hn::ScalableTag<float> d; // 描述符:可伸缩标签,宽度取当前目标最大值 using V = hn::Vec<decltype(d)>; V sum = hn::Zero(d); size_t i = 0; const size_t N = hn::Lanes(d); // 运行时向量宽度(lane 数) if (count >= N) { for (; i <= count - N; i += N) { sum = hn::Add(sum, hn::LoadU(d, array + i)); // 非对齐加载 } } float total = hn::ReduceSum(d, sum); // 横向归约到标量 for (; i < count; ++i) { // 标量处理尾数 total += array[i]; } return total; }

要点解析:

  • ScalableTag<float>表示「每个向量装尽可能多的 float」,实际 lane 数在不同 ISA 下不同(SSE4 下为 4、AVX3 下为 16),因此Lanes(d)返回的是运行期值,代码本身是长度无关的;
  • 使用LoadU(unaligned load)是因为本例未保证数组对齐,这是最保守也最安全的写法;
  • main中通过HWY_STATIC_DISPATCH(SumArraySIMD)(data.data(), count)调用——静态分发直接选中编译时启用的最佳目标,代码量最少,适合单目标部署(见 sum_array_simple.cc)。

入门示例二:sum_array_advanced —— 4 路展开与 LoadN 尾数处理

sum_array_advanced.cc 展示「更高性能」的求和,README 归纳了它相对简单版新增的三点:

  1. 循环展开(展开因子 4):用sum0~sum3四个累加器并行推进,掩盖加法延迟;
  2. LoadN+ 隐式FirstN语义:用带长度限制的加载处理尾数,替代标量回退,LoadN会把超出remainder的高位 lane 填 0;
  3. 运行时校验:对 float 与 int32 分别用标量实现做 100 000 次重复对照。

其核心结构(sum_array_advanced.cc)体现了 Highway 推荐的余数处理范式:

// Unroll by 4 to mask latency of adds. if (count >= 4 * N) { for (; i <= count - 4 * N; i += 4 * N) { sum0 = hn::Add(sum0, hn::Load(d, array + i)); sum1 = hn::Add(sum1, hn::Load(d, array + i + N)); sum2 = hn::Add(sum2, hn::Load(d, array + i + 2 * N)); sum3 = hn::Add(sum3, hn::Load(d, array + i + 3 * N)); } } // 剩余整向量用 Duff's device 风格的 switch-fallthrough 消化 size_t num_vectors = (count - i) / N; switch (num_vectors) { case 3: ... [[fallthrough]]; case 2: ... [[fallthrough]]; case 1: ... [[fallthrough]]; case 0: break; } // 最后不足一个向量的部分交给 LoadN V loaded_vec = hn::LoadN(d, array + i, remainder); sum3 = hn::Add(sum3, loaded_vec);

两个细节值得注意:

  • 展开主循环使用了对齐版Load(而非LoadU),因为该示例配合hwy::AlignedVector分配内存,注释明确写了 “We know memory is aligned, so we can use Load instead of LoadU”;
  • 只调用一次ReduceSum——注释指出归约「uses shuffles」,代价较高,所以先向量级累加再归约一次。

HWY_ONCE块中则演示了完整工程闭环:HWY_EXPORT_T(SumArrayTable, SumArraySIMD<T>)建立类型化分发表,HWY_DYNAMIC_DISPATCH_T(SumArrayTable)在运行期调用「最宽」目标实现;数据填充使用FillBytes+hwy::Unpredictable1()防止编译器把循环优化掉(注释也提醒该函数较昂贵,应少用);浮点结果按1e-1容差校验。计时用hwy::platform::Now(),并直接打印Speedup: x.x x的标量/SIMD 对比。

入门示例三:dot_product_unroll 与混合精度点积

dot_product_unroll.cc 结构上与sum_array_advanced相同(4 路展开 + switch 尾数 +LoadN收尾),但把加法换成了融合乘加MulAdd

sum0 = hn::MulAdd(hn::Load(d, array1 + i), hn::Load(d, array2 + i), sum0);

MulAdd(a, b, c)对应多数 ISA 上的 FMA 指令(a*b + c),在浮点场景下既减少指令数又降低舍入次数;测试部分额外覆盖了double(由HWY_HAVE_FLOAT64宏保护)和int32,并用随机数FillRandom生成输入。

dot_product_mixed_precision.cc 演示 README 所说的类型提升(type promotion):对整型点积,先把窄类型向量提升(promote)到更宽的类型再累加,从而在溢出前获得更大的求和值域——这是处理窄类型归约的典型手段。

图形学示例:mandelbrot 的交错存储、掩码与 FMA

mandelbrot.cc 是一个 512×512 网格的 Mandelbrot 类迭代计算(README 列出它覆盖:交错存储、掩码、循环展开、融合乘加)。源码中的工程选择很能说明问题:

  • 为彻底避免尾数处理,断言x_points/y_points为 512 的倍数(HWY_ASSERT(size_t{0} == x_points % NF));
  • 每轮迭代并行推进 4 组迭代点(ij += 4 * NF内调用 4 次CalculateNextSimd),用NegMulAdd/MulSub/MulAdd拼出复杂迭代公式,即 FMA 的密集使用;
  • 逃逸判定用布尔掩码Gt比较生成mask_escaped,再与「此前未逃逸」掩码And组合,最后用MaskedSetOr把新颜色只写入逃逸点——完全避免按 lane 分支;
  • 输出 PPM 文件时,把分离的 R/G/B 平面用StoreInterleaved3交错写成连续的 RGB 像素流。

程序同时跑标量与 SIMD 两套实现,写出mandelbrot_scalar.ppmmandelbrot_simd.ppm,并用归一化差值< 0.01校验两者一致(mandelbrot.cc)。

数据重排示例:baker_mix 与矩阵转置

baker_mix.cc 演示 Baker's map 风格的数组置换,README 点名的技巧在源码中一一对应:

  • Setuphn::Iota(du32, i)生成从i开始的连续整数向量并StoreU落盘——这是向量化生成有序序列的惯用法;
  • LocalMixLowerHalf/UpperHalf把向量拆上下半区,再StoreInterleaved2(upper, lower, ...)交错写回,实现向量内部的「洗牌」;
  • Diffuse演示跨向量移动:先SlideUpLanes预取头部,循环中用SlideUpLanes+SlideDownLanesOr把相邻向量内容拼接移动(Or变体保证被移出的 lane 用旧值补齐);
  • 结尾调用hn::Print(du32, "\nLast diffused vector\n", vec)把向量内容打到 stderr,是调试向量数据的标准工具;
  • 注意#if HWY_TARGET != HWY_SCALAR分支:依赖UpperHalf的函数在标量目标下退化为空实现,这是多目标共存的典型写法。

matrix_transpose_scatter_gather.cc 则用非连续访存实现转置:GatherIndex/ScatterIndex按寄存器中的索引向量从分散地址批量加载/存储;尾数部分用LoadN/StoreNGatherIndexN/ScatterIndexN;步长偏移不查表、不重复乘法,而是预先用Iota生成索引再Mul出一个偏移向量,全程驻留寄存器。

其余两个入门示例:stream_triad.cc 是经典的c[i] = a[i] + s * b[i]流式计算,README 特别强调它演示了多线程划分(配合hwy/contrib/thread_pool使用);game_of_life.cc 是模板计算(stencil),展示Slide1UpOr/Slide1DownOr实现「上/下邻居 + 自身」的模板取值,以及布尔掩码做生老病死判定。

基础设施示例:benchmark、profiler 与 skeleton 模块骨架

benchmark.cc—— benchmark.cc 提供点积与 delta 编码的基准测试,集成 Highway 内置的hwy/nanobenchmark.h微基准框架与hwy/aligned_allocator.h对齐分配,是观察「SIMD 相对标量收益」的现成模板。

profiler_example.cc—— profiler_example.cc 演示内置 profiler:在标注的代码区间(zone)内计时,并自动扣除嵌套子区间的时间,适合定位热点(对应 hwy/profiler.h 的 API)。

skeleton 三件套—— 这是整个目录中最重要的架构级示例,README 称其为「一个完整支持运行时分发的模块示例,外加一个『per-target 头文件』,用于把 SIMD 内联进多个 .cc 文件」。三个文件各承担一种角色:

  • skeleton.h:普通头文件,只声明与指令集无关的公共接口CallFloorLog2/SavedCallFloorLog2,用HWY_DLLEXPORT标记导出符号;
  • skeleton.cc:动态分发的全部机制集中在这一个.cc中,按顺序包含关键要素:
#include "hwy/examples/skeleton.h" // 动态分发专用:告诉 foreach_target.h 需要重复包含哪个源文件 #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "hwy/examples/skeleton.cc" #include "hwy/foreach_target.h" // IWYU pragma: keep #include "hwy/highway.h" // 必须放在 foreach_target.h 之后 HWY_BEFORE_NAMESPACE(); namespace skeleton { namespace HWY_NAMESPACE { // 每个目标唯一命名空间,多目标代码可共存 ... // 每目标编译一次的 SIMD 实现(FloorLog2 等) } // namespace HWY_NAMESPACE } // namespace skeleton HWY_AFTER_NAMESPACE(); #if HWY_ONCE // 仅在第一遍(某单一目标)编译时成立 namespace skeleton { HWY_EXPORT(FloorLog2); // 生成分发表 HWY_DLLEXPORT void CallFloorLog2(...) { return HWY_DYNAMIC_DISPATCH(FloorLog2)(in, count, out); // 运行时选最优目标 } HWY_DLLEXPORT void SavedCallFloorLog2(...) { const auto ptr = HWY_DYNAMIC_POINTER(FloorLog2); // 存函数指针避免重复查表 return ptr(in, count, out); } } // namespace skeleton #endif // HWY_ONCE

源码注释还给出了按目标选择代码路径的方法:#if HWY_TARGET == HWY_SSE4或能力宏HWY_HAVE_INTEGER64,并用hwy::TargetName(HWY_TARGET)打印当前目标。

  • skeleton-inl.h:per-target 头文件。当 SIMD 函数需要被多个.cc内联引用(而非经函数指针调用)时使用。它的头文件保护写法很特别,是动态分发下的必需品:
// 当「已包含标记」与 HWY_TARGET_TOGGLE 定义状态不一致时才处理 // —— 即每个目标恰好重新包含一次 #if defined(HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_) == defined(HWY_TARGET_TOGGLE) #ifdef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_ #undef HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_ #else #define HIGHWAY_HWY_EXAMPLES_SKELETON_INL_H_ #endif

其中MulAddLoop展示了「类型无关 + 宽度无关」函数签名:以const D d为参数推导向量类型,同一份代码在每个目标上都能内联生效。skeleton_test.cc 则验证了CallFloorLog2的输出与标量参考一致。

挑战示例:掩码逻辑与硬件 AES

masks_and_logic.cc 是一个 ASCII 艺术渲染器,README 列出其覆盖点:寄存器内分支与掩码、掩码布尔运算(AndAndNot)、用连续IfThenElse表达嵌套条件,以及用带HWY_ATTR标记的 lambda 封装 SIMD 操作——这让多目标函数能以泛型方式被调用。

ctf_aes.cc 是暴力猜密码程序,展示:

  • FixedTag:与硬件最大宽度无关的定长向量(固定 N 个 lane),适合 AES 这类要求固定 128-bit 语义的算法;
  • 可移植的硬件加速 AES 轮函数AESRound(在支持 AES-NI 的目标上落到指令,否则软件实现);
  • FirstN生成前 n 个 lane 为真的掩码,配合MaskedEq做掩码化比较,从而一次判定多个候选密码。

如何运行:Bazel / CMake / 单编译器三种方式

README 的 “How to Run” 给出三条路径,这里原样保留并补充实测要点。

使用 Bazel

在 highway 仓库根目录下(其余目标名见根 BUILD 文件):

bazel run //::sum_array_simple bazel run //::sum_array_advanced # etc; see BUILD file for the other build targets

使用 CMake

CMake 构建中示例默认开启:CMakeLists.txt 中set(HWY_ENABLE_EXAMPLES ON CACHE BOOL "Build examples"),各可执行目标(如sum_array_simple,链接hwy与原子库)定义在 CMakeLists.txt 起的HWY_ENABLE_EXAMPLES分支内。从 highway 根目录:

mkdir build && cd build cmake .. -DHWY_ENABLE_EXAMPLES=ON make ./examples/sum_array_simple ./examples/sum_array_advanced # etc; see CMakeLists.txt for the other build targets

使用 Clang 直接编译

不借助构建系统时,只需把示例源文件与 Highway 的几个运行时支持.cc一起编译(在 highway 根目录下):

clang++ -std=c++17 -O3 -I. hwy/examples/sum_array_simple.cc hwy/targets.cc hwy/per_target.cc hwy/print.cc hwy/abort.cc hwy/aligned_allocator.cc -o sum_array_simple ./sum_array_simple

使用 GCC 直接编译

g++ -std=c++17 -O3 -I. hwy/examples/sum_array_simple.cc hwy/targets.cc hwy/per_target.cc hwy/print.cc hwy/abort.cc hwy/aligned_allocator.cc -o sum_array_simple ./sum_array_simple

注意:README 提醒g++可能输出类似no SFrame FDE emitted的汇编器告警,这些是无害的,可以忽略。

几个适用于上述所有方式的适用前提:

  • 编译参数需 C++17(-std=c++17)与优化(-O3),-I.使#include "hwy/highway.h"相对仓库根目录解析;
  • 需要动态分发的示例(带HWY_TARGET_INCLUDE+foreach_target.h,如sum_array_advanced.ccbaker_mix.cc)必须编译进hwy/targets.cchwy/per_target.cc;而仅用HWY_STATIC_DISPATCHsum_array_simple.cc依赖更少,这也是 README 直接编译示例选它的原因;
  • 需要Print的示例(如baker_mix.cc)额外链接hwy/print.cc;使用AlignedVector的示例需要hwy/aligned_allocator.cc

小结

hwy/examples/目录实质上是一条完整的学习路径:sum_array_simple建立「D/d/V + 循环 + 归约」的基本功;sum_array_advanceddot_product_unroll补上展开、LoadN尾数与 FMA 的性能要素;mandelbrotbaker_mixmatrix_transpose_scatter_gathergame_of_lifestream_triad分别覆盖掩码、数据重排、Gather/Scatter、模板计算与多线程;skeleton系列则给出可直接套用的多目标分发模块架构。建议按「先读源码、再用 CMake 一键构建、最后用单条g++/clang++命令手动编译验证依赖」的顺序推进,配合 hwy/highway.h 的 API 与 g3doc/tutorial.md 的教程文档,即可在任意支持 Highway 的目标架构上编写并验证自己的 SIMD 代码。

【免费下载链接】highwayPerformance-portable, length-agnostic SIMD with runtime dispatch项目地址: https://gitcode.com/GitHub_Trending/hi/highway

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 6:05:52

用python-pptx解析PPTX:SMART目标校验与任务排期巡检

简介&#xff1a;这是一份面向团队管理者、项目负责人及培训人员的《团队目标管理》PPT课件&#xff0c;围绕目标设定与落地执行展开&#xff0c;适合内部培训、管理入门或团队复盘参考。课件从彼得杜拉克的目标观切入&#xff0c;梳理团队目标的三大作用&#xff0c;剖析目标模…

作者头像 李华
网站建设 2026/9/17 6:05:51

Linux 安装 VS Code 与项目运行配置全指南

Linux 安装 VS Code 这件事&#xff0c;看起来是一条命令的事&#xff0c;但真正在团队里带新人时&#xff0c;我发现十个人里有八个会在同一个地方卡住——要么是装了个版本落后的发行版仓库包&#xff0c;要么是装完不知道code命令为什么敲不出来&#xff0c;要么是环境跑通了…

作者头像 李华
网站建设 2026/9/17 6:05:38

LabVIEW UDS刷写Main.vi:状态机编排与图莫斯协议栈协同设计

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

作者头像 李华
网站建设 2026/9/17 6:05:22

字符串组成问题解析:哈希表与排序法实战

1. 字符串组成问题解析&#xff1a;从入门到精通字符串组成问题在技术面试中出现的频率高达78%&#xff08;根据2023年算法面试题库统计&#xff09;&#xff0c;这类问题看似简单却暗藏玄机。作为面试官最爱的考察点之一&#xff0c;它不仅能检验候选人对基础数据结构的掌握程…

作者头像 李华
网站建设 2026/9/17 6:04:37

Python京东抢购脚本技术解析:虚拟环境、Cookie管理与异步高并发

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

作者头像 李华
网站建设 2026/9/17 6:03:37

可编程直流电源在光模块测试中的关键作用与AT66333A应用指南

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

作者头像 李华