diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml new file mode 100644 index 00000000000..79a7519f133 --- /dev/null +++ b/.github/workflows/benchmarks.yaml @@ -0,0 +1,83 @@ +name: Benchmarks +on: + pull_request_target: + branches: + - master + +concurrency: + group: ${{ format('{0}-{1}', github.workflow_ref, github.head_ref) }} + cancel-in-progress: true + +jobs: + benchmarks: + if: github.repository_owner == 'mybatis' + permissions: + contents: read + pull-requests: write # for benchmark comment + runs-on: ubuntu-latest + env: + COMMENT_FILE: ${{ github.workspace }}/benchmark-comment.md + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - id: get-master-sha + run: | + # getting exact master sha at the moment to reference it in the comment + echo "sha=$( curl -u "u:${{ github.token }}" https://api.github.com/repos/${{ github.repository }}/git/ref/heads/${{ github.base_ref }} | jq .object.sha | tr -d '"' )" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + ref: ${{ steps.get-master-sha.outputs.sha }} + path: benchmark-master + + - uses: actions/checkout@v4 + with: + # https://github.com/actions/checkout/issues/518#issuecomment-890401887 + ref: "${{ github.event.pull_request.merge_commit_sha }}" + path: benchmark-pull-request + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: zulu + + - name: Run benchmarks (pull-request) + working-directory: benchmark-pull-request + run: ./mvnw -B -V --no-transfer-progress jmh:benchmark -Dlicense.skip=true + + - name: Run benchmarks (master) + working-directory: benchmark-master + run: ./mvnw -B -V --no-transfer-progress jmh:benchmark -Dlicense.skip=true + + - name: Compose comment content + run: | + cat <> ${{ env.COMMENT_FILE }} + > [!NOTE] + > These results are affected by shared workloads on GitHub runners. Use the results only to detect possible regressions, but always rerun on more stable machine before making any conclusions! + + ### Benchmark results (pull-request, ${{ github.event.pull_request.head.sha }}) + \`\`\`text + $(cat benchmark-pull-request/target/benchmark-results.txt) + \`\`\` + + ### Benchmark results (${{ github.base_ref }}, ${{ steps.get-master-sha.outputs.sha }}) + \`\`\`text + $(cat benchmark-master/target/benchmark-results.txt) + \`\`\` + EOF + + - name: Find benchmark results comment + uses: peter-evans/find-comment@v3 + id: benchmark-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark results + + - name: Create or update comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.benchmark-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: ${{ env.COMMENT_FILE }} + edit-mode: replace \ No newline at end of file diff --git a/pom.xml b/pom.xml index d91ea2682e4..ffe422fa7db 100644 --- a/pom.xml +++ b/pom.xml @@ -137,6 +137,8 @@ 5.11.0 12.6.1.jre11 1.19.7 + 1.37 + 0.2.2 @@ -351,6 +353,20 @@ ${log4j.version} test + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + @@ -455,6 +471,17 @@ + + pw.krejci + jmh-maven-plugin + ${jmh.plugin.version} + + ${project.build.directory}/benchmark-results.txt + text + 1 + gc + + diff --git a/src/test/java/org/apache/ibatis/benchmarks/jmh/basic/BasicBlogBenchmark.java b/src/test/java/org/apache/ibatis/benchmarks/jmh/basic/BasicBlogBenchmark.java new file mode 100644 index 00000000000..2829c6baec3 --- /dev/null +++ b/src/test/java/org/apache/ibatis/benchmarks/jmh/basic/BasicBlogBenchmark.java @@ -0,0 +1,93 @@ +/* + * Copyright 2009-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.benchmarks.jmh.basic; + +import java.util.concurrent.TimeUnit; + +import javax.sql.DataSource; + +import org.apache.ibatis.BaseDataTest; +import org.apache.ibatis.binding.BoundAuthorMapper; +import org.apache.ibatis.binding.BoundBlogMapper; +import org.apache.ibatis.domain.blog.Author; +import org.apache.ibatis.domain.blog.Blog; +import org.apache.ibatis.domain.blog.Post; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Fork(1) +@Warmup(iterations = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +public class BasicBlogBenchmark { + + @State(Scope.Benchmark) + public static class SessionFactoryState { + + private SqlSessionFactory sqlSessionFactory; + + @Setup + public void setup() throws Exception { + DataSource dataSource = BaseDataTest.createBlogDataSource(); + BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DDL); + BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DATA); + + TransactionFactory transactionFactory = new JdbcTransactionFactory(); + Environment environment = new Environment("Production", transactionFactory, dataSource); + Configuration configuration = new Configuration(environment); + configuration.getTypeAliasRegistry().registerAlias(Blog.class); + configuration.getTypeAliasRegistry().registerAlias(Post.class); + configuration.getTypeAliasRegistry().registerAlias(Author.class); + configuration.addMapper(BoundBlogMapper.class); + configuration.addMapper(BoundAuthorMapper.class); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); + } + + public SqlSessionFactory getSqlSessionFactory() { + return sqlSessionFactory; + } + } + + @Benchmark + public Blog retrieveSingleBlogUsingConstructorWithResultMap(SessionFactoryState sessionFactoryState) { + try (SqlSession sqlSession = sessionFactoryState.getSqlSessionFactory().openSession()) { + final BoundBlogMapper mapper = sqlSession.getMapper(BoundBlogMapper.class); + return mapper.selectBlogUsingConstructorWithResultMap(1); + } + } + + @Benchmark + public Blog retrieveSingleBlog(SessionFactoryState sessionFactoryState) { + try (SqlSession sqlSession = sessionFactoryState.getSqlSessionFactory().openSession()) { + final BoundBlogMapper mapper = sqlSession.getMapper(BoundBlogMapper.class); + return mapper.selectBlog(1); + } + } +}