Page 242 - 《软件学报》2026年第3期
P. 242

李清伟 等: Go  语言程序的内存性能与安全问题实证研究                                                   1205


                 本部分深入分析接口类型转换模式及其对程序性能的影响.
                  3.1   基本访存操作的分布
                    ● 基本访存操作分布. 根据        Go  语言官方提供的规范      [33] , 本文将  Go  语言程序中的基本内存访问操作总结为
                 7  类, 如表  2  所示. 其中, 类别  Direct 为对变量的访问操作表达式, Deref 为解引用表达式, Ref 为取地址表达式,
                 FieldAcc 为域访问表达式, SliceIdx、ArrayIdx、MapIdx  为索引模式, 分别为对切片、数组和映射的索引. 由于对
                 变量的直接访问 (Direct) 广泛嵌套于其他表达式中, 统计时会存在重复计数. 为避免这一问题, QLStat 对                     Direct 模
                 式的统计做了去重处理: 当 Direct 模式作为外层模式            (如  Ref, Deref 等) 的一部分时, 不计入  Direct 模式. 例如, 在
                 表达式&a 中, 对变量    a 的访问不计入     Direct 模式中.

                                                  表 2 基本访存操作的分布

                                形式               类别                数量               比例 (%)
                                  a              Direct          17 649 448          51.10
                                 *a              Deref            6 089 052          17.63
                                 &a               Ref             1 181 230          3.42
                                 a.f            FieldAcc          8 785 663          25.44
                                slice[i]        SliceIdx          511 916            1.48
                               array[i]         ArrayIdx           40 872            0.12
                                m[i]            MapIdx            283 304            0.82
                                         总计                      34 541 485          100.00

                    对 966  个数据库的统计分析表明, 除        Direct 之外, Deref 和  FieldAcc 操作占比最高, 分别为  17.63%  和  25.44%.
                 这表明在编译器优化和静态分析工具的建模中, 应重点关注这两类操作, 以更有效地提高分析精度和优化性能.
                    在程序分析算法设计中, 为了提升算法分析精度, 通常需要使用更细粒度的建模方式, 获取更多的分析事
                 实. 但是这也会导致算法开销显著增加, 因此, 如何平衡使用更高精度分析算法的收益与代价是所有设计者都
                 要面临的问题. 为了给       Go  程序的分析与语言优化提供更细粒度、更精确的参考, 本文聚焦于统计结果中占比
                 较高的   Deref 和  FieldAcc, 对连续域访问操作和连续解引用操作进行深入分析, 以探索优化潜力.
                    ● 连续域访问次数. 连续域访问是指一个域访问表达式的基表达式仍然为域访问表达式, 如                            a.f1.g2  的基表达
                 式  a.f1  仍是域访问表达式. 连续域访问模式会影响域敏感分析的算法效率和空间开销. 如果域敏感分析对所有程
                 序中出现的域均进行建模, 那么会有较大的空间浪费——部分域上的状态信息可能并不会被使用, 但是仍然需要
                 被更新. 这也会导致算法执行效率较低.
                    例如, 假设   a 结构体变量包含     2  个域  f1  和  f2, 这两个域是包含两个域  g1  和  g2  的结构体. 对于  y = a.f1.g1  语句,
                 在域不敏感分析中, 语句会被建模为           y = a, 这会导致分析不精确. 而在域敏感分析中, f1        和  g1  都需要被建模, 分析更
                 加精确, g1  和  g2  的状态会被区分, 但会带来较大的分析时空开销. 如果将分析的粒度控制在只建模                     f1, 而不区分  g1
                 和  g2, 即将语句建模为    y = a.f1, 则可以降低分析开销, 同时保留     f1  和  f2  之间的区分. 如果可以知道连续域访问次数
                 的分布情况, 就可以调整域敏感分析的粒度, 从而在不损失太多精度的情况下提升算法执行效率并减少空间开销.
                    QLStat 通过递归遍历所有域访问表达式, 检查其基表达式是否仍为域访问表达式, 从而计算连续域访问次数.
                 统计结果如表     3  所示. 在所有域访问操作中, 单次域访问占比约为             93.32%; 连续域访问次数为      2  的占比为  5.65%,
                 次数大于等于     3  的占比仅为   1.03%. 上述结果显示, 在设计针对       Go  语言的域敏感程序分析算法时, 关注单次域访
                 问操作即可覆盖绝大多数场景, 获得显著的精度收益. 而对连续多次域访问操作的敏感性分析虽然可以提高精度,
                 但其占比较小且处理难度较高, 可能带来不成比例的时空代价, 因此可以根据需求权衡是否支持.
                    ● 连续解引用次数. 连续解引用是指一个解引用表达式的子表达式仍然为解引用表达式, 例如**p                            的子表达式
                 *p  仍是解引用表达式. 如果需要精确知道**p          所指向的对象, 需要进行两次指向集合计算, 这会带来较大的时间和
                 空间开销. 但是部分指向分析下游应用 (如逃逸分析) 并不需要如此高的精度. 如果可以知道连续解引用次数的分
                 布, 那么下游应用就可以减少指向集合计算次数, 在保证效率的同时达到相对理想的分析效果.
   237   238   239   240   241   242   243   244   245   246   247