用遗传算法自动搜索 Yul 优化器最佳优化步骤序列:Solidity yul-phaser 工具实战指南
用遗传算法自动搜索 Yul 优化器最佳优化步骤序列Solidity yul-phaser 工具实战指南【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidityyul-phaser是 Solidity 仓库内置的一个内部工具用于自动寻找 Yul 优化器Yul optimiser的最优优化步骤序列。它把为给定程序挑选优化步骤与排列顺序这一被称为phase-ordering problem的难题建模为搜索问题并使用遗传算法genetic algorithm在庞大的解空间中迭代求解。读完本文你将掌握 yul-phaser 的工作原理、全部核心命令行参数与默认值、如何从零开始搜索、如何从上次的搜索结果续跑、如何分析某个特定序列的指标与优化效果以及如何把搜到的序列通过--yul-optimizations直接交给solc使用。yul-phaser 要解决的问题Solidity 编译器在启用优化后会经过多个 Yul 优化阶段optimisation steps每个阶段各司其职有的消除无用代码如UnusedPruner、有的做公共子表达式消除如CommonSubexpressionEliminator、有的展开函数如FullInliner等。这些阶段的执行顺序对最终生成代码的质量影响显著而可能的排列组合空间极其庞大——这就是经典的 phase-ordering problem。更棘手的是从理论上讲可能不存在一个对所有程序都最优的单一序列某个序列对合约 A 效果极佳对合约 B 却可能适得其反。因此与其手工反复试验不如让程序自动搜索。yul-phaser 正是为此而生的内部工具位于 tools/yulPhaser 目录编译目标名为yul-phaser由 tools/CMakeLists.txt 构建。它不参与日常编译流程而是作为开发者的参数搜索器输入一组 Yul 程序输出一个或一批适合这些程序的优化步骤序列。工作流程从问题建模到遗传算法三个核心抽象从源码结构看整个工具建立在三个核心抽象之上均在 tools/yulPhaser 下Program程序Program.h 负责读取并解析输入文件将其编译为 Yul AST并允许把一组合规的优化步骤应用到程序上。Chromosome染色体Chromosome.h 表示一条优化步骤序列即种群中的一个个体。它内部把步骤序列编码成字符串每个字符代表一个优化步骤见后文并且一旦创建便不可变——想变异只能基于旧染色体生成新染色体。FitnessMetric适应度指标FitnessMetrics.h 计算一个染色体序列作用于程序后的得分得分越低序列越好。所有指标都是确定性的只依赖染色体本身与指标状态。遗传算法的运转方式算法入口在 AlgorithmRunner它持有当前种群在每一轮中调用可插拔的GeneticAlgorithm::runNextRound()对种群做进化并负责打印轮次反馈、去重把重复染色体替换为随机新个体可用--no-randomise-duplicates关闭、以及按需把种群写入文件。yulPhaser/GeneticAlgorithms.h 中实现了三种可选算法GEWEPGenerationalElitistWithExclusivePools每一轮将种群分为互斥的三部分——精英池直接保留交叉池与变异池分别由精英个体通过交叉、变异生成新个体。这是默认算法。classicClassicGeneticAlgorithm经典三段式流程——按适应度比例选择可重复选中同一染色体、按概率配对做交叉、再按概率对每个基因逐一施加变异基因随机化/删除/添加。randomRandomAlgorithm每轮仅保留固定比例的精英其余全部替换为随机生成的全新染色体。它不基于当前种群产生后代相当于一个带精英保留的随机搜索基线。三种算法的参数与默认值都在 Phaser.cpp 的命令行描述中定义详见下文参数表。序列的简写表示字母即优化步骤优化步骤序列在 yul-phaser 中以字母字符串呈现每个字符代表一个优化步骤。完整对照表见 docs/internals/optimizer.rst 的 Optimizer Steps 一节例如缩写完整步骤名fBlockFlattenercCommonSubexpressionEliminatorDDeadCodeEliminatoriFullInlineruUnusedPrunergFunctionGrouperhFunctionHoisteraSSATransformvEquivalentFunctionCombinertStructuralSimplifier注意BlockFlattener、FunctionGrouper、ForLoopInitRewriter三个步骤是其他步骤的前提Yul 优化器总会先于用户指定步骤应用它们见 docs/internals/optimizer.rst 说明因此 yul-phaser 在--prefix说明中也提到它会隐式添加hgo前缀确保染色体可以包含任意优化步骤。此外[...]中的步骤会被循环重复应用直到 Yul 代码不再变化或达到最大轮数当前为 12——这个机制直接复用到solc的--yul-optimizations中详见 docs/internals/optimizer.rst。快速上手一次最简单的搜索yul-phaser 对大多数参数都有合理的默认值最简单的调用只需给出输入文件与随机种群大小tools/yul-phaser ../test/libyul/yulOptimizerTests/fullSuite/*.yul \ --random-population 100前提是你已经编译了 Solidity 源码树从而生成tools/yul-phaser可执行文件并且当前位于该工作副本的构建目录中。输入是一个或多个 Yul 程序每个候选序列都会被应用到所有这些程序上再根据所选指标打分。--random-population 100表示初始种群包含 100 条随机生成的序列。从 PopulationFactory 的实现可以看到初始种群支持三种来源且可以组合--population显式指定序列、--random-population随机生成、--population-from-file从文件读取最终种群是三者的并集。运行yul-phaser --help可以查看全部可用选项帮助文本由 buildCommandLineDescription 生成。常用工作模式从上次搜索结果继续搜索可能需要很长时间yul-phaser 支持每轮结束后把当前种群保存到文件中断后可无缝续跑tools/yul-phaser *.yul \ --random-population 100 \ --population-autosave /tmp/population.txt停止应用后用--population-from-file读回种群继续搜索同时继续自动保存保存文件会随每轮更新tools/yul-phaser *.yul \ --population-from-file /tmp/population.txt \ --population-autosave /tmp/population.txt保存文件是纯文本格式每行一条染色体即一串字母。这来自 buildFromFile 的实现——它按行读取文件并把每行当作一个基因序列。分析一条给定序列如果已经有了一条序列无论是手工构造的还是搜出来的可以用--rounds 0跳过算法迭代让 phaser 只做分析工作。查看某条序列在指定指标下的得分tools/yul-phaser *.yul \ --show-initial-population \ --rounds 0 \ --metric code-size \ --metric-aggregator sum \ --population your sequence--rounds 0算法运行 0 轮即不进化--metric code-size使用程序大小指标默认是relative-code-size--metric-aggregator sum把该序列对每个输入程序的得分求和默认为average--population your sequence把目标序列作为初始也是唯一种群。查看该序列优化后的程序长什么样tools/yul-phaser *.yul \ --rounds 0 \ --mode print-optimised-programs \ --population your sequence--mode共有三种取值见 Phaser.hrun-algorithm默认模式运行遗传算法print-optimised-programs打印每个染色体优化后的程序代码对应 printOptimisedProgramsOrASTs 中operator输出的 Yul 文本print-optimised-asts打印优化后的程序 ASTJSON 形式对应同一函数中的toJson()分支。使用 solc 生成的中间表示solc可以把 Solidity 合约编译成 Yul IR而这些输出可以直接作为 yul-phaser 的输入solc/solc sol file --ir --output-dir output directory输出目录中会出现一个或多个.yul文件。注意这些文件包含的是完整的 Yul 对象object而不仅是裸的 Yul 程序——yul-phaser 已经准备好处理这种情况Program::load 负责解析包含 object 的输入。这一步是针对你自己的合约搜索优化序列的标准做法把项目里有代表性的合约转成 IR喂给 phaser。把搜索结果应用到编译器搜索结束后把得到的序列直接通过--yul-optimizations交给solc让 Yul 优化器按该序列执行优化solc/solc sol file --optimize --ir-optimized --yul-optimizations sequence该选项的语义与默认序列的差异在 docs/internals/optimizer.rst 中有详细说明默认情况下优化器应用预定义的步骤序列传入--yul-optimizations后即覆盖之。例如文档中的示例dhfoD[xarrscLMcCTU]uljmul:fDnTOcmu——其中方括号内的部分会被循环应用。完整命令行参数参考以下参数及默认值均直接取自 Phaser.cpp 中buildCommandLineDescription()的定义yul-phaser --help的输出同源。通用参数参数默认值说明--help—显示帮助信息并退出--input-files PATH必填输入文件位置参数可多个--prefix CHROMOSOME空字符串自动应用于每个输入程序的初始优化步骤结果视为实际输入这些步骤不属于染色体、不会被变异相对指标的基准也是应用这些步骤之后的程序。phaser 总是隐式加hgo前缀本选项值在其后--seed NUM随机生成随机数生成器种子配合--show-seed可复现实验--rounds NUM无限制算法停止前的轮数--mode NAMErun-algorithm运行模式见上文三种取值算法通用参数参数默认值说明--algorithm NAMEGEWEP可选GEWEP、classic、random--no-randomise-duplicates关闭默认每轮后把重复染色体替换为随机个体开启后禁用该后处理--min-chromosome-length NUM100随机染色体最小长度--max-chromosome-length NUM100随机染色体最大长度--crossover NAMEuniform交叉算子single-point、two-point、uniform--uniform-crossover-swap-chance PROBABILITY0.5uniform 交叉中两个基因互换的概率GEWEP 算法参数参数默认值说明--gewep-mutation-pool-size FRACTION0.25每轮由变异再生的种群比例--gewep-crossover-pool-size FRACTION0.25每轮由交叉再生的种群比例--gewep-randomisation-chance PROBABILITY0.9选择基因随机化作为变异操作的概率--gewep-deletion-vs-addition-chance PROBABILITY0.5未选择随机化时选择基因删除而非添加的概率--gewep-genes-to-randomise PROBABILITY1/max-chromosome-length基因随机化中任意基因被变异的概率--gewep-genes-to-add-or-delete PROBABILITY1/max-chromosome-length基因添加或删除中某个基因被添加/删除的概率classic 算法参数参数默认值说明--classic-elite-pool-size FRACTION0.25精英占种群比例每轮直接保留--classic-crossover-chance FRACTION0.75染色体被选中参与交叉的概率--classic-mutation-chance FRACTION0.01基因被随机化的概率--classic-deletion-chance PROBABILITY0.01基因被删除的概率--classic-addition-chance PROBABILITY0.01随机基因被添加的概率random 算法参数参数默认值说明--random-elite-pool-size FRACTION每轮保留 1 个个体与种群大小无关每轮保留的种群比例种群参数参数默认值说明--population CHROMOSOMES空加入初始种群的显式序列可空格分隔多个值或多次指定该选项--random-population SIZE空加入初始种群的随机染色体数量--population-from-file FILE空从文本文件每行一条染色体读取并加入初始种群--population-autosave FILE禁用每轮结束后把种群写入指定文件指标参数参数默认值说明--metric NAMErelative-code-size适应度指标code-size程序绝对大小或relative-code-size相对原程序的比值--metric-aggregator NAMEaverage多个程序得分如何合并average、sum、maximum、minimum--relative-metric-scale EXPONENT3相对指标的定标因子指数指标为整数相对值乘以 10^exp 后取整如 exp3 时 0.5→500、1.0→1000。因子越大越能区分细微差异但数值可读性变差且大数可能丢精度--chromosome-repetitions COUNT1染色体代表的步骤序列被重复应用的次数指标权重参数相对/绝对代码大小指标按 Yul AST 节点计成本CodeWeights。默认权重如下见 Phaser.cpp 的 METRIC WEIGHTS 一节源码注释指出这组权重是过渡方案只为保证任何语句/表达式都不为零成本参数默认值--expression-statement-cost1--assignment-cost1--variable-declaration-cost1--function-definition-cost1--if-cost2--switch-cost1--case-cost2--for-loop-cost3--break-cost2--continue-cost2--leave-cost2--block-cost1--function-call-cost1--identifier-cost1--literal-cost1缓存与输出参数参数默认值说明--program-cache关闭缓存染色体前缀对应的中间程序大幅加速适应度评估但染色体较长时极其消耗内存默认关闭目前无法设置内存上限内存充足时强烈建议开启--show-initial-population关闭算法开始前打印初始种群--show-only-top-chromosome关闭每轮只打印最优染色体而非整个种群--hide-round关闭隐藏轮次信息轮数与已用时间--show-cache-stats关闭每轮后打印缓存大小与命中情况--show-seed关闭打印选中的随机种子其中--program-cache的实现位于 ProgramCache.h由于优化步骤是顺序应用的前缀相同的序列共享相同的中间程序缓存这些中间结果可以避免大量重复优化计算代价是内存占用随染色体长度显著上升。如何选好参数来自实验的实用建议挑选遗传算法参数并不简单但 phaser 的默认值通常已足够在给定程序集上找到与经验丰富的开发者手工调优结果相当甚至更好的序列。真正困难的其实是提供有代表性的输入文件集如果输入文件用不到某些优化工具会倾向产出不使用这些优化的序列遇到能受益于它们的程序时会表现很差反之如果所有输入文件都极度依赖某个优化找到的序列可能对不依赖它的程序不友好。因此输入文件集的代表性比参数微调更重要。在默认值基础上以下经验结论来自维护者基于一组粗略实验的总结相关讨论见 Solidity 问题 #7806供参考表现最好的算法是GEWEP因此它也是默认算法。初始种群使用更长的序列效果更好——算法本身擅长裁剪多余步骤。保留上一轮的最优序列有助于提升结果尤其在使用classic算法时精英池elite至少应包含少量个体。不要把变异/删除/添加概率设得太高过高会破坏交叉保留的良好模式。1%–5% 左右的取值似乎效果最好classic 算法默认的 0.01 正落在这个区间。让算法至少运行 1000 轮以上。通常它能更快找到好序列但运行更久可以显著缩短序列长度——当以长序列起步时这一点尤其重要。源码阅读指引如果你想深入理解实现细节推荐按以下路径阅读Phaser.cpp命令行解析、各组件工厂GeneticAlgorithmFactory、FitnessMetricFactory、PopulationFactory、ProgramFactory、ProgramCacheFactory与总调度逻辑GeneticAlgorithms.h三种遗传算法的接口与选项结构Chromosome.h染色体基因串与优化步骤序列之间的编码/解码genesToSteps/stepsToGenesFitnessMetrics.hProgramSize、RelativeProgramSize以及四种聚合器average/sum/maximum/minimumPopulation.h种群数据结构与makeRandomMutations.h基因随机化、基因添加、基因删除三类变异算子AlgorithmRunner.h轮次循环、去重、自动保存与输出反馈。对应的 Yul 优化器全貌所有优化步骤的详细说明见 docs/internals/optimizer.rst步骤缩写表见其中的 Optimizer Steps 一节Yul 语言本身的文档见 docs/yul.rst。小结yul-phaser 把Yul 优化步骤排序这一组合优化问题交给遗传算法求解并提供了从搜索、续跑、分析到与solc联动的完整工作流。它的正确使用姿势可以概括为三步准备有代表性的 Yul 程序集最好来自你自己的合约、经solc --ir转换、以默认参数跑足够多轮必要时调大初始序列长度与精英池、把搜到的序列通过--yul-optimizations应用到编译器从而为特定合约量身定制优化管线。【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考