Skip to content
This repository was archived by the owner on Jan 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5cb33c9
Update .gitignore
expanded-for-real Jun 1, 2025
63313e4
initial commit for imprint-java
Jun 1, 2025
dd4fdbc
initial commit for imprint-java
expanded-for-real Jun 1, 2025
bce1d13
Add GitHub Actions CI workflow for automated testing
Jun 2, 2025
f5d90b5
Merge remote-tracking branch 'origin/dev' into dev
Jun 2, 2025
72c468f
Update GitHub Actions workflow to use upload-artifact@v4
Jun 2, 2025
468d682
Add Gradle wrapper validation to CI workflow
Jun 2, 2025
cf05b13
Fix gitignore to include gradle-wrapper.jar for CI
Jun 2, 2025
d0d7983
Force add gradle-wrapper.jar to repository
Jun 2, 2025
f2cdd1b
Update wrapper validation action to v3
Jun 2, 2025
57c8249
Fix Javadoc syntax errors and disable strict Javadoc checking
Jun 2, 2025
edb3057
Add JMH benchmark .bat and .sh for full suite benchmarking and perfor…
Jun 2, 2025
2853e3f
fix map serialization error in benchmark test and streamline ci file …
Jun 2, 2025
3a5a113
Add execute permissions back for gradlew in CI
Jun 2, 2025
50a288b
Add some more string based performance benchmarks and try to make str…
Jun 2, 2025
ea1c4c4
Merge pull request #2 from imprint-serde/faster-strings
expanded-for-real Jun 2, 2025
43cab28
second main commit to address initial commits
expanded-for-real Jun 3, 2025
fdb8a56
additional cleanup to address concerns in https://github.com/imprint-…
Jun 3, 2025
2e56688
minor style fixes
Jun 3, 2025
9353388
minor style fixes again
Jun 3, 2025
09d0377
minor style fixes on benchmark tests and supress unused
Jun 3, 2025
6209bb1
minor reordering
Jun 4, 2025
ace7c67
Merge branch 'main' into dev
Jun 4, 2025
12d2823
Merge Comparisons into dev branch (#8)
expanded-for-real Jun 5, 2025
2834dbb
Merge remote-tracking branch 'origin/main' into dev
Jun 5, 2025
83ed961
minor cleanup
Jun 5, 2025
a605b65
minor cleanup
Jun 5, 2025
aacddeb
minor cleanup
Jun 5, 2025
3bf81ad
Actually fixes offsets and read Byte Values for Maps and Arrays even …
Jun 5, 2025
7eaa6e9
change CI file to use JMH plugin to respect iteration and warmup valu…
Jun 5, 2025
32640cd
ok plugin didn't work apparently so reverting that and just reducing …
Jun 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 247 additions & 27 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,250 @@ jobs:
java-version: [11, 17, 21]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Run tests
run: ./gradlew test

- name: Run build
run: ./gradlew build
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Run tests
run: ./gradlew test

- name: Run build
run: ./gradlew build

benchmark:
runs-on: ubuntu-latest
needs: test
# Add explicit permissions for commenting on PRs
permissions:
contents: read
pull-requests: write
issues: write
# Only run benchmarks on main branch pushes and PRs to main to avoid excessive CI time
if: github.ref == 'refs/heads/main' || github.base_ref == 'main'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Create benchmark results directory
run: mkdir -p benchmark-results

- name: Run serialization benchmarks
run: |
./gradlew jmhRunSerializationBenchmarks
continue-on-error: true

- name: Run deserialization benchmarks
run: |
./gradlew jmhRunDeserializationBenchmarks
continue-on-error: true

- name: Run field access benchmarks
run: |
./gradlew jmhRunFieldAccessBenchmarks
continue-on-error: true

- name: Run size comparison benchmarks
run: |
./gradlew jmhRunSizeComparisonBenchmarks
continue-on-error: true
Comment on lines +98 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should run the comparison benchmarks on CI - they can be run on demand since we don't expect the performance of other systems to change when we commit to Imprint and the Imprint-specific benchmarks should catch regressions (which is the point of running things on each PR).

Not only does this slow down the PR builds, but I think there's some limit to the free GH plan on how many minutes you can run workflows for (though I need to double check that, it may only apply to private repos)

Copy link
Collaborator Author

@expanded-for-real expanded-for-real Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes my life way easier lol. I can reduce it to just a gradle task for ease of use to run locally and remove all the complex custom tasking


- name: Upload benchmark results
uses: actions/upload-artifact@v4
if: always()
with:
name: benchmark-results-${{ github.sha }}
path: benchmark-results/
retention-days: 30

- name: Comment benchmark results on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
try {
const fs = require('fs');
const path = require('path');

// Find the latest benchmark results file
const resultsDir = 'benchmark-results';
let latestFile = null;
let latestTime = 0;

if (fs.existsSync(resultsDir)) {
const files = fs.readdirSync(resultsDir);
for (const file of files) {
if (file.endsWith('.json')) {
const filePath = path.join(resultsDir, file);
const stats = fs.statSync(filePath);
if (stats.mtime.getTime() > latestTime) {
latestTime = stats.mtime.getTime();
latestFile = filePath;
}
}
}
}

if (latestFile) {
console.log(`📊 Found benchmark results: ${latestFile}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend having the minimal amount of scripting inside ci.yml and instead delegate it to a script that we can just run locally. See https://github.com/imprint-serde/imprint/blob/main/scripts/ci_bench.sh for an example, I generate the markdown within the script there and just call that script from the GHA workflow

const results = JSON.parse(fs.readFileSync(latestFile, 'utf8'));

// Group results by benchmark type
const serialization = results.filter(r => r.benchmark.includes('serialize'));
const deserialization = results.filter(r => r.benchmark.includes('deserialize'));
const fieldAccess = results.filter(r => r.benchmark.includes('singleFieldAccess'));
const sizes = results.filter(r => r.benchmark.includes('measure'));

// Format results into a table
const formatResults = (benchmarks, title) => {
if (benchmarks.length === 0) return '';

let table = `\n### ${title}\n\n| Library | Score (ns/op) | Error | Unit |\n|---------|---------------|-------|------|\n`;

benchmarks
.sort((a, b) => a.primaryMetric.score - b.primaryMetric.score)
.forEach(benchmark => {
const name = benchmark.benchmark.split('.').pop().replace(/serialize|deserialize|singleFieldAccess|measure/, '').replace(/Imprint|JacksonJson|Kryo|MessagePack|Avro|Protobuf|FlatBuffers/, (match) => match);
const score = benchmark.primaryMetric.score.toFixed(2);
const error = benchmark.primaryMetric.scoreError.toFixed(2);
const unit = benchmark.primaryMetric.scoreUnit;
table += `| ${name} | ${score} | ±${error} | ${unit} |\n`;
});

return table;
};

const comment = `## 📊 Benchmark Results

Benchmark comparison between Imprint and other serialization libraries:
${formatResults(serialization, 'Serialization Performance')}
${formatResults(deserialization, 'Deserialization Performance')}
${formatResults(fieldAccess, 'Single Field Access Performance')}
${formatResults(sizes, 'Serialized Size Comparison')}

<details>
<summary>View detailed results</summary>

Results generated from commit: \`${context.sha.substring(0, 7)}\`

Lower scores are better for performance benchmarks.

</details>`;

await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

console.log('✅ Successfully posted benchmark results to PR');
} else {
console.log('⚠️ No benchmark results found');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## 📊 Benchmark Results\n\nBenchmark execution completed but no results file was found. Check the [workflow logs](' +
`https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}` + ') for details.'
});
}
} catch (error) {
console.log('❌ Failed to post benchmark comment:', error.message);
console.log('📁 Benchmark results are still available in workflow artifacts');

// Try to post a simple error message
try {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 📊 Benchmark Results\n\n⚠️ Failed to process benchmark results automatically.\n\nResults are available in the [workflow artifacts](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`
});
} catch (commentError) {
console.log('❌ Also failed to post error comment:', commentError.message);
}
}

# Optional: Run full benchmark suite on releases
benchmark-full:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'

- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Create benchmark results directory
run: mkdir -p benchmark-results

- name: Run full benchmark suite
run: |
./gradlew jmhRunAllBenchmarks

- name: Upload full benchmark results
uses: actions/upload-artifact@v4
with:
name: full-benchmark-results-${{ github.ref_name }}
path: benchmark-results/
retention-days: 90
Loading