`
netcome
  • 浏览: 485964 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Classworking 工具箱: 用方面跟踪性能

    博客分类:
  • AIX
阅读更多

上个月 我介绍了如何用 AspectWerkz 框架实现一个基本的性能跟踪方面(aspect)。这个月我将进一步发展这个方面, 首先增加一些方便的功能,然后将它用于分析一个复杂的真实世界代码 —— Apache Axis Web 服务框架。您将看到我用这种面向方面的方法进行性能跟踪的好处和局限性,最后我的看法是,进行一般性的日志和跟踪是面向方面编程(AOP)通常可以发挥作用的地方。

在深入 Axis 之前,需要讨论另一个问题。在上个月的性能跟踪方面以及在更早的一篇 关于 Java 编程动态性 的文章中(请参阅 参考资料),我使用了 方法计时信息来分析应用程序性能问题。我个人认为这种方法在某些情况下非常方便,但是, 为了使对它的介绍尽可能地全面,还需要讨论一些副作用的问题。我将在本文的第一部分中讨论这些问题。

适时的离题

我的性能跟踪方面的主要目的是监视程序的执行,并给出执行时间从上到下的视图( 与通常用 profiler 得到的从下到上的视图正好相反)。要真正有效地使用它,需要 对时间是如何测量的多少有一些了解。

Java 编程语言有一个计时方法,从早期版本起它就是标准 API 的一部分。 这个方法是 java.lang.System.currentTimeMillis(),它返回从 UTC 1970 年 1 月 1 日午夜开始经过的毫秒数。这个方法非常有用,特别 是它在各个版本的 Java 平台上都有。因此,在 上个月 我的 初始方面演示代码中,我使用了它。不过,currentTimeMillis() 也有 一些缺点。

currentTimeMillis() 的一个问题是,它虽然是以毫秒计数返回 时间,但是不能保证返回的计数的粒度或精度。在一些系统上,时间是真正的毫 秒计数,大约每一毫秒改变一次。在另一些系统上,时间可能每隔 10 或 20 毫秒才变一 次,因此每一次的精度都可能变化。

另一个局限是在方法定义中的基本时间单位。当 Java 平台第一次开始使用时,一毫秒 仍然是相当短的时间间隔(特别是对于解释语言)。今天,随着 CPU 时钟速度比第一次发布 Java 平台时快了近一百倍,一毫秒已经成了相对长的一段时间。

除了 currentTimeMillis() 的这些问题, 还有另一个使用任何类型的计时器方法监视程序执行都会有的问题。这个问题 是计时器方法本身 —— 可能要深入操作系统才能访问计时器 —— 这增加了一些开销, 如果计时器方法开销过大(或者方法调用过于频繁),那么就会严重扭曲计时测量。 为了明智地使用我们的性能跟踪方面,我需要至少大体知道计时器方法调用增加 了多少开销。

计时 currentTimeMillis()

使用 currentTimeMillis() 到底有多大问题?为了澄清这一点,我编写了 一个程序进行一些测试。这个程序如清单 1 所示,通过重复调用这个方法直到返回值改变, 以一种非常简单的方式检查 currentTimeMillis() 的 粒度、精度和开销。改变的幅度告诉我计时器的 粒度,两次改变之间的调用次数可以得出每次调用的开销,而改变幅度的一致性和改变 之间的调用次数让我可以检查精度。


清单 1. currentTimeMillis() 粒度
				
public class MilliGran
{
    // round value to a single digit after decimal point
    private static String roundOneDecimal(double value) {
        int whole = (int)value;
        return whole + "." + (int)((value-whole)*10.0);
    }
    
    // print average and deviation with lead text
    private static void print(String lead, double sum,
        double ssqr, int reps) {
        double mean = sum / reps;
        double stddev = Math.sqrt(ssqr/reps-mean*mean);
        System.out.println(lead + roundOneDecimal(mean) +
            ", with standard deviation " + roundOneDecimal(stddev));
    }
    
    public static void main(String[] argv) {
        
        // get control values from command line
        int reps = Integer.parseInt(argv[0]);
        int sets = Integer.parseInt(argv[1]);
        
        // loop until time rolls over
        long start = System.currentTimeMillis();
        long last;
        while ((last = System.currentTimeMillis()) == start);
        
        // loop for count of sets specified
        int[] diffs = new int[reps];
        for (int i = 0; i < sets; i++) {
            double lsum = 0.0;
            double lsumsq = 0.0;
            double dsum = 0.0;
            double dsumsq = 0.0;
            for (int j = 0; j < reps; j++) {
                
                // count loops until result changes
                int loop = 0;
                last = System.currentTimeMillis();
                long now;
                do {
                    loop++;
                    now = System.currentTimeMillis();
                } while (now == last);
                
                // accumulate statistics information
                lsum += loop;
                lsumsq += loop*loop;
                int diff = (int)(now-last);
                dsum += diff;
                dsumsq += diff*diff;
                diffs[j] = diff;
            }
            
            // print results for set
            System.out.println("Set " + i + " results:");
            print(" Average time change ", dsum, dsumsq, reps);
            print(" Average calls before change ", lsum, lsumsq, reps);
            System.out.println(" Average time per call " +
                roundOneDecimal(dsum / lsum * 1000000.0) + " nanoseconds");
            System.out.print(" Differences:");
            for (int j = 0; j < reps; j++) {
                System.out.print(" " + diffs[j]);
            }
            System.out.println();
        }
    }
}

清单 2 展示在我的笔记本计算机(有较慢的 1GHz PIIIm,运行 Mandrake Linux 10.0)上用 JDK 1.4.2 运行这个程序的结果。结果显示,在这个例子上,currentTimeMillis() 的时间改变总是一个毫秒,调用这个 方法的开销平均只有 3 毫秒。因为这些值都很一致(在这里是百分之一以内),所以至少 可以合理地假定计时器值是足够精确的。


清单 2. 运行 currentTimeMillis() 测试的结果
				
[dennis@notebook time]$ java -cp . MilliGran 10 4
Set 0 results:
 Average time change 1.0, with standard deviation 0.0
 Average calls before change 332.8, with standard deviation 39.4
 Average time per call 3004.8 nanoseconds
 Differences: 1 1 1 1 1 1 1 1 1 1
Set 1 results:
 Average time change 1.0, with standard deviation 0.0
 Average calls before change 338.6, with standard deviation 38.7
 Average time per call 2953.3 nanoseconds
 Differences: 1 1 1 1 1 1 1 1 1 1
Set 2 results:
 Average time change 1.0, with standard deviation 0.0
 Average calls before change 333.6, with standard deviation 33.5
 Average time per call 2996.7 nanoseconds
 Differences: 1 1 1 1 1 1 1 1 1 1
Set 3 results:
 Average time change 1.0, with standard deviation 0.0
 Average calls before change 336.8, with standard deviation 41.4
 Average time per call 2969.1 nanoseconds
 Differences: 1 1 1 1 1 1 1 1 1 1

用 nanoTime() 放大

在 JDK 5.0 中,Sun 增加了另一个方法 java.lang.System.nanoTime(),它可以提供更好的计时信息。顾名思义,nanoTime() 返回一个以纳秒为单位的时间值, 提供了比 currentTimeMillis() 方法精确一百万倍的精度。即使当今的 大多数系统无法提供纳秒精度的时间,但是实际值应当至少比用老的方法获得的要好得多。

很容易修改 清单 1 代码以用 nanoTime() 方法代替 currentTimeMillis()。我在这里就不显示修改后的代码了 (不过它包括在 参考资料 部分中的代码下载中), 但是清单 3 显示了运行修改后的程序的结果,这一次使用了 JDK 5.0:


清单 3. 运行 nanoTime() 测试的结果
				
[dennis@notebook time]$ java -cp . NanoGran 10 4
Set 0 results:
 Average time change 2800.0, with standard deviation 400.0
 Average calls before change 1.0, with standard deviation 0.0
 Average time per call 2800.0 nanoseconds
 Differences: 3000 3000 3000 3000 2000 2000 3000 3000 3000 3000
Set 1 results:
 Average time change 2900.0, with standard deviation 300.0
 Average calls before change 1.0, with standard deviation 0.0
 Average time per call 2900.0 nanoseconds
 Differences: 3000 3000 3000 3000 3000 3000 2000 3000 3000 3000
Set 2 results:
 Average time change 2700.0, with standard deviation 458.2
 Average calls before change 1.0, with standard deviation 0.0
 Average time per call 2700.0 nanoseconds
 Differences: 2000 3000 3000 2000 3000 3000 3000 3000 3000 2000
Set 3 results:
 Average time change 2900.0, with standard deviation 300.0
 Average calls before change 1.0, with standard deviation 0.0
 Average time per call 2900.0 nanoseconds
 Differences: 3000 3000 3000 3000 2000 3000 3000 3000 3000 3000

从清单 3 的结果中可以看出,nanoTime() 方法运行得比 currentTimeMillis() 要稍快一些。但是更重要的是,每次调用后,它返回 不同的时间值,看起来粒度为 1000 纳秒(或者 1 毫秒)。我是怎样知道粒度的?当然从这些结果中不能十分确定,但是对 nanoTime() 的前后两次调用的结果值之差总是 2 或者 3 毫秒这一实事 则很能说明问题。







缓冲方面

将 上个月 的性能跟踪方面改为使用 nanoTime() 很容易。在改的时候,我还做了一些其他改变以改进代码的 易用性,并让测试更灵活。清单 4 给出了相应部分做了修改的代码:


清单 4. 性能跟踪方面代码
				
public class TraceAspect
{
    private static final long DEFAULT_MINIMUM = 1000000;
    
    private final long m_minimumNanoseconds;
    private final int m_maximumNesting;
    private final String m_triggerClass;
    private final String m_triggerName;
    private int m_matchDepth;
    private int m_nestingDepth;
    private ArrayList m_pendingPrints = new ArrayList();
    
    public TraceAspect(AspectContext context) {
        
        // set minimum time value for tracing a method
        String value = context.getParameter("minimum-time");
        m_minimumNanoseconds = (value == null) ?
            DEFAULT_MINIMUM : Long.parseLong(value);
        
        // set maximum depth to be printed
        value = context.getParameter("maximum-depth");
        m_maximumNesting = (value == null) ?
            Integer.MAX_VALUE : Integer.parseInt(value);
        
        // set trigger class and method name if supplied
        value = context.getParameter("trigger-method");
        if (value == null) {
            m_matchDepth = 1;
            m_triggerClass = null;
            m_triggerName = null;
        } else {
            int split = value.lastIndexOf('.');
            m_triggerClass = value.substring(0, split);
            m_triggerName = value.substring(split+1);
        }
    }
    
    private void indent(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print((i % 2 == 0) ? ' ' : '-');
        }
    }
    
    private void indent() {
        indent(m_nestingDepth);
    }
    
    private void printFullMethod(JoinPoint call) {
        MethodSignature signature = (MethodSignature)call.getSignature();
        System.out.print(signature.getDeclaringType().getName());
        System.out.print('.');
        System.out.print(signature.getName());
    }
    public Object trace(JoinPoint join) throws Throwable {
        
        // check for match on trigger
        boolean match = false;
        MethodSignature signature = (MethodSignature)join.getSignature();
        if (signature.getName().equals(m_triggerName)) {
            if (signature.getDeclaringType().getName().equals(m_triggerClass)) {
                match = true;
                m_matchDepth++;
            }
        }
        
        // check if execution is to be processed
        if (m_matchDepth == 0 || m_nestingDepth > m_maximumNesting) {
            
            // skip any timing or printing
            return join.proceed();
            
        } else {
            
            // add joinpoint to list pending print
            m_pendingPrints.add(join);
            m_nestingDepth++;
            
            // time joinpoint execution
            long start = System.nanoTime();
            Object result = null;
            Throwable thrown = null;
            try {
                result = join.proceed();
            } catch (Throwable t) {
                thrown = t;
            } finally {
                
                // adjust depths
                m_nestingDepth--;
                if (match) {
                    m_matchDepth--;
                }
                
                // remove joinpoint if still on pending list
                //  (will always be last in list)
                boolean entered = true;
                if (m_pendingPrints.size() > 0) {
                    m_pendingPrints.remove(m_pendingPrints.size()-1);
                    entered = false;
                }
                
                // check if execution time above cutoff
                long time = System.nanoTime() - start;
                if (time >= m_minimumNanoseconds || thrown != null) {
                    
                    // print all unprinted "entering" lines
                    int count = m_pendingPrints.size();
                    for (int i = 0; i < count; i++) {
                        
                        // print entering information for nesting call
                        //  (nesting depth based on start of pendings)
                        indent(m_nestingDepth-count+i);
                        JoinPoint prior = (JoinPoint)m_pendingPrints.get(i);
                        System.out.print('>');
                        printFullMethod(prior);
                        System.out.println();
                    }
                    
                    // clear all pendings now that they're printed
                    m_pendingPrints.clear();
                    
                    // print exiting information including time
                    indent();
                    System.out.print(entered ? '<' : '*');
                    printFullMethod(join);
                    System.out.print(" took ");
                    long tenthmillis = (time+50000)/100000;
                    System.out.print(tenthmillis / 10);
                    System.out.print('.');
                    System.out.print(tenthmillis % 10);
                    System.out.print(" ms.");
                    
                    // append exception information
                    if (thrown != null) {
                        System.out.println(" threw " +
                            thrown.getClass().getName() +
                            ": " + thrown.getMessage());
                        throw thrown;
                    } else {
                        System.out.println();
                    }
                }
            }
            return result;
        }
    }
}

基本原理仍然与 上个月 一样,但是这段代码 添加了几个警告。除了使用 nanoTime(),我还在方面定义中增加了三个可选配置参数。这些参数控制:

  • 在跟踪输出中加入的方法调用的最小测量时间。
  • 输出中的最大嵌套深度。
  • 用一个触发器方法打开跟踪,这样,只有这个方法进行的调用(直接或者间接)才会被加入到输出中。

增加的构造函数代码使用 AspectWerkz 2.0 定义的一个签名来访问上下文信息,这些信息包括 为方面定义的所有参数。我将在用到这些参数时,展示如何在 XML 配置文件中定义 它们。

在实际的 trace() 方法开始的地方,我添加了检查触发器方法名与类 匹配的逻辑,如果知道调用不会打印出来,就直接执行 joinpoint 方法调用而不进行计时 (由于触发器方法不在调用的当前嵌套中,或者由于我们已经深入到嵌套中我们不感兴趣的地方)。 我还在基本计时代码中增加了一个 throwable 处理,这样,当方法由于一个抛出而退出而不是正常返回时, 在跟踪中就会加入基本抛出信息。

最后,我改变了跟踪输出的格式以使它更具可读性。这段代码不用空格缩进,而用连字号代替空格, 当方法调用是在嵌套的最深层时,它将在前面打印出星号而不是用于显示从嵌套级中退出的小于号(<)。

Linux 上的 AspectWerkz

我在 Linux 上运行 AspectWerkz 确实遇到过几个奇怪的小问题。首先,提供的 shell 脚本 在我的系统上不运行,因为它们使用 DOS 格式的行结束。我改了行结束后,它们可以在 1.0 和 2.0 RCX 版本上工作,但是不能工作在 1.1 上(我在这里不深究这个问题)。 希望这个问题在 2.0 版本时能修复。

其次,我遇到了 AspectWerkz 1.0 与 2.0 RC3 之间的兼容性问题,这看来是特定于 Linux 的,它使我在使用 AspectWerkz 2.0 时,不得不在 classpath 中加入 Axis 所引用 的所有 JAR 文件,即使其中一些 JAR(包括 Servlet API 定义)通常在客户机上是不需要的。 AspectWerkz 开发人员在 Windows 上再现不了这个问题,但是如果在 Linux 上使用 AspectWerkz 2.0,那么您可能也会遇到同样的问题。







分析 Axis

准备好了 清单 4 中新的和改进过的性能跟踪方面,就需要找一个目标应用程序 用这个方面显微镜进行检查。我选择了 Apache Axis Web 服务框架,运行一个客户机应用程序以避免让服务器代码复杂化。因为我的主要工作是 Web 服务,因此 Axis 很适合。我曾经至少花了几周时间分析过 Axis 代码,因此我对它的结构相当熟悉 —— 不过我也还记得第一次试图跟踪这些代码采用的通常很费解的路径时有多么 痛苦。

为照顾不与 Web 服务打交道的读者,下面是它的简单介绍。 Axis 是 Java Web 服务的 JAX-RPC 标准的实现。它支持客户机应用程序(它以 XML 消息的 格式发出请求)和服务器端应用程序(它对收到的请求生成 XML 响应消息), 大多数 Axis 代码是两端都使用的。由于 Web 消息的特性,框架的工作过程在高层上是很清楚的。 在客户机端,可以归纳如下:接受来自用户代码的请求的数据、发送请求并等待响应、 从响应中提取数据、最后将数据提交给用户代码。但是在 Axis 框架中这个过程的实际发生 细节却很容易让人迷惑。

我已经有一个在前面的 Web 服务性能测试中使用的 Axis 客户机应用程序(请参阅 参考资料 中的链接),因此在本月的文章中,我将对这个客户机应用程序使用性能跟踪方面。 我还要运行对应的 Axis 服务器应用程序,这样客户机就可以有响应,但是不会包括在性能跟踪中。 这个应用程序的实际目的是从服务器上提取地震数据库中的信息。

高层视图

初步观察 Axis 执行时,我想看看基本时间分布,这可让我了解代码的哪些部分执行的时间最长 (当然还有到达这些部分的路径)。要完成这个任务,我使用的方面配置将告诉它截获我的应用程序代码和 Axis 代码 中的所有方法调用,但是最大嵌套深度只到七 —— 这样我就不会被大量细节所淹没。 清单 5 给出了我将使用的 AspectWerkz 配置:


清单 5. 初步观察的配置
				
<aspectwerkz>
  <system id="AspectWerkzExample">
    <package name="com.sosnoski.aspectwerkz">
      <aspect class="TraceAspect">
        <pointcut name="traceexecution">
          execution(* org.apache.axis..*(..)) || execution(* com.sosnoski.seismic..*(..))
        </pointcut>
        <advice name="trace" type="around" bind-to="traceexecution"/>
        <param name="maximum-depth" value="7"/>
      </aspect>
    </package>
  </system>
</aspectwerkz>

上个月 文章的读者应当熟悉清单 5 配置中的大部分内容。所使用的配置的主要区别是我使用了更复杂的表达式来定义我的方面的切入点,并且增加了一个参数值来定义最大嵌套深度。在我以前的配置中, 使用了 pointcut 元素的属性来定义这个表达式,使用更长的 表达式可以更容易地使它成为元素的内容。AspectWerkz 允许互换地使用这两种形式。 切入点表达式表明我希望在 org.apache.axis 和 com.sosnoski.seismic 层次结构中加入包中的所有类中的所有方法调用。

在测试 Axis 时,我通常多次穿越(请求-响应交换)代码,这样,第一次穿越将处理 初始化,而后面的穿越就可以全速运行。在用性能跟踪方面测试时,我只使用两次穿越, 并且我将只显示第二次穿越的输出。清单 6 给出了使用 清单 5 配置 时,通过 AspectWerkz 运行这个程序的输出。注意,即使使用 nanoTime() 进行计时, 我还是将所有时间值转换为毫秒以保证数字大小适合于打印。


清单 6.初步观察性能跟踪
				
Running query for date range from 2002-04-02 13:12:53 to 2002-12-15 04:08:40,
  longitude range from 40.71031 to 160.91571,
  latitude range from -70.6799 to 22.293274,
 ->com.sosnoski.seismic.axislit.AxisDocLitClient.runQuery
 - >com.sosnoski.seismic.axislit.SeismicBindingStub.matchQuakes
 - ->org.apache.axis.client.Call.invoke
 - - >org.apache.axis.client.Call.invoke
 - - ->org.apache.axis.client.Call.invoke
 - - - *org.apache.axis.client.Call.invoke took 591.4 ms.
 - - - *org.apache.axis.message.RPCElement.getParams took 1447.0 ms.
 - - -<org.apache.axis.client.Call.invoke took 2042.0 ms.
 - - <org.apache.axis.client.Call.invoke took 2042.9 ms.
 - -<org.apache.axis.client.Call.invoke took 2043.1 ms.
 - ->org.apache.axis.client.Stub.extractAttachments
 - - >org.apache.axis.Message.getAttachments
 - - ->org.apache.axis.attachments.AttachmentsImpl.getAttachmentCount
 - - - *org.apache.axis.SOAPPart.saveChanges took 155.7 ms.
 - - -<org.apache.axis.attachments.AttachmentsImpl.getAttachmentCount took 157.5 ms.
 - - <org.apache.axis.Message.getAttachments took 157.7 ms.
 - -<org.apache.axis.client.Stub.extractAttachments took 157.9 ms.
 - <com.sosnoski.seismic.axislit.SeismicBindingStub.matchQuakes took 2202.4 ms.
 -<com.sosnoski.seismic.axislit.AxisDocLitClient.runQuery took 2202.9 ms.
Results from query:
Seismic region New Zealand Region has 1 regions and 2 matching quakes
Seismic region Bismarck and Solomon Islands has 4 regions and 30 matching quakes
Seismic region New Guinea has 9 regions and 55 matching quakes
Seismic region Caroline Islands Area has 2 regions and 8 matching quakes
Seismic region Guan to Japan has 2 regions and 10 matching quakes
Seismic region Taiwan Area has 1 regions and 2 matching quakes
Seismic region Philippine Islands has 5 regions and 22 matching quakes
Seismic region Bornea - Sulawesi has 10 regions and 37 matching quakes
Seismic region Sunda Arc has 12 regions and 49 matching quakes
Seismic region Myanmar and Southeast Asia has 1 regions and 2 matching quakes
Seismic region India - Xizand - Sichuan - Yunnan has 1 regions and 2 matching quakes
Seismic region Indian Ocean has 9 regions and 27 matching quakes
Seismic region Africa has 3 regions and 3 matching quakes
Seismic region Macquarie Loop has 1 regions and 2 matching quakes
Seismic region Andaman Islands to Sumatera has 4 regions and 14 matching quakes
 -*com.sosnoski.seismic.axislit.AxisDocLitClient.summarize took 1.7 ms.
Result match count 265 in 2203 ms.

我的客户机测试程序将结果打印到控制台,这样,它们就与清单 6 中的性能跟踪信息混在一起。忽略客户机程序输出后,跟踪信息明白地告诉我时间花在什么地方了 —— 总时间的 90% 多花在org.apache.axis.client.Call.invoke() 方法上。这个时间的不到 30% 花在用同一个名字调用另一个方法上(或者对同一方法的递归调用 —— 我从跟踪中无法区别),而所有剩下的时间实质上都花在了对org.apache.axis.message.RPCElement.getParams() 方法的调用上。

为开销买单

通过比较 清单 6 的最后一行中显示的请求的总执行时间(2203 毫秒)与不通过 AspectWerkz 运行的请求 的计时,我可以大致了解到我的监视对程序增加了多少开销。在本例中,大约是 1 倍 —— 我直接运行这个程序的时间一般是在 1000 到 1100 毫秒这个范围。

为什么这个程序使用了方面后,它的运行速度只有一半了呢?有两个不同的因素 增加了开销。第一个是由 AspectWerk 的字节码操作增加的,这使它可以在每当目标方法被 调用时调用我的方面。第二个是由计时代码和向控制台输出跟踪所增加的。

注释掉除了 trace() 方法体中 return join.proceed(); 以外的所有内容,以便惟一的开销就是截获方法调用所增加的,这样可以比较这两个组件。 结果它只比 清单 6 所显示的全部输出快约 10%, 因此我知道在这里几乎所有开销都是由于调用我的方面所产生的。

如果更好地选择切入点方法 —— 比如取消对只返回一个字段值 的简单 get 方法的截获,本来是可以减少这些开销的。这些简单的 get 方法可能构成了大量的方法调用,因此消除它们 有可能极大地降低开销。不幸的是,对于 Axis 代码,不能根据方法名统一地做到这一点,因为它遵守 标准 Java 语言的对简单和复杂的操作都使用 get 格式的名字的习惯做法。 AspectWerkz 在使用方面时,可以容易地区分简单的方法与至少进行一些处理的方法, 但是没有办法告诉它从切入点中排除这些方法。因此,我保留截获开销,除非我想要减少切入点中类的数量,或者 列出要从切入点中排除的各个方法。这两种方法都没有吸引力,因此我就留着这些开销,并观察 在我的方法垮掉之前能得到多少有用的信息。

深入挖掘

请参阅前面的 清单 6,总时间的 90% 以上都花在了 org.apache.axis.client.Call.invoke() 方法上。它自然成了更详细的性能跟踪的目标。 我将使用清单 7 配置获得更详细的视图:


清单 7. 深入挖掘配置
				
<aspectwerkz>
  <system id="AspectWerkzExample">
    <package name="com.sosnoski.aspectwerkz">
      <aspect class="TraceAspect">
        <pointcut name="traceexecution">
          execution(* org.apache.axis..*(..)) || execution(* com.sosnoski.seismic..*(..))
        </pointcut>
        <advice name="trace" type="around" bind-to="traceexecution"/>
        <param name="maximum-depth" value="5"/>
        <param name="trigger-method" value="org.apache.axis.client.Call.invoke"/>
      </aspect>
    </package>
  </system>
</aspectwerkz>

清单 7 配置保留了与以前一样的切入点定义,但是为方面增加了 一个触发器方法,并将最大嵌套深度减小到 5。清单 8 给出了这个跟踪配置 生成的输出(不带客户机程序输出,那些没有改变):


清单 8. 深入挖掘跟踪输出
				
>org.apache.axis.client.Call.invoke
 >org.apache.axis.client.Call.invoke
 ->org.apache.axis.client.Call.invoke
 - >org.apache.axis.client.Call.invoke
 - ->org.apache.axis.MessageContext.setTargetService
 - - *org.apache.axis.AxisEngine.getService took 14.9 ms.
 - -<org.apache.axis.MessageContext.setTargetService took 16.7 ms.
 - ->org.apache.axis.client.Transport.setupMessageContext
 - - *org.apache.axis.transport.http.HTTPTransport.setupMessageContextImpl took 2.8 ms.
 - -<org.apache.axis.client.Transport.setupMessageContext took 3.4 ms.
 - ->org.apache.axis.message.MessageElement.output
 - - *org.apache.axis.message.SOAPEnvelope.outputImpl took 5.8 ms.
 - -<org.apache.axis.message.MessageElement.output took 6.5 ms.
 - ->org.apache.axis.client.Call.invokeEngine
 - - *org.apache.axis.client.AxisClient.invoke took 572.3 ms.
 - -<org.apache.axis.client.Call.invokeEngine took 573.4 ms.
 - >org.apache.axis.client.Call.invoke took 602.6 ms.
 - >org.apache.axis.message.RPCElement.getParams
 - ->org.apache.axis.message.RPCElement.deserialize
 - - *org.apache.axis.message.MessageElement.publishToHandler took 1447.2 ms.
 - -<org.apache.axis.message.RPCElement.deserialize took 1449.0 ms.
 - <org.apache.axis.message.RPCElement.getParams took 1449.4 ms.
 -<org.apache.axis.client.Call.invoke took 2054.9 ms.
 <org.apache.axis.client.Call.invoke took 2057.1 ms.
<org.apache.axis.client.Call.invoke took 2057.4 ms.

这个更详细的执行时间视图开始展示一些有用的细节了。我现在可以看到对 org.apache.axis.client.Call.invoke() 最内部的调用是包装了 请求处理的调用,包括对服务器的调用。这个调用在返回之前必须等待服务器响应,因此显然 有大量处理是在跟踪所覆盖的范围之外进行的 —— 所有服务器代码都是在没有 AspectWerkz 的、单独的 JVM 中 运行的。真正的客户机处理负荷出现在 org.apache.axis.message.RPCElement.getParams() 调用中,我可以看到 实质上所有处理时间都花在对 org.apache.axis.message.MessageElement.publishToHandler() 的调用上。

超越极限

我要进一步深入挖掘,进入 org.apache.axis.message.MessageElement.publishToHandler() 方法, 清单 8 输出指出它是大部分客户机处理发生的地方。清单 9 显示了 完成这个任务的一个配置。在这里,我从切入点中去掉了客户机代码,因为对它不再感兴趣了, 我还将加入跟踪的方法调用的最小时间减小到 .4 毫秒(以纳秒表示)。最后,我改变了用来激活对 感兴趣的新方法的跟踪的触发器方法。


清单 9. 二次深入挖掘配置
				
<aspectwerkz>
  <system id="AspectWerkzExample">
    <package name="com.sosnoski.aspectwerkz">
      <aspect class="TraceAspect">
        <pointcut name="traceexecution"
          expression="execution(* org.apache.axis..*(..))"/>
        <advice name="trace" type="around" bind-to="traceexecution"/>
        <param name="minimum-time" value="400000"/>
        <param name="maximum-depth" value="5"/>
        <param name="trigger-method" value="org.apache.axis.message.MessageElement.publishToHandler"/>
      </aspect>
    </package>
  </system>
</aspectwerkz>

清单 10 显示了清单 9 配置生成的输出的一个示例。完整的输出非常长,且 有很多重复,因此我只包括了一个示例。在这里分析 Axis XML 消息处理的细节,以及 各个方法调用所用的时间并没有多大意义 —— 它只是在处理消息的各个组件时所做 的调用的数量叠加。


清单 10. 二次深入挖掘跟踪输出
				
>org.apache.axis.message.MessageElement.publishToHandler
 >org.apache.axis.message.SAX2EventRecorder.replay
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - *org.apache.axis.message.RPCHandler.onStartChild took 0.7 ms.
 - >org.apache.axis.encoding.ser.BeanDeserializer.startElement
 - ->org.apache.axis.encoding.DeserializerImpl.startElement
 - - *org.apache.axis.encoding.ser.BeanDeserializer.onStartElement took 12.8 ms.
 - -<org.apache.axis.encoding.DeserializerImpl.startElement took 13.1 ms.
 - <org.apache.axis.encoding.ser.BeanDeserializer.startElement took 13.1 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 15.9 ms.
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - *org.apache.axis.encoding.ser.BeanDeserializer.onStartChild took 0.6 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 1.0 ms.
 -*org.apache.axis.encoding.DeserializationContext.startElement took 0.6 ms.
 -*org.apache.axis.encoding.DeserializationContext.endElement took 0.5 ms.
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - *org.apache.axis.encoding.ser.BeanDeserializer.onStartChild took 0.5 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 0.9 ms.
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - >org.apache.axis.encoding.ser.BeanDeserializer.onStartChild
 - -*org.apache.axis.encoding.DeserializationContext.getTypeFromAttributes took 1.3 ms.
 - <org.apache.axis.encoding.ser.BeanDeserializer.onStartChild took 1.9 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 2.5 ms.
 -*org.apache.axis.encoding.DeserializationContext.endElement took 0.6 ms.
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - *org.apache.axis.encoding.ser.BeanDeserializer.onStartChild took 0.4 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 0.8 ms.
 ->org.apache.axis.encoding.DeserializationContext.startElement
 - >org.apache.axis.encoding.ser.BeanDeserializer.startElement
 - ->org.apache.axis.encoding.DeserializerImpl.startElement
 - - *org.apache.axis.encoding.ser.BeanDeserializer.onStartElement took 121.8 ms.
 - -<org.apache.axis.encoding.DeserializerImpl.startElement took 123.3 ms.
 - <org.apache.axis.encoding.ser.BeanDeserializer.startElement took 123.3 ms.
 -<org.apache.axis.encoding.DeserializationContext.startElement took 123.9 ms.
 ->org.apache.axis.encoding.DeserializationContext.endElement
 - *org.apache.axis.encoding.DeserializerImpl.endElement took 0.5 ms.
 -<org.apache.axis.encoding.DeserializationContext.endElement took 0.8 ms.
 ...

清单 10 展示了这个性能跟踪方面的作用的局限性。它可以很快地让您了解 应用程序中最花费时间的部分,这很不错,但是在实际找出哪些代码运行得慢并且加以改进 这方面不是很有用。不过,后一个问题用常规的执行 profiler 可以很好地解决,因此 不会错过什么。性能跟踪和执行 profiler 可以成为分析和改进应用程序性能的有用工具。




结束语

本月专栏展示了跟踪型的方面如何提供关于程序执行的有用信息。它们可以取代嵌入到程序代码 中的日志语句吗?在我看来,还不能全部取代,因为日志通常用于记录开发人员或者用户感兴趣的特定信息, 这些信息只能在方法的逻辑中访问(在这些地方不能定义切入点)。出于性能考虑,禁止日志后, 甚至可能不会生成这些信息。我在本月专栏中分析的 Axis 代码包含许多提供了这种特殊信息的日志语句。

其他类型的日志语句由简单的方法进入和退出信息组成。用一个可以在运行时激活的外部方面 可以容易地替代它们。这个方面可以设计为使用嵌入日志代码所使用的同样日志框架, 这样合在一起的输出就可以给出程序执行的综合视图。我不准备写一个这样的方面,但是希望 本专栏的这些文章可以使您更清楚如何在自己的应用程序中使用这种方法。

下个月我将分析 ASM 字节码操作框架。ASM 是我在关于 classworking 的以前的文章中讨论过的 Javassist 和 BCEL 框架的另一种选择。 它提出了一些令人印象深刻的性能要求(claim),因此我将分析性能和易用性,重写了为 Javassist 和 BCEL 所写的一些例子以在 ASM 中使用。

http://www.ibm.com/developerworks/cn/java/j-cwt04055.html

分享到:
评论

相关推荐

    《永磁无刷直流电机控制系统与软件综合研究-集成电机计算软件、电机控制器及电磁设计软件的创新设计与实践》,永磁无刷直流电机计算与控制软件:高效电机控制器与电磁设计工具,永磁无刷直流电机计算软件,电机控

    《永磁无刷直流电机控制系统与软件综合研究——集成电机计算软件、电机控制器及电磁设计软件的创新设计与实践》,永磁无刷直流电机计算与控制软件:高效电机控制器与电磁设计工具,永磁无刷直流电机计算软件,电机控制器,无刷电机设计软件,电机电磁设计软件 ,永磁无刷直流电机计算软件; 电机控制器; 无刷电机设计软件; 电机电磁设计软件,无刷电机设计专家:永磁无刷直流电机计算与控制器设计软件

    新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及控制策略,MBD电控开发 新能源汽车大势所

    新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及策略详解:从控制策略到软件设计全面解析,新能源汽车VCU开发模型及控制策略,MBD电控开发 新能源汽车大势所向,紧缺VCU电控开发工程师,特别是涉及新能源三电系统,工资仅仅低于无人驾驶、智能驾驶岗位。 ——含控制策略模型 整车控制策略详细文档 通讯协议文档 接口定义 软件设计说明文档 等(超详细,看懂VCU电控策略开发就通了) 内容如下: 新能源汽车整车控制器VCU学习模型,适用于初学者。 1、模型包含高压上下电,行驶模式管理,能量回馈,充电模式管理,附件管理,远程控制,诊断辅助功能。 2、软件说明书(控制策略说明书) 3、模型有部分中文注释 对想着手或刚开始学习整车控制器自动代码生成或刚接触整车控制器有很大帮助。 ,新能源汽车VCU开发模型; 控制策略; MBD电控开发; 模型学习; 代码生成; 整车控制器; 能量回馈; 诊断辅助功能,新能源汽车电控开发详解:VCU控制策略模型及学习手册

    Python读取Excel文件的方法详解及应用场景

    内容概要:本文详细介绍了两种利用 Python 读取 Excel 文件的不同方法,分别是基于 pandas 和 openpyxl。对于想要利用Python 处理 Excel 数据的读者来说,文中不仅提供了简洁明了的具体代码片段以及执行效果展示,还针对每个库的应用特性进行了深度解析。此外,文档提到了一些进阶应用技巧如只读特定的工作薄、过滤某些列等,同时强调了需要注意的地方(像是路径设置、engine 参数调整之类),让读者可以在面对实际项目需求时做出更加明智的选择和技术选型。 适合人群:对 Python 有基本掌握并希望提升数据读取能力的开发人员。 使用场景及目标:适用于任何涉及到批量数据导入或是与 Excel 进行交互的业务流程。无论是做初步的数据探索还是深入挖掘隐藏于电子表格背后的故事,亦或是仅为了简化日常办公自动化任务都可以从中受益。最终目标帮助使用者熟悉两大主流 Excel 解决方案的技术特性和最佳实践。 阅读建议:本文既是一份详尽的学习指南也是一份方便随时查阅的手册。因此初学者应当认真研究所提供的示例,而有一定经验者也可以快速定位到感兴趣的部分查看关键要点。

    毕设springboot基于springboot的医护人员排班系统.zip

    # 医护人员排班系统 ## 1. 项目介绍 本系统是一个基于SpringBoot框架开发的医护人员排班管理系统,用于医院管理医护人员的排班、调班等工作。系统提供了完整的排班管理功能,包括科室管理、人员管理、排班规则配置、自动排班等功能。 ## 2. 系统功能模块 ### 2.1 基础信息管理 - 科室信息管理:维护医院各科室基本信息 - 医护人员管理:管理医生、护士等医护人员信息 - 排班类型管理:配置不同的排班类型(如:早班、中班、晚班等) ### 2.2 排班管理 - 排班规则配置:设置各科室排班规则 - 自动排班:根据规则自动生成排班计划 - 排班调整:手动调整排班计划 - 排班查询:查看各科室排班情况 ### 2.3 系统管理 - 用户管理:管理系统用户 - 角色权限:配置不同角色的操作权限 - 系统设置:管理系统基础配置 ## 3. 技术架构 ### 3.1 开发环境 - JDK 1.8 - Maven 3.6 - MySQL 5.7 - SpringBoot 2.2.2 ### 3.2 技术栈 - 后端框架:SpringBoot - 持久层:MyBatis-Plus - 数据库:MySQL - 前端框架:Vue.js - 权限管理:Spring Security ## 4. 数据库设计 主要数据表: - 科室信息表(keshixinxi) - 医护人员表(yihurengyuan) - 排班类型表(paibanleixing) - 排班信息表(paibanxinxi) - 用户表(user) ## 5. 部署说明 ### 5.1 环境要求 - JDK 1.8+ - MySQL 5.7+ - Maven 3.6+ ### 5.2 部署步骤 1. 创建数据库并导入SQL脚本 2. 修改application.yml中的数据库配置 3. 执行maven打包命令:mvn clean package 4. 运行jar包:java -jar xxx.jar ## 6. 使用说明 ### 6.1 系统登录 - 管理员账号:admin - 初始密码:admin ### 6.2 基本操作流程 1. 维护基础信息(科室、人员等) 2. 配置排班规则 3. 生成排班计划 4. 查看和调整排班 ## 7. 注意事项 1. 首次使用请及时修改管理员密码 2. 定期备份数据库 3. 建议定期检查和优化排班规则

    MATLAB仿真的夫琅禾费衍射强度图:圆孔、圆环、矩形孔定制研究,MATLAB仿真:夫琅禾费衍射强度图的可定制性-以圆孔、圆环及矩形孔为例的研究分析,MATLAB夫琅禾费衍射强度图仿真 圆孔,圆环

    MATLAB仿真的夫琅禾费衍射强度图:圆孔、圆环、矩形孔定制研究,MATLAB仿真:夫琅禾费衍射强度图的可定制性——以圆孔、圆环及矩形孔为例的研究分析,MATLAB夫琅禾费衍射强度图仿真 圆孔,圆环,矩形孔可定制。 ,MATLAB; 夫琅禾费衍射; 强度图仿真; 圆孔; 圆环; 矩形孔; 可定制。,MATLAB仿真夫琅禾费衍射强度图:定制孔型(圆孔/圆环/矩形)

    商道融绿ESG评级20241231.xlsx

    详细介绍及样例数据:https://blog.csdn.net/samLi0620/article/details/145652300

    基于Dugoff轮胎模型与B08-01基础建模的七自由度车辆动力学模型验证:利用MATLAB 2018及以上版本与CarSim 2020.0软件的仿真对比研究,基于Dugoff轮胎模型与B08-01框

    基于Dugoff轮胎模型与B08_01基础建模的七自由度车辆动力学模型验证:利用MATLAB 2018及以上版本与CarSim 2020.0软件的仿真对比研究,基于Dugoff轮胎模型与B08_01框架的七自由度车辆动力学模型验证——使用MATLAB 2018及以上版本与CarSim 2020.0软件进行仿真对比研究,七自由度车辆动力学模型验证(Dugoff轮胎模型,B08_01基础上建模) 1.软件: MATLAB 2018以上;CarSim 2020.0 2.介绍: 基于Dugoff轮胎模型和车身动力学公式,搭建7DOF车辆动力学Simulink模型,对相关变量(质心侧偏角,横摆角速度,纵、横向速度及加速度)进行CarSim对比验证。 ,核心关键词:七自由度车辆动力学模型验证; Dugoff轮胎模型; B08_01建模基础; MATLAB 2018以上; CarSim 2020.0; Simulink模型; 变量对比验证。,基于Dugoff轮胎模型的七自由度车辆动力学模型验证与CarSim对比

    【毕业设计】基于Java+servlet+jsp+css+js+mysql实现“转赚”二手交易平台_pgj.zip

    【毕业设计】基于Java+servlet+jsp+css+js+mysql实现“转赚”二手交易平台_pgj

    恋爱聊妹术V2小程序源码4.1.0多开版.zip

    微猫恋爱聊妹术小程序源码介绍: 微猫恋爱聊妹术小程序源码是一款全新升级的聊天工具,它采用全新主题和UI,完美支持分享朋友圈功能。同时,它的独立后台也进行了大规模更新,让操作更加简单。其中,课堂页面、搜索页面和子话术列表页面等,均增加了流量主展示,具有超多的功能。 安装教程: 您可以先加入微猫恋爱聊妹术小程序源码的赞助群,然后在群内找到魔方安装说明。根据源码编号找到相应的安装说明,非常详细,让您轻松完成安装。

    电气安装工程安全技术规程-蒋凯,杨华甫,马仲范,王清禄译;孙照森校;鞍钢工程技术编委会编.pdf

    电气安装工程安全技术规程_蒋凯,杨华甫,马仲范,王清禄译;孙照森校;鞍钢工程技术编委会编

    基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减MATLAB研究,基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减算法研究,基于copula的风光联合场

    基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减MATLAB研究,基于Copula函数的风光空间相关性联合场景生成与K-means聚类削减算法研究,基于copula的风光联合场景生成?K-means聚类并削减 MATLAB 由于目前大多数研究的是不计风光出力之间的相关性影响,但是地理位置相近的风电机组和光伏机组具有极大的相关性。 因此,采用 Copula 函数作为风电、光伏联合概率分布,生成风、光考虑空间相关性联合出力场景,在此基础上,基于Kmeans算法,分别对风光场景进行聚类,从而实现大规模场景的削减,削减到5个场景,最后得出每个场景的概率与每个对应场景相乘求和得到不确定性出力 ,基于Copula的风光联合场景生成; K-means聚类削减; 空间相关性; 概率分布; 场景削减,基于Copula与K-means的风光联合场景生成与削减研究

    模块化多电平变流器MMC的VSG控制技术研究:基于MATLAB-Simulink的仿真分析与定制实现-支持三相与任意电平数,构网型模块化多电平变流器MMC的VSG控制策略与仿真模型:三相负荷变动下的

    模块化多电平变流器MMC的VSG控制技术研究:基于MATLAB-Simulink的仿真分析与定制实现——支持三相与任意电平数,构网型模块化多电平变流器MMC的VSG控制策略与仿真模型:三相负荷变动下的虚拟同步发电机控制研究,构网型 模块化多电平变流器 MMC 的VSG控制 同步发电机控制 MATLAB–Simulink仿真模型,可按需求定制 10电平.14电平,任意电平可做。 三相MMC,采用VSG控制。 设置负荷变动,调整有功无功,保持电网电压和频率 ,构网型模块化多电平变流器; MMC的VSG控制; 虚拟同步发电机控制; MATLAB–Simulink仿真模型; 任意电平可做; 三相MMC; 负荷变动; 有功无功调整; 电网电压和频率保持。,基于VSG控制的模块化多电平变流器(MMC)的构网型仿真模型

    暗通道算法DCP-Python实现

    暗通道算法DCP-Python实现

    南师大实验室安全准入知识供学习

    南师大实验室安全准入知识供学习

    纯openMV寻迹小车.zip

    纯openMV寻迹小车.zip

    【毕业设计】基于Java mvc架构开发的完整购物网站.zip

    【毕业设计】基于Java mvc架构开发的完整购物网站

    以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手

    以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手。

    springboot医院信管系统--.zip

    springboot医院信管系统--

Global site tag (gtag.js) - Google Analytics