スポックパフォーマンスの問題

オパール

Spockに実装された仕様のパフォーマンスに問題があります。特に実行時間です。問題を掘り下げた後、それがスペックの設定に何らかの形で関係していることに気づきました-私はsetup()特に方法を意味するものではありません

この発見の後@Shared、仕様で宣言されているすべてのフィールドに注釈を追加しました。これは、以前の2倍の速度で実行されます。その後、私は思った、そのパフォーマンスの問題がに関連することができるConcurrentHashMaprandom*(コモンズ-lang3から)メソッドが、それはそうではありませんでした。

結局、必死になって、仕様のすべてのフィールドを次のように装飾しました。

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())
  }

  ...

興味深いことに、どのフィールドが最初のフィールドとして宣言されていても、フィールドを初期化するのに数百ミリ秒かかります。

1542801494583
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495045
1542801495046
1542801495046
1542801495046
1542801495046
1542801495047
1542801495047

どうしたの?これを数百ミリ秒節約する方法は?

Szymon Stepniak

TL; DR

println最初の静的ブロックを呼び出すと、Groovy DevelopmentKitに関連する約30k以上のオブジェクトが初期化されます。このテストを実行するラップトップの馬力によっては、完了するまでに最低50ミリ秒かかる場合があります。

詳細

数百ミリ秒のレベルでラグを再現することはできませんでしたが、30〜80ミリ秒のラグを取得することができました。ユースケースを再現するローカルテストで使用したクラスから始めましょう。

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 {}
}

これを実行すると、コンソールに次のようなものが表示されます。

1542819186960 - start
1542819187019 - object
1542819187019 - sales
1542819187019 - purchase
1542819187035 - test 1
1542819187058 - test 2

最初の2つの静的ブロック間で59ミリ秒の遅延が見られます。Groovyコンパイラは、これら4つの静的ブロックすべてをプレーンJavaでは次のような単一の静的ブロックにマージするため、これら2つのブロックの間に何があるかは関係ありません。

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ミリ秒の遅延は2つの最初の行の間で発生します。最初の行にブレークポイントを設定して、デバッガーを実行してみましょう。

ここに画像の説明を入力してください

この行を次の行にステップオーバーして、何が起こるか見てみましょう。

ここに画像の説明を入力してください

Groovyを呼び出すprintln("${System.currentTimeMillis()} - start")と、JVMに3万を超えるオブジェクトが作成されたことがわかります。それでは、2行目から3行目にステップオーバーして、何が起こるかを見てみましょう。

ここに画像の説明を入力してください

あと数個のオブジェクトだけが作成されました。

この例は、追加することを示しています

static {
    println(System.currentTimeMillis())
}

adds accidental complexity to the test setup and it does not show there is a lag between initialization of two class methods, but it creates this lag. However, the cost of initializing all Groovy related objects is something we can't completely avoid and it has to be paid somewhere. For instance, if we simplify the test to something like this:

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
    }
}

and we put a breakpoint in the first println statement and step over to the next one, we will see something like this:

ここに画像の説明を入力してください

It still creates a few thousands of objects, but it is much less than in the first example because most of the objects we saw in the first example were already created before Spock executed the first method.

Overclocking Spock test performance

One of the first things we can do is to use static compilation. In case of my simple test it reduced execution time from 300 ms (non static compilation) to 227 ms approximately. Also the number of objects that have to be initialized is significantly reduced. If I run the same debugger scenario as the last one shown above with @CompileStatic added, I will get something like this:

ここに画像の説明を入力してください

It is still pretty significant, but we see that the number of objects initialized to invoke println method was dropped.

And the last thing worth mentioning. When we use static compilation and we want to avoid calling Groovy methods in the class static block to print some output we can use a combination of:

System.out.println(String.format("...", args))

because Groovy executes exactly this. On the other hand, following code in Groovy:

System.out.printf("...", args)

前のものと似ているように見えるかもしれませんが、次のようにコンパイルされます(静的コンパイルが有効になっています):

DefaultGroovyMethods.printf(System.out, "...", args)

この時点ではGroovyjarがまだロードされておらず、クラスローダーがDefaultGroovyMethodsJARファイルからクラスを解決する必要があるため、2番目のケースはクラス静的ブロックで使用するとはるかに遅くなります。Spockがテストメソッドを実行するとき、Groovyクラスはすでにロードされているため、System.out.printlnまたはを使用しても大きな違いはありませんDefaultGroovyMethods.printf

そのため、最初の例を次のように書き直します。

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 {}

}

次のコンソール出力が得られます。

1542821438552 - start
1542821438552 - object
1542821438552 - sales
1542821438552 - purchase
1542821438774 - test 1
1542821438786 - test 2

しかし、もっと重要なことは、Groovyがこれらの4つのブロックを次のように単一のブロックにコンパイルするため、フィールドの初期化時間をログに記録しないことです。

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;
}

この時点でGroovyクラスをロードする必要がないため、1回目と2回目の呼び出しの間にラグはありません。

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

パフォーマンスの問題

分類Dev

スタック-scalaの実装/パフォーマンスの問題

分類Dev

デスクトップのパフォーマンスの問題

分類Dev

ディスクI / Oパフォーマンスの問題

分類Dev

965gmGPUでのクラッターパフォーマンスの問題

分類Dev

SpringバッチJPAItemReaderのパフォーマンスの問題

分類Dev

MongoDBバッチ更新のパフォーマンスの問題

分類Dev

RegexクエリのMongoDBパフォーマンスの問題

分類Dev

再帰クエリのパフォーマンスの問題

分類Dev

MongoDBクエリのパフォーマンスの問題

分類Dev

ベクトルのパフォーマンスの問題

分類Dev

linqクエリのパフォーマンスの問題

分類Dev

Reactフックスクロールイベントのパフォーマンスの問題

分類Dev

再帰的なORMクラスでのSpringリポジトリのパフォーマンスの問題

分類Dev

OpenGlフレームバッファのパフォーマンスの問題

分類Dev

パッケージの公開中のパフォーマンスの問題

分類Dev

反応-Androidでのアポロパフォーマンスの問題

分類Dev

UITableViewのスクロールパフォーマンスの問題iOS7

分類Dev

RaspberryPi上のJavaのスレッドパフォーマンスの問題

分類Dev

Sharepointワークフローのパフォーマンスの問題

分類Dev

変数でインデックスを作成するときのSD []のパフォーマンスの問題

分類Dev

Javaリフレクションのパフォーマンスの問題

分類Dev

機能コンポーネントの小道具としてコールバックを渡す際のReactパフォーマンスの問題

分類Dev

bashスクリプトのパフォーマンスの問題

分類Dev

UIKitダイナミクスのパフォーマンスの問題

分類Dev

「select」SQLiteリクエストのパフォーマンスの問題

分類Dev

メソッドチェーンのパフォーマンスの問題

分類Dev

NestJsテストのパフォーマンスの問題

分類Dev

システムのパフォーマンスの問題

Related 関連記事

  1. 1

    パフォーマンスの問題

  2. 2

    スタック-scalaの実装/パフォーマンスの問題

  3. 3

    デスクトップのパフォーマンスの問題

  4. 4

    ディスクI / Oパフォーマンスの問題

  5. 5

    965gmGPUでのクラッターパフォーマンスの問題

  6. 6

    SpringバッチJPAItemReaderのパフォーマンスの問題

  7. 7

    MongoDBバッチ更新のパフォーマンスの問題

  8. 8

    RegexクエリのMongoDBパフォーマンスの問題

  9. 9

    再帰クエリのパフォーマンスの問題

  10. 10

    MongoDBクエリのパフォーマンスの問題

  11. 11

    ベクトルのパフォーマンスの問題

  12. 12

    linqクエリのパフォーマンスの問題

  13. 13

    Reactフックスクロールイベントのパフォーマンスの問題

  14. 14

    再帰的なORMクラスでのSpringリポジトリのパフォーマンスの問題

  15. 15

    OpenGlフレームバッファのパフォーマンスの問題

  16. 16

    パッケージの公開中のパフォーマンスの問題

  17. 17

    反応-Androidでのアポロパフォーマンスの問題

  18. 18

    UITableViewのスクロールパフォーマンスの問題iOS7

  19. 19

    RaspberryPi上のJavaのスレッドパフォーマンスの問題

  20. 20

    Sharepointワークフローのパフォーマンスの問題

  21. 21

    変数でインデックスを作成するときのSD []のパフォーマンスの問題

  22. 22

    Javaリフレクションのパフォーマンスの問題

  23. 23

    機能コンポーネントの小道具としてコールバックを渡す際のReactパフォーマンスの問題

  24. 24

    bashスクリプトのパフォーマンスの問題

  25. 25

    UIKitダイナミクスのパフォーマンスの問題

  26. 26

    「select」SQLiteリクエストのパフォーマンスの問題

  27. 27

    メソッドチェーンのパフォーマンスの問題

  28. 28

    NestJsテストのパフォーマンスの問題

  29. 29

    システムのパフォーマンスの問題

ホットタグ

アーカイブ