首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Spock性能问题

Spock性能问题
EN

Stack Overflow用户
提问于 2018-11-21 12:47:40
回答 1查看 705关注 0票数 4

在使用Spock实现的规范方面,我遇到了一些问题--我指的是执行时间。在深入研究这个问题之后,我注意到它与设置规范有某种关系--我指的不是特别的setup()方法。

在这个发现之后,我在规范中声明的所有字段中添加了@Shared注释,它的运行速度比以前快了2倍。然后,我想,性能问题可能与ConcurrentHashMaprandom*方法有关(来自commons-lang3),但事实并非如此。

最后,在绝望的情况下,我以下列方式装饰了规范中的所有字段:

代码语言:javascript
运行
复制
class EntryFacadeSpec extends Specification {

  static {
    println(System.currentTimeMillis())
  }
  @Shared
  def o = new Object()
  static {
    println(System.currentTimeMillis())
  }
  @Shared
  private salesEntries = new InMemorySalesEntryRepository()
  static {
    println(System.currentTimeMillis())
  }
  @Shared
  private purchaseEntries = new InMemoryPurchaseEntryRepository()
  static { 
    println(System.currentTimeMillis())
  }

  ...

有趣的是,不管哪个字段被声明为第一个字段,初始化该字段需要数百毫秒:

代码语言:javascript
运行
复制
1542801494583
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495046
1542801495046
1542801495046
1542801495046
1542801495047
1542801495047

有什么问题吗?如何节省这几百毫秒?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-11-21 17:32:51

TL;DR

在第一个静态块中调用println将初始化与Groovy相关的30k+对象。它至少需要50毫秒才能完成,这取决于我们测试的笔记本电脑的马力。

细节

我无法在数百毫秒的水平上再现这种滞后,但我能够得到30-80毫秒之间的延迟。让我们从我在本地测试中使用的类开始,它复制了您的用例。

代码语言:javascript
运行
复制
import spock.lang.Shared
import spock.lang.Specification

class EntryFacadeSpec extends Specification {

    static {
        println("${System.currentTimeMillis()} - start")
    }

    @Shared
    def o = new Object()

    static {
        println("${System.currentTimeMillis()} - object")
    }

    @Shared
    private salesEntries = new InMemorySalesEntryRepository()

    static {
        println("${System.currentTimeMillis()} - sales")
    }

    @Shared
    private purchaseEntries = new InMemoryPurchaseEntryRepository()

    static {
        println("${System.currentTimeMillis()} - purchase")
    }

    def "test 1"() {
        setup:
        System.out.println(String.format('%d - test 1', System.currentTimeMillis()))

        when:
        def a = 1

        then:
        a == 1
    }

    def "test 2"() {
        setup:
        System.out.println(String.format('%d - test 2', System.currentTimeMillis()))

        when:
        def a = 2

        then:
        a == 2
    }

    static class InMemorySalesEntryRepository {}

    static class InMemoryPurchaseEntryRepository {}
}

现在,当我运行它时,我在控制台中看到了类似的东西。

代码语言:javascript
运行
复制
1542819186960 - start
1542819187019 - object
1542819187019 - sales
1542819187019 - purchase
1542819187035 - test 1
1542819187058 - test 2

我们可以看到在两个第一个静态块之间有59毫秒的延迟。这两个块之间有什么关系并不重要,因为Groovy编译器将这4个静态块合并成一个静态块,在普通Java中如下所示:

代码语言:javascript
运行
复制
static {
    $getCallSiteArray()[0].callStatic(EntryFacadeSpec.class, new GStringImpl(new Object[]{$getCallSiteArray()[1].call(System.class)}, new String[]{"", " - start"}));
    $getCallSiteArray()[2].callStatic(EntryFacadeSpec.class, new GStringImpl(new Object[]{$getCallSiteArray()[3].call(System.class)}, new String[]{"", " - object"}));
    $getCallSiteArray()[4].callStatic(EntryFacadeSpec.class, new GStringImpl(new Object[]{$getCallSiteArray()[5].call(System.class)}, new String[]{"", " - sales"}));
    $getCallSiteArray()[6].callStatic(EntryFacadeSpec.class, new GStringImpl(new Object[]{$getCallSiteArray()[7].call(System.class)}, new String[]{"", " - purchase"}));
}

因此,这59毫秒的延迟发生在前两行之间。让我们在第一行中放置一个断点并运行调试器。

让我们跨过这一行到下一行,看看会发生什么:

我们可以看到,调用Groovy的println("${System.currentTimeMillis()} - start")导致在JVM中创建了超过30k的对象。现在,让我们跳过第二行到第三行,看看会发生什么:

只创建了几个对象。

这个例子显示了添加

代码语言:javascript
运行
复制
static {
    println(System.currentTimeMillis())
}

增加了测试设置的意外复杂性,它并不显示两个类方法的初始化之间存在延迟,但它造成了这种滞后。但是,初始化所有Groovy相关对象的成本是我们无法完全避免的,必须在某个地方支付。例如,如果我们将测试简化为如下内容:

代码语言:javascript
运行
复制
import spock.lang.Specification

class EntryFacadeSpec extends Specification {

    def "test 1"() {
        setup:
        println "asd ${System.currentTimeMillis()}"
        println "asd ${System.currentTimeMillis()}"

        when:
        def a = 1

        then:
        a == 1
    }

    def "test 2"() {
        setup:
        System.out.println(String.format('%d - test 2', System.currentTimeMillis()))

        when:
        def a = 2

        then:
        a == 2
    }
}

我们在第一个println语句中放置一个断点,然后转到下一个语句,我们将看到如下所示:

它仍然创建了数千个对象,但比第一个示例中的对象少得多,因为我们在第一个示例中看到的大多数对象都是在Spock执行第一个方法之前创建的。

超频史波克测试性能

我们可以做的第一件事是使用静态编译。在我的简单测试中,它大约将执行时间从300 ms (非静态编译)缩短到227 ms。此外,必须初始化的对象数量也显著减少。如果我运行的调试器场景与上面使用@CompileStatic添加的最后一个调试器场景相同,我将得到如下内容:

它仍然非常重要,但是我们看到初始化以调用println方法的对象的数量被删除了。

最后值得一提的是。当我们使用静态编译,并且我们希望避免调用类静态块中的Groovy方法来打印一些输出时,我们可以使用以下组合:

代码语言:javascript
运行
复制
System.out.println(String.format("...", args))

因为Groovy就是这样执行的。另一方面,Groovy中的代码如下:

代码语言:javascript
运行
复制
System.out.printf("...", args)

可能与前一个类似,但它会被编译成类似于这样的内容(启用静态编译):

代码语言:javascript
运行
复制
DefaultGroovyMethods.printf(System.out, "...", args)

第二种情况在类静态块中使用时要慢得多,因为此时Groovy还没有加载,类加载器必须解析jar文件中的DefaultGroovyMethods类。当Spock执行测试方法时,如果使用System.out.printlnDefaultGroovyMethods.printf,就不会有太大的区别,因为Groovy类已经加载了。

这就是为什么如果我们将您的初始示例改写为如下所示:

代码语言:javascript
运行
复制
import groovy.transform.CompileStatic
import spock.lang.Shared
import spock.lang.Specification

@CompileStatic
class EntryFacadeSpec extends Specification {

    static {
        System.out.println(String.format('%d - start', System.currentTimeMillis()))
    }

    @Shared
    def o = new Object()

    static {
        System.out.println(String.format('%d - object', System.currentTimeMillis()))
    }

    @Shared
    private salesEntries = new InMemorySalesEntryRepository()

    static {
        System.out.println(String.format('%d - sales', System.currentTimeMillis()))
    }

    @Shared
    private purchaseEntries = new InMemoryPurchaseEntryRepository()

    static {
        System.out.println(String.format('%d - purchase', System.currentTimeMillis()))
    }

    def "test 1"() {
        setup:
        System.out.println(String.format('%d - test 1', System.currentTimeMillis()))

        when:
        def a = 1

        then:
        a == 1
    }

    def "test 2"() {
        setup:
        System.out.println(String.format('%d - test 2', System.currentTimeMillis()))

        when:
        def a = 2

        then:
        a == 2
    }

    static class InMemorySalesEntryRepository {}

    static class InMemoryPurchaseEntryRepository {}

}

我们将得到以下控制台输出:

代码语言:javascript
运行
复制
1542821438552 - start
1542821438552 - object
1542821438552 - sales
1542821438552 - purchase
1542821438774 - test 1
1542821438786 - test 2

但是更重要的是,它不记录字段初始化时间,因为Groovy将这4个块编译成这样的单个块:

代码语言:javascript
运行
复制
static {
    System.out.println(String.format("%d - start", System.currentTimeMillis()));
    Object var10000 = null;
    System.out.println(String.format("%d - object", System.currentTimeMillis()));
    var10000 = null;
    System.out.println(String.format("%d - sales", System.currentTimeMillis()));
    var10000 = null;
    System.out.println(String.format("%d - purchase", System.currentTimeMillis()));
    var10000 = null;
}

在第1次调用和第2次调用之间没有任何延迟,因为此时不需要加载Groovy类。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53412363

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档