ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

Highway 示例教程:从数组求和到运行时分发,hwy/examples 实战样本库全解 Highway 示例教程从数组求和到运行时分发hwy/examples 实战样本库全解【免费下载链接】highwayPerformance-portable, length-agnostic SIMD with runtime dispatch项目地址: https://gitcode.com/GitHub_Trending/hi/highwayHighway 是一个「性能可移植、长度无关length-agnostic的 SIMD 库」而hwy/examples/目录正是学习它的最佳入口本仓库用一组由浅入深的可编译示例覆盖了ScalableTag/FixedTag、循环展开、掩码、交错存储、Gather/Scatter、多线程以及动态分发骨架等全部核心手法。读完本文并跑通这些示例后你将掌握编写高性能 SIMD 模块的完整套路——从最小求和函数到可复用的 per-target 分发架构以及如何用 Bazel、CMake 或单个编译器命令编译运行它们。示例库总览三级难度的目录结构hwy/examples/README.md 将目录下的示例分为三类难度与主题层层递进类别文件定位Introductory入门sum_array_simple.cc、sum_array_advanced.cc、dot_product_unroll.cc、dot_product_mixed_precision.cc、mandelbrot.cc、baker_mix.cc、matrix_transpose_scatter_gather.cc、stream_triad.cc、game_of_life.cc逐个演示一类 SIMD 技巧适合作为第一份 Highway 代码Infrastructure基础设施benchmark.cc、profiler_example.cc、skeleton*skeleton.h/skeleton.cc/skeleton-inl.h/skeleton_test.cc基准测试、内置 profiler、以及带运行时分发的完整模块骨架Challenge挑战masks_and_logic.cc、ctf_aes.ccASCII 艺术渲染器、AES 暴力破解综合运用掩码逻辑与硬件加速指令此外目录中还包含crc64.cc、float_distribution.cc、sum_hex.cc等未在 README 中逐项展开的补充示例。所有示例都遵循 Highway 的统一组织方式SIMD 代码放在HWY_NAMESPACE内、通过HWY_BEFORE_NAMESPACE()/HWY_AFTER_NAMESPACE()包裹再用HWY_DYNAMIC_DISPATCH或HWY_STATIC_DISPATCH在main中调用。入门示例一sum_array_simple —— 最小可用模板sum_array_simple.cc 是整个目录中最小的完整示例对一个float数组求和余数部分用简单的标量循环回退。它浓缩了 Highway 的「标准三段式」——定义描述符 D、标签 d、向量类型 Vfloat SumArraySIMD(const float* HWY_RESTRICT array, size_t count) { const hn::ScalableTagfloat d; // 描述符可伸缩标签宽度取当前目标最大值 using V hn::Vecdecltype(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; }要点解析ScalableTagfloat表示「每个向量装尽可能多的 float」实际 lane 数在不同 ISA 下不同SSE4 下为 4、AVX3 下为 16因此Lanes(d)返回的是运行期值代码本身是长度无关的使用LoadUunaligned load是因为本例未保证数组对齐这是最保守也最安全的写法main中通过HWY_STATIC_DISPATCH(SumArraySIMD)(data.data(), count)调用——静态分发直接选中编译时启用的最佳目标代码量最少适合单目标部署见 sum_array_simple.cc。入门示例二sum_array_advanced —— 4 路展开与 LoadN 尾数处理sum_array_advanced.cc 展示「更高性能」的求和README 归纳了它相对简单版新增的三点循环展开展开因子 4用sum0~sum3四个累加器并行推进掩盖加法延迟LoadN 隐式FirstN语义用带长度限制的加载处理尾数替代标量回退LoadN会把超出remainder的高位 lane 填 0运行时校验对 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)); } } // 剩余整向量用 Duffs 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, SumArraySIMDT)建立类型化分发表HWY_DYNAMIC_DISPATCH_T(SumArrayTable)在运行期调用「最宽」目标实现数据填充使用FillByteshwy::Unpredictable1()防止编译器把循环优化掉注释也提醒该函数较昂贵应少用浮点结果按1e-1容差校验。计时用hwy::platform::Now()并直接打印Speedup: x.x x的标量/SIMD 对比。入门示例三dot_product_unroll 与混合精度点积dot_product_unroll.cc 结构上与sum_array_advanced相同4 路展开 switch 尾数 LoadN收尾但把加法换成了融合乘加MulAddsum0 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 的交错存储、掩码与 FMAmandelbrot.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.ppm与mandelbrot_simd.ppm并用归一化差值 0.01校验两者一致mandelbrot.cc。数据重排示例baker_mix 与矩阵转置baker_mix.cc 演示 Bakers map 风格的数组置换README 点名的技巧在源码中一一对应Setup用hn::Iota(du32, i)生成从i开始的连续整数向量并StoreU落盘——这是向量化生成有序序列的惯用法LocalMix用LowerHalf/UpperHalf把向量拆上下半区再StoreInterleaved2(upper, lower, ...)交错写回实现向量内部的「洗牌」Diffuse演示跨向量移动先SlideUpLanes预取头部循环中用SlideUpLanesSlideDownLanesOr把相邻向量内容拼接移动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/StoreN与GatherIndexN/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.hper-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的输出与标量参考一致。挑战示例掩码逻辑与硬件 AESmasks_and_logic.cc 是一个 ASCII 艺术渲染器README 列出其覆盖点寄存器内分支与掩码、掩码布尔运算And、AndNot、用连续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使用 CMakeCMake 构建中示例默认开启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_EXAMPLESON make ./examples/sum_array_simple ./examples/sum_array_advanced # etc; see CMakeLists.txt for the other build targets使用 Clang 直接编译不借助构建系统时只需把示例源文件与 Highway 的几个运行时支持.cc一起编译在 highway 根目录下clang -stdc17 -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 -stdc17 -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的汇编器告警这些是无害的可以忽略。几个适用于上述所有方式的适用前提编译参数需 C17-stdc17与优化-O3-I.使#include hwy/highway.h相对仓库根目录解析需要动态分发的示例带HWY_TARGET_INCLUDEforeach_target.h如sum_array_advanced.cc、baker_mix.cc必须编译进hwy/targets.cc、hwy/per_target.cc而仅用HWY_STATIC_DISPATCH的sum_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_advanced与dot_product_unroll补上展开、LoadN尾数与 FMA 的性能要素mandelbrot、baker_mix、matrix_transpose_scatter_gather、game_of_life、stream_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),仅供参考
返回列表