40、abc:可扩展的AspectJ编译器

abc:可扩展的AspectJ编译器

1. 运行时工厂扩展

为了给合适的连接点创建实现新 CastSignature 接口的对象,我们对运行时工厂进行了子类化,添加了相应方法。 AbcExtension 类中有一个方法,用于指定 thisJoinPointStaticPart 对象的工厂运行时类,该方法被重写,以使用新工厂创建运行时对象:

public String runtimeSJPFactoryClass()
{
    return “org.aspectbench.eaj.runtime.reflect.EajFactory”;
}

2. 代码测量

为了让读者评估实现每个新特性所需的工作量,我们总结了一些统计数据,如下表所示:
| 类型 | 文件数 | 代码行数 |
| — | — | — |
| 解析 | 1 | 74 |
| 私有切入点变量 - AST节点 | 2 | 130 |
| 私有切入点变量 - 传递 | 0 | 0 |
| 私有切入点变量 - 织入器 | 0 | 0 |
| 私有切入点变量 - 运行时 | 0 | 0 |
| 全局切入点声明 - AST节点 | 4 | 64 |
| 全局切入点声明 - 传递 | 1 | 77 |
| 全局切入点声明 - 织入器 | 0 | 0 |
| 全局切入点声明 - 运行时 | 0 | 0 |
| 强制类型转换切入点 - AST节点 | 2 | 46 |
| 强制类型转换切入点 - 传递 | 0 | 0 |
| 强制类型转换切入点 - 织入器 | 2 | 94 |
| 强制类型转换切入点 - 运行时 | 2 | 27 |
| 抛出切入点 - AST节点 | 2 | 46 |
| 抛出切入点 - 传递 | 0 | 0 |
| 抛出切入点 - 织入器 | 2 | 91 |
| 抛出切入点 - 运行时 | 2 | 16 |
| 扩展信息和共享类 | 7 | 205 |
| 总计 | 27 | 870 |

从这些数据可以看出,不同特性的实现工作量差异明显,这表明模块化目标已达成。而且,所有扩展都无需修改基础编译器的代码,它们是清晰分离的插件模块。

3. 与ajc的详细比较

3.1 组件分离

3.1.1 前端和后端代码行数对比
编译器 前端 后端 总计
ajc 10,197 23,938 34,135
abc 16,444 17,397 33,841

乍一看,ajc的前端比abc小很多,但这是以对其依赖的Java编译器源文件进行大量修改为代价的,而这些修改未在上述数据中体现。另外,abc使用Polyglot,它鼓励使用许多小类,并且需要大量的访问者和工厂样板代码。abc后端较小,这是因为使用了干净的中间表示Jimple和Soot框架中的丰富分析工具。

3.1.2 与基础编译器的分离
  • ajc :ajc基于Eclipse Java编译器,该编译器为速度而设计,避免使用Java集合类,采用低级数据结构和整数常量调度。但ajc需要自己的Eclipse编译器源树副本,并进行了大量修改,包括44个Java文件和至少119个源位置的显式更改,还修改了语法。这些更改存在复杂依赖,导致将ajc与最新版本的Eclipse编译器合并很困难。
  • abc :abc基于Polyglot,无需对其源文件进行任何更改。Polyglot设计为可扩展,abc只是它的一个扩展。通过引入新的环境类型和类型系统来处理作用域规则的更改,这些都是对Polyglot相应类的简单扩展。Polyglot允许通过子类化进行更改,因此升级到新版本时无需额外工作。此外,abc提供了干净的LALR(1)语法,实现了Java语法和AspectJ扩展语法的清晰分离。
3.1.3 与字节码操作的分离
  • ajc :ajc使用BCEL库进行字节码操作和代码生成,但需要维护一个特殊版本的BCEL,该版本最初通过约300行的补丁文件与BCEL发行版同步,现在作为ajc的一部分独立开发,修改后的BCEL有23,259行代码。
  • abc :abc与Soot转换和代码生成框架完全分离,无需对Soot进行任何更改。

综上所述,abc是第一个实现与构建组件清晰分离的AspectJ编译器,其思想有望应用于为其他编程语言添加面向方面特性。

3.2 编译时间

为了评估使用方面对编译时间的影响,我们比较了四种不同的AspectJ编译器:普通ajc、ajc加上Soot优化(ajc + soot)、关闭所有优化的abc(abc -O0)和使用默认过程内优化的abc(abc)。以下是六个基准测试的编译时间:
| 基准测试 | SLOC | APPS | ajc | ajc + Soot | abc - O0 | abc | javac |
| — | — | — | — | — | — | — | — |
| bean | 124 | 4 | 1.77 | 4.00 | 3.30 | 3.59 | - |
| bean - java | 104 | 0 | 1.43 | 3.21 | 3.05 | 3.03 | 0.54 |
| sim - nullptr | 1474 | 138 | 2.96 | 12.00 | 10.38 | 10.69 | - |
| sim - nullptr - java | 1547 | 0 | 1.75 | 6.52 | 7.45 | 8.64 | 0.76 |
| figure | 94 | 12 | 1.62 | 3.43 | 2.95 | 3.07 | - |
| figure - java | 98 | 0 | 1.25 | 2.83 | 2.63 | 2.65 | 0.51 |
| LoD - sim | 1586 | 1332 | 4.10 | 29.87 | 36.47 | 46.14 | - |
| dcm | 1668 | 359 | 3.37 | 17.07 | 14.74 | 17.43 | - |
| tetris | 1043 | 29 | 2.88 | 8.42 | 8.40 | 8.93 | - |

从这些数据可以看出,abc的编译时间明显比ajc长,这是因为abc的代码没有针对编译时间进行优化,而ajc的设计目标之一就是短编译时间。不过,abc的编译时间反映了其强大的优化框架的成本,与ajc + soot相比,abc的编译时间相当,这是令人鼓舞的。而且,像abc这样的研究型编译器能够处理大型示例,适合用于对日常使用ajc开发的程序进行优化构建。

3.3 织入Jimple(abc)与织入字节码(ajc)

我们通过一个简单的例子来说明abc织入三地址Jimple表示与ajc直接织入字节码的优势。在Java代码中,在调用 bar 方法之前织入一段通知,以下是织入前后的代码:

// (a) 基础Java代码
public int f(int x, int y, int z)
{
    return bar(x, y, z);
}

// (b) 直接织入字节码(ajc)
public int f(int x, int y, int z)
0: aload_0
1: iload_1
2: iload_2
3: iload_3
4: istore %4
6: istore %5
8: istore %6
10: astore %7
12: invokestatic A.aspectOf ()LA;
15: aload %7
17: invokevirtual A.ajc$before$A$124 (LFoo;)V
20: aload %7
22: iload %6
24: iload %5
26: iload %4
28: invokevirtual Foo.bar (III)I
31: ireturn

// (c) 织入Jimple(abc)
public int f(int, int, int)
{ 
    Foo this;
    int x, y, z, $i0;
    A theAspect;
    this := @this;
    x := @parameter0;
    y := @parameter1;
    z := @parameter2;
    theAspect = A.aspectOf();
    theAspect.before$0(this);
    $i0 = this.bar(x, y, z);
    return $i0;
}

// (d) 从Jimple生成的字节码(abc)
public int f(int x, int y, int z)
0: invokestatic A.aspectOf ()LA;
3: aload_0
4: invokevirtual A.before$0 (LFoo;)V
7: aload_0
8: iload_1
9: iload_2
10: iload_3
11: invokevirtual Foo.bar (III)I
14: ireturn

ajc织入字节码时,除了实现查找方面和调用通知体的字节码外,还需要生成大量的栈修复代码来修复隐式字节码计算栈。而abc织入Jimple时,Jimple不使用隐式计算栈,所有值都用显式变量表示。织入时,abc只需声明一个Jimple变量,插入查找方面和调用通知的两行代码,无需额外的栈修复代码。从Jimple生成的字节码比ajc生成的字节码更小,使用的局部变量也更少,对编织代码的性能有积极影响。

3.4 使用Soot优化进行织入

abc使用Soot作为后端,能够利用Soot现有的优化过程来改进生成的代码。这不仅简化了织入器的设计,还能实现一些在直接织入时难以或无法应用的面向方面的优化。例如,AspectJ在通知体中提供了特殊变量 thisJoinPoint ,它包含连接点的各种反射信息,构建成本较高。abc和ajc都实现了该变量的“懒”初始化,即只有在通知体真正需要时才构建,且在一个连接点应用多个通知时,该变量只构建一次。
- ajc :当连接点有环绕通知时,懒初始化实现可能不起作用,并且在只有一个通知时会进行特殊处理以避免不必要的懒加载。
- abc :在所有情况下都使用懒初始化,并通过后续的空值分析消除大多数情况下的懒加载开销。该分析是标准的Java分析,并结合了AspectJ运行时库方法构建 thisJoinPoint 对象永远不会返回空值的额外信息,因此实现比ajc更简单、更健壮。

3.5 目标代码性能

虽然本文未对ajc和abc生成代码的效率进行详细比较,但在之前的工作中,我们对面向方面程序的动态行为进行了详细研究。通过特制的测量工具,我们证实了在许多AspectJ程序中,方面引入的开销可以忽略不计,但也发现了一些开销较高的常见情况。因此,abc的一个明确目标是能够试验新的面向方面的优化。

3.5.1 环绕通知优化

abc对环绕通知进行了改进,在一些基准测试中实现了六倍的加速。在某些情况下,ajc为了实现 proceed 会生成闭包,这会占用大量堆空间,导致显著的开销。而abc在几乎所有情况下都能避免闭包的构建。当ajc不生成闭包时,会进行大量内联,可能导致代码膨胀。abc的编译策略在代码大小和速度之间取得了平衡,如下表所示:
| 基准测试 | abc - 时间(s) | ajc - 时间(s) | abc - 大小(指令) | ajc - 大小(指令) |
| — | — | — | — | — |
| sim - nullptr | 21.9 | 21.4 | 7893 | 10186 |
| sim - nullptr - rec | 23.6 | 124.0 | 8216 | 10724 |
| weka - nullptr | 19.0 | 16.0 | 103,018 | 134,290 |
| weka - nullptr - rec | 18.9 | 45.5 | 103,401 | 130,483 |
| ants - delayed | 17.5 | 18.2 | 3688 | 3785 |
| ants - profiler | 22.5 | 21.2 | 7202 | 13401 |

3.5.2 cflow优化

ajc 1.2中 cflow 的实现使用了昂贵的栈操作,而使用简单计数器就足够了。此外,它在单个过程体中多次检索相同的线程局部状态,并且在多个相同 cflow 切入点的出现之间不共享工作。abc消除了这些问题,与ajc 1.2相比,这些小优化在 LoD - sim 基准测试中实现了182倍的改进。一些简单的优化(计数器和共享)已被纳入ajc 1.2.1。abc还提供了重织入技术,利用现有的纯Java分析,通过构建动态调用图的静态近似,在编译时确定每个影子是否在给定切入点的 cflow 中,从而完全消除 cflow 的成本。

整体来看,abc在扩展性、优化和组件分离方面具有明显优势,虽然编译时间较长,但能为程序带来更好的性能优化,适合用于对程序进行优化构建。

3.5.2 cflow优化(续)

在之前的ajc 1.2版本中, cflow 的实现存在诸多问题。下面是对这些问题及abc解决方案的详细分析:

ajc 1.2中 cflow 实现的问题
  • 昂贵的栈操作 :ajc 1.2在处理 cflow 时,采用了复杂的栈操作。实际上,很多情况下使用简单的计数器就可以满足需求,栈操作不仅增加了代码的复杂度,还带来了额外的性能开销。
  • 多次检索线程局部状态 :在单个过程体中,ajc 1.2会多次检索相同的线程局部状态,这无疑增加了不必要的开销。
  • 缺乏工作共享 :对于多个相同 cflow 切入点的出现,ajc 1.2没有进行工作共享,导致重复的计算和资源浪费。
abc的解决方案
  • 小优化改进 :abc通过一系列小优化,消除了上述ajc 1.2中存在的问题。例如,使用计数器替代栈操作,避免多次检索线程局部状态,并实现了多个切入点之间的工作共享。这些优化在 LoD - sim 基准测试中取得了显著的效果,实现了182倍的性能提升。
  • 重织入技术 :abc还提供了重织入技术,利用现有的纯Java分析能力。其核心思想是构建动态调用图的静态近似,这样在编译时就可以确定每个影子是否在给定切入点的 cflow 中,从而完全消除 cflow 的成本。

以下是不同编译器在不同优化配置下的 cflow 性能对比表格:
| 编译器 | 无优化 | 计数器 | 计数器 + 共享 | 计数器 + 共享 + 重用 | 计数器 + 共享 + 重用 + 过程间优化 |
| — | — | — | — | — | — |
| abc | 1072.2 | 238.3 | 90.3 | 20.3 | 1.96 |
| ajc 1.2 | 450.5 | 167.7 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - figure | | | | | |
| abc | 122.3 | 75.1 | 27.9 | 27.4 | 27.3 |
| ajc 1.2 | 123.5 | 28.9 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - quicksort | | | | | |
| abc | 29.0 | 29.1 | 22.8 | 22.5 | 20.4 |
| ajc 1.2 | 29.7 | 24.2 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - sablecc | | | | | |
| abc | 18.7 | 18.8 | 18.7 | 17.9 | 13.1 |
| ajc 1.2 | 33.0 | 32.9 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - ants | | | | | |
| abc | 1723.9 | 46.6 | 32.8 | 26.2 | 23.7 |
| ajc 1.2 | 4776.2 | 35.3 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - LoD - sim | | | | | |
| abc | 1348.7 | 142.5 | 91.9 | 75.2 | 66.3 |
| ajc 1.2 | 2349.2 | 113.5 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - LoD - weka | | | | | |
| abc | 592.8 | 80.1 | 41.2 | 27.4 | 23.1 |
| ajc 1.2 | 1107.4 | 56.0 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - Cona - stack | | | | | |
| abc | 75.8 | 75.3 | 73.8 | 72.0 | 73.6 |
| ajc 1.2 | 76.8 | 69.0 | - | - | - |
| ajc 1.2.1 | - | - | - | - | - |
| 基准测试 - Cona - sim | | | | | |

从这个表格中可以清晰地看到,abc在不同的优化配置下,性能都有显著的提升,尤其是在使用了更高级的优化策略后,与ajc相比优势更加明显。

4. 总结

4.1 abc的优势
  • 组件分离 :abc是第一个实现与构建组件清晰分离的AspectJ编译器。与ajc相比,abc无需对基础编译器(Polyglot)和字节码操作框架(Soot)进行修改,避免了复杂的依赖和合并问题,具有更好的可扩展性和维护性。
  • 优化能力 :abc拥有强大的优化框架,通过使用Soot的优化过程,能够实现面向方面的优化。例如,在环绕通知和 cflow 方面的优化,显著提高了程序的性能。虽然编译时间相对较长,但与ajc + soot相比,编译时间相当,并且能够处理大型示例,适合用于对日常使用ajc开发的程序进行优化构建。
  • 代码生成 :abc织入Jimple表示的方式比ajc直接织入字节码更具优势。Jimple不使用隐式计算栈,织入时无需额外的栈修复代码,生成的字节码更小,使用的局部变量更少,对编织代码的性能有积极影响。
4.2 性能对比
对比项 ajc abc
编译时间 短,设计目标之一是快速编译 较长,但反映了强大的优化框架成本,与ajc + soot相当
组件分离 对基础编译器和字节码操作库有大量修改,合并困难 与基础编译器和字节码操作框架完全分离,易于升级和维护
代码生成 直接织入字节码,可能需要大量栈修复代码,生成的代码较大 织入Jimple表示,生成的字节码更小,使用局部变量更少
优化能力 缺乏abc的一些优化能力,如环绕通知和 cflow 优化 具有强大的优化能力,能显著提高程序性能
4.3 应用建议

对于日常开发,由于ajc编译时间短,适合快速迭代和开发。而对于需要进行优化构建的程序,abc是一个很好的选择。其强大的优化能力可以在不牺牲太多编译时间的情况下,显著提高程序的性能。

5. 流程图:abc编译流程

graph LR
    A[源代码] --> B[Polyglot解析]
    B --> C[生成Jimple表示]
    C --> D[Soot优化]
    D --> E[织入通知]
    E --> F[生成目标代码]

这个流程图展示了abc的编译流程,从源代码开始,经过Polyglot解析生成Jimple表示,然后利用Soot进行优化,接着织入通知,最后生成目标代码。整个流程体现了abc的组件分离和优化能力。

6. 总结表格:abc与ajc对比

特性 ajc abc
基础编译器依赖 基于Eclipse Java编译器,需大量修改 基于Polyglot,无需修改
字节码操作库 使用特殊版本的BCEL,需维护 与Soot完全分离,无需修改
编译时间 长,但与ajc + soot相当
代码生成 直接织入字节码,有栈修复代码 织入Jimple,生成字节码更小
优化能力 部分优化,存在不足 强大的面向方面优化
组件分离 依赖紧密,合并困难 清晰分离,易于升级

通过这个表格,可以更加直观地对比abc和ajc在各个方面的差异,从而根据具体需求选择合适的编译器。

综上所述,abc作为一个可扩展的AspectJ编译器,在组件分离、优化能力和代码生成等方面具有明显的优势,虽然编译时间较长,但在优化构建方面表现出色,为面向方面编程提供了一个优秀的选择。

【激光质量检测】利用丝杆与步进电机的组合装置带动光源的移动,完成对光源使用切片法测量其光束质量的目的研究(Matlab代码实现)内容概要:本文研究了利用丝杆与步进电机的组合装置带动光源移动,结合切片法实现对激光光源光束质量的精确测量方法,并提供了基于Matlab的代码实现方案。该系统通过机械装置精确控制光源位置,采集不同截面的光强分布数据,进而分析光束的聚焦特性、发散角、光斑尺寸等关键质量参数,适用于高精度光学检测场景。研究重点在于硬件控制与图像处理算法的协同设计,实现了自动化、高重复性的光束质量评估流程。; 适合人群:具备一定光学基础知识和Matlab编程能力的科研人员或工程技术人员,尤其适合从事激光应用、光电检测、精密仪器开发等相关领域的研究生及研发工程师。; 使用场景及目标:①实现对连续或脉冲激光器输出光束的质量评估;②为激光加工、医疗激光、通信激光等应用场景提供可靠的光束分析手段;③通过Matlab仿真与实际控制对接,验证切片法测量方案的有效性与精度。; 阅读建议:建议读者结合机械控制原理与光学测量理论同步理解文档内容,重点关注步进电机控制逻辑与切片数据处理算法的衔接部分,实际应用时需校准装置并优化采样间距以提高测量精度。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值