Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
417 commits
Select commit Hold shift + click to select a range
759e2ef
perf: reuse concat blessing eligibility
fglock Sep 11, 2026
20ad79a
docs: record post-change string profile
fglock Sep 11, 2026
9440d98
perf: lower small negative integer literals
fglock Sep 11, 2026
ad19649
perf: fast-path direct BMP substring offsets
fglock Sep 11, 2026
b33fbf8
docs: record loaded-host post-BMP portfolio
fglock Sep 11, 2026
68209a8
perf: reuse empty named-capture state
fglock Sep 11, 2026
84a6654
docs: reject empty named-capture map optimization
fglock Sep 11, 2026
314dba5
docs: record rejected regex map experiment
fglock Sep 11, 2026
88e96cc
docs: record loaded-host Life allocation profile
fglock Sep 11, 2026
b8639a1
docs: record rejected closure add-chain experiment
fglock Sep 11, 2026
86d259e
docs: record closure result-wrapper diagnostics
fglock Sep 11, 2026
e43229b
feat: identify PerlOnJava in version output
fglock Sep 10, 2026
793de5c
wip: snapshot before fixing issue #1308 scalar semantics
fglock Sep 8, 2026
e12e562
fix: restore MooX::Options list context and dynamic glob localization
fglock Sep 9, 2026
a3da65e
fix: implement undef-aware equality operators
fglock Sep 9, 2026
f0df2b4
fix(regex): safely publish recursive unmatched captures
fglock Sep 9, 2026
551f19c
fix(regex): bound pathological backtracking memory
fglock Sep 9, 2026
89abd24
fix(io): restore diamond ARGV compatibility
fglock Sep 10, 2026
fa0ca63
fix(runtime): preserve Unicode regex captures in interpolation
fglock Sep 10, 2026
1050371
wip: snapshot before issue #1269 compatibility fixes
fglock Sep 11, 2026
5418278
perf: establish benchmark authority for issue 1196
fglock Sep 8, 2026
29e111f
fix: preserve tie magic in utf8 conversion
fglock Sep 8, 2026
7cd0ff7
wip: snapshot before fixing issue #1308 scalar semantics
fglock Sep 8, 2026
9b65f49
perf: pool feature-free Joni matchers
fglock Sep 10, 2026
bfaff69
perf: rebind pooled Joni matchers across subjects
fglock Sep 10, 2026
f87eb60
perf: bound subject regex encoding cache
fglock Sep 10, 2026
3c369e3
perf: reuse empty named-capture state
fglock Sep 11, 2026
214d01b
docs: reject empty named-capture map optimization
fglock Sep 11, 2026
deb63e2
docs: record rejected CV warning-bit cache
fglock Sep 11, 2026
243fabf
perf: bypass call frame for guarded integer closure leaves
fglock Sep 11, 2026
5957e8f
docs: record direct leaf closure high-load portfolio
fglock Sep 11, 2026
75d5621
docs: record unstable direct leaf pair follow-up
fglock Sep 11, 2026
b4b20e7
docs: record method call-boundary profile selection
fglock Sep 11, 2026
18c33f1
docs: bound method frame setup budget
fglock Sep 11, 2026
c3e5e97
docs: record method frame allocation selection
fglock Sep 11, 2026
dd80e7d
perf: reuse guarded immediate-unpack method frames
fglock Sep 11, 2026
157d0e2
docs: record inconclusive method-frame measurement
fglock Sep 11, 2026
9fee8eb
docs: record method-frame JFR selection
fglock Sep 11, 2026
56a79e4
docs: record loaded-host portfolio baseline
fglock Sep 11, 2026
8466689
docs: define direct argument binding proof gates
fglock Sep 11, 2026
9ce7ffc
docs: record string workload JFR selection
fglock Sep 11, 2026
856b575
perf: bypass Unicode scans for byte-string substr
fglock Sep 11, 2026
f6e6b95
fix: resolve performance rebase integration conflicts
fglock Sep 11, 2026
4463f47
docs: record scalar result lifecycle audit
fglock Sep 11, 2026
409b8d5
test: define direct argument binding safety boundary
fglock Sep 11, 2026
f2cb72f
docs: record high-load method portfolio evidence
fglock Sep 11, 2026
50e12e6
docs: reject active-pad elision measurement
fglock Sep 11, 2026
b04be68
docs: define direct argument lowering contract
fglock Sep 11, 2026
a50d9eb
docs: record issue 1196 closure profile
fglock Sep 11, 2026
045378d
docs: reject direct leaf return bypass
fglock Sep 11, 2026
41109c9
perf: add guarded direct closure addition entry
fglock Sep 11, 2026
5d51e72
docs: record issue 1196 Life profile
fglock Sep 11, 2026
6532b61
perf: retain native unsigned bitwise results
fglock Sep 11, 2026
0a42d4e
docs: record paired issue 1196 measurements
fglock Sep 11, 2026
2c771b7
perf: cache direct closure captures until rebinding
fglock Sep 11, 2026
dee5215
docs: record full rebased issue 1196 portfolio
fglock Sep 11, 2026
c6ce570
docs: select method scalar churn from current JFR
fglock Sep 11, 2026
8ce21d7
docs: reject broad wide-UV bitwise conversion
fglock Sep 11, 2026
92e3294
docs: record source-matched regex JFR selection
fglock Sep 11, 2026
5334a6b
perf: scope Joni matcher pools to Perl runtimes
fglock Sep 11, 2026
b0824e7
perf: reject runtime-owned Joni matcher-pool lookup
fglock Sep 11, 2026
e0cf782
docs: attribute method lexical-copy allocation churn
fglock Sep 11, 2026
f88643a
test: guard direct method hash-update lowering
fglock Sep 11, 2026
fe82b49
perf: guard direct method hash-update lowering
fglock Sep 11, 2026
5441449
perf: reject direct method hash-update lowering
fglock Sep 11, 2026
d5dea91
docs: define method copy-cell lease boundary
fglock Sep 11, 2026
796a24f
docs: exclude shallow method lexical cell pooling
fglock Sep 11, 2026
b9937d4
docs: select method lexical representation from JFR
fglock Sep 11, 2026
ff156aa
perf: borrow guarded immediate method lexicals
fglock Sep 11, 2026
26d3144
perf: reject guarded method lexical borrowing
fglock Sep 12, 2026
7bd56a6
docs: select closure range-topic cost from JFR
fglock Sep 12, 2026
2572ef5
perf: reuse guarded direct-leaf range topics
fglock Sep 12, 2026
649dadd
perf: reject guarded direct-leaf range topics
fglock Sep 12, 2026
e8f4b4e
perf: avoid concat taint varargs allocation
fglock Sep 12, 2026
af9305e
docs: record rejected string concat allocation candidate
fglock Sep 12, 2026
70ebdfc
perf: fast-path ordinary string concatenation
fglock Sep 12, 2026
7860a91
docs: reject guarded string concat fast path
fglock Sep 12, 2026
85fbef1
docs: refresh method allocation selection
fglock Sep 12, 2026
285d53e
perf: avoid empty named-capture map allocation
fglock Sep 12, 2026
5246ed9
docs: reject repeated named-capture map optimization
fglock Sep 12, 2026
e48027f
docs: define method lexical-cell reuse contract
fglock Sep 12, 2026
c7541da
docs: record source-matched method performance baseline
fglock Sep 12, 2026
36addf9
perf: reuse exact method lexical copy cells
fglock Sep 12, 2026
0bcbbab
docs: reject frame-local method lexical-cell reuse
fglock Sep 12, 2026
3efe547
docs: record post-revert method allocation refresh
fglock Sep 12, 2026
9d8e5ed
docs: record stable current closure baseline
fglock Sep 12, 2026
aec8e08
docs: record dense method CPU selection
fglock Sep 12, 2026
adb4a76
perf: bypass disabled trace-owner monitors
fglock Sep 12, 2026
3c5b642
docs: reject disabled trace-owner monitor elision
fglock Sep 12, 2026
4ba7c55
fix: remove duplicated regex cache declarations after rebase
fglock Sep 12, 2026
9de503e
docs: record rebased high-load closure measurement
fglock Sep 12, 2026
9125b5d
perf: reuse scalar result pool slot
fglock Sep 12, 2026
161a8d5
docs: record current method performance attribution
fglock Sep 12, 2026
758c5e7
docs: record current full performance portfolio
fglock Sep 12, 2026
ad4417d
docs: record Life representation selection
fglock Sep 12, 2026
6b53d7a
docs: reject transient bitwise result reuse
fglock Sep 12, 2026
3e9d3a0
docs: define regex cursor snapshot ownership
fglock Sep 12, 2026
b54f613
fix(regex): preserve captures after terminal global match
fglock Sep 12, 2026
458aa5f
docs(perf): record loaded-host regex measurement
fglock Sep 12, 2026
b639293
docs(perf): refresh method allocation selection
fglock Sep 12, 2026
aeabe20
test(perf): cover direct argument-copy method semantics
fglock Sep 12, 2026
fbff92e
perf(runtime): guard direct argument-copy bindings
fglock Sep 12, 2026
96e5bf3
perf: lower proven immediate argument copies
fglock Sep 12, 2026
b5e0599
perf: admit safe hash-subscript argument copies
fglock Sep 12, 2026
6ad4f73
docs: record high-load argument-copy evidence
fglock Sep 12, 2026
24ae216
perf: measure direct argument-copy lowering reachability
fglock Sep 12, 2026
22bf74a
fix: remove duplicate regex cache declaration after rebase
fglock Sep 12, 2026
cae1396
docs: record rebased method attribution triage
fglock Sep 12, 2026
dd734d8
docs: record steady-state method attribution evidence
fglock Sep 12, 2026
7d253cf
perf: bypass concat blessing checks for plain scalars
fglock Sep 12, 2026
d9f28a3
docs: record plain concat fast-path selection
fglock Sep 12, 2026
0572e90
perf: reject unproven concat leaf fast path
fglock Sep 12, 2026
dce66e6
docs: summarize rejected concat measurement
fglock Sep 12, 2026
6f44b21
perf: recycle zero-capture regex cursors
fglock Sep 12, 2026
39f4727
perf: reject zero-capture regex cursor pool
fglock Sep 12, 2026
912974a
perf: avoid bigint allocation for native integer comparisons
fglock Sep 12, 2026
76d047b
perf: reject native integer comparison shortcut
fglock Sep 12, 2026
b9471b5
perf: reject direct closure result transfer
fglock Sep 12, 2026
ece1853
docs: record Life representation selection for #1196
fglock Sep 12, 2026
dee4687
test: cover read-only argument array copy boundaries
fglock Sep 12, 2026
4489022
test: preserve argument-array copy boundaries
fglock Sep 12, 2026
cfb1ea4
docs: record flat Life baseline for #1196
fglock Sep 12, 2026
9ec1f64
test: preserve bitwise not-and semantics
fglock Sep 12, 2026
acd2b4d
test: preserve existing array element store semantics
fglock Sep 12, 2026
0ccc0c2
docs: record bitwise-not fast-path measurement
fglock Sep 12, 2026
d4e504a
perf: return proven closure additions directly in scalar context
fglock Sep 12, 2026
d97a088
docs: record closure parity evidence for #1196
fglock Sep 12, 2026
9816c15
docs: record method guard-scan measurement
fglock Sep 12, 2026
3106998
docs: record authoritative #1196 portfolio baseline
fglock Sep 12, 2026
9320af2
docs: refresh #1196 method JFR selection
fglock Sep 12, 2026
2d69a3b
test: cover borrowed argument cleanup lifetime
fglock Sep 12, 2026
19f534e
docs: refresh #1196 string JFR selection
fglock Sep 12, 2026
aaa987f
docs: refresh #1196 regex JFR selection
fglock Sep 12, 2026
4e1056e
perf: fast-path current active lexical registration
fglock Sep 12, 2026
a0e1dbd
perf: reject active lexical frame shortcut
fglock Sep 12, 2026
bcc1be6
docs: define Life bitwise-tree lowering boundary
fglock Sep 12, 2026
5407b75
perf: stage native integer bitwise expression trees
fglock Sep 12, 2026
2176729
perf: reject staged Life bitwise lowering
fglock Sep 12, 2026
deee20c
docs: record current method structural attribution
fglock Sep 12, 2026
f64b423
perf: detach published regex capture cursors
fglock Sep 12, 2026
102b286
perf: reject regex cursor snapshot pool
fglock Sep 12, 2026
dbe0f18
docs: record rebased method call-boundary attribution
fglock Sep 12, 2026
7f90306
test: guard direct argument copies from tied observers
fglock Sep 12, 2026
db4c238
docs: select fused concat-substr expression boundary
fglock Sep 12, 2026
0f33320
perf: fuse eligible concat-substr snapshot expressions
fglock Sep 12, 2026
1f46c13
perf: reject regressive concat-substr lowering
fglock Sep 12, 2026
4c11a21
perf: disable unselectable direct argument-copy lowering
fglock Sep 12, 2026
2be2c5b
perf: reject removal of direct argument-copy guard
fglock Sep 12, 2026
7895074
perf: specialize guarded plain-hash integer methods
fglock Sep 12, 2026
b89e03c
docs: record guarded plain-hash method measurement
fglock Sep 12, 2026
4d90491
docs: record full guarded method portfolio
fglock Sep 12, 2026
0ed7044
docs: record Life call-boundary selection
fglock Sep 12, 2026
41dc576
perf: skip cleanup scan for proven plain arrays
fglock Sep 12, 2026
c3650ed
perf: reject regressive plain-array cleanup invariant
fglock Sep 12, 2026
b553cf9
docs: record Life call-frame attribution audit
fglock Sep 12, 2026
a5e9aa9
docs: clarify Life attribution priority
fglock Sep 12, 2026
1fabf62
docs: revalidate regex allocation boundary under load
fglock Sep 12, 2026
19b8822
perf: reuse unshared scalar global regex cursors
fglock Sep 12, 2026
6477dfb
docs: refresh string and Life performance boundaries
fglock Sep 12, 2026
fed92f3
docs: record incomplete high-load portfolio attempt
fglock Sep 12, 2026
aec6ab0
fix(bench): recover timed-out portfolio reader pipes
fglock Sep 12, 2026
553fe57
docs: record authoritative high-load portfolio
fglock Sep 13, 2026
e5a8bb5
docs: define safe whole-expression Life lowering boundary
fglock Sep 13, 2026
7d00a05
perf: lower guarded native word array expressions
fglock Sep 13, 2026
c89e6f8
perf: reject sub-material native word array lowering
fglock Sep 13, 2026
d8fd86e
docs: record native word lowering selection evidence
fglock Sep 13, 2026
673579a
docs: correct Life native word matcher evidence
fglock Sep 13, 2026
bcf5aa5
perf: lower guarded lexical word expressions
fglock Sep 13, 2026
d3bcf2c
docs: record #1196 lexical word lowering measurement
fglock Sep 13, 2026
d5ddfe8
docs: record post-word Life JFR selection
fglock Sep 13, 2026
a881855
docs: record full high-load #1196 portfolio
fglock Sep 13, 2026
7c920b4
docs: record #1196 string and regex JFR selection
fglock Sep 13, 2026
a1a708b
perf: avoid captureless regex region snapshots
fglock Sep 13, 2026
6faf91a
Revert "perf: avoid captureless regex region snapshots"
fglock Sep 13, 2026
41bbbf9
test: retain captureless regex publication oracle
fglock Sep 13, 2026
bfc82b2
docs: record rejected regex region allocation candidate
fglock Sep 13, 2026
25f6c36
perf: avoid scalar regex result-list allocation
fglock Sep 13, 2026
c2f2410
docs: record retained regex result-list measurement
fglock Sep 13, 2026
fed5306
perf: avoid compiled regex resolution wrapper
fglock Sep 13, 2026
97839d6
revert: avoid compiled regex resolution wrapper
fglock Sep 13, 2026
8d71722
docs: record rejected regex resolver measurement
fglock Sep 13, 2026
c027548
perf: specialize six and seven byte regex literals
fglock Sep 13, 2026
7476679
revert: specialize six and seven byte regex literals
fglock Sep 13, 2026
a621a0f
docs: record rejected exact-literal measurement
fglock Sep 13, 2026
38a7694
perf: batch single-byte regex map searches
fglock Sep 13, 2026
6561574
revert: batch single-byte regex map searches
fglock Sep 13, 2026
dbe4cb0
docs: record rejected regex map-search measurement
fglock Sep 13, 2026
de5203e
perf: batch generic regex exact-byte comparisons
fglock Sep 13, 2026
75af693
docs: record retained generic regex exact measurement
fglock Sep 13, 2026
187604e
docs: record rebased regex JFR selection
fglock Sep 13, 2026
6caeb84
perf: bypass package mutation for cached static regexes
fglock Sep 13, 2026
a05c9e8
revert: reject static regex package cache bypass
fglock Sep 13, 2026
e244e12
docs: record final-rebase string JFR selection
fglock Sep 13, 2026
c820946
perf: avoid two-argument substr varargs arrays
fglock Sep 13, 2026
1df199b
docs: record retained two-argument substr measurement
fglock Sep 13, 2026
2bd2c7e
fix: preserve shared object isolation across thread return
fglock Sep 13, 2026
59296c1
docs: record exact-head regex JFR selection
fglock Sep 13, 2026
97a5995
perf: reuse empty named capture state
fglock Sep 13, 2026
f3a3617
revert: reject empty named-capture map reuse
fglock Sep 13, 2026
6b83e2d
docs: record rejected empty capture map experiment
fglock Sep 13, 2026
2a11577
perf: specialize capture-free literal alternations
fglock Sep 13, 2026
6666ef0
fix: preserve Joni find conditions for literal alternations
fglock Sep 13, 2026
cdbf6a7
docs: record literal alternation portfolio evidence
fglock Sep 13, 2026
d00eff6
perf: fast-path plain UTF-8 string concatenation
fglock Sep 13, 2026
ebc6dfa
docs: record plain string concat comparison
fglock Sep 13, 2026
c54ee69
docs: record plain concat full portfolio
fglock Sep 13, 2026
65f555a
docs: record string regex life attribution
fglock Sep 13, 2026
e8fe150
perf: fast-path plain string integer concatenation
fglock Sep 13, 2026
e37bb9c
revert: reject string integer concat fast path
fglock Sep 13, 2026
564d3af
docs: record rejected string integer concat comparison
fglock Sep 13, 2026
e03d1f8
perf: search root literal alternations directly
fglock Sep 13, 2026
9d4964c
docs: focus performance handoff on forward experiments
fglock Sep 13, 2026
1a5923b
revert: reject direct literal alternation search
fglock Sep 13, 2026
613733e
docs: record rejected literal alternation search
fglock Sep 13, 2026
4c9c1ad
perf: fast-path plain byte-string concatenation
fglock Sep 13, 2026
ee775c0
docs: record retained byte-string concat fast path
fglock Sep 13, 2026
dca6704
docs: record byte concat integration portfolio
fglock Sep 13, 2026
8f86ac8
perf: fast-path plain byte-string integer concatenation
fglock Sep 13, 2026
8493c15
docs: record byte-string integer concat evidence
fglock Sep 13, 2026
f4b1a05
docs: record byte-string integer integration portfolio
fglock Sep 13, 2026
b5ea82d
perf: avoid void array assignment result allocation
fglock Sep 13, 2026
db14278
revert: reject void array assignment result elision
fglock Sep 13, 2026
6ff0aba
docs: focus performance handoff on forward execution
fglock Sep 13, 2026
698de67
perf: remove redundant native word store overhead
fglock Sep 13, 2026
1028005
revert: reject native word store micro-optimization
fglock Sep 13, 2026
3fd8f89
perf: reuse non-escaping lexical range cells
fglock Sep 13, 2026
bf3abaa
revert: reject lexical range cell reuse
fglock Sep 13, 2026
2740b26
perf: avoid literal-pad lock on cache hits
fglock Sep 13, 2026
3fb881c
revert: reject literal-pad lock-elision candidate
fglock Sep 14, 2026
61b5451
perf: retain Joni matcher within global cursor
fglock Sep 14, 2026
450abe5
revert: reject Joni global matcher retention
fglock Sep 14, 2026
ad05595
docs: exclude rejected regex micro-paths
fglock Sep 14, 2026
9c76c4e
docs: preserve retained byte concat paths in performance handoff
fglock Sep 14, 2026
96b936d
docs: record rejected direct regex cache candidate
fglock Sep 14, 2026
1c68f29
perf: lazily materialize scalar regex whole matches
fglock Sep 14, 2026
5aac377
docs: update performance handoff after lazy regex match work
fglock Sep 14, 2026
767f56d
perf: skip empty regex use-site warning resolution
fglock Sep 14, 2026
9d7ec7b
docs: record retained regex warning-path boundary
fglock Sep 14, 2026
adb8532
docs: record rejected regex cursor eligibility cache
fglock Sep 14, 2026
e9e59f4
docs: record current full performance portfolio
fglock Sep 14, 2026
fcc3e4e
docs: record rejected Life word-store candidate
fglock Sep 14, 2026
3cd42f1
docs: record rejected lexical range candidate
fglock Sep 14, 2026
5630653
docs: record rejected empty named capture candidate
fglock Sep 14, 2026
e8263a1
docs: record rejected private array transfer candidate
fglock Sep 14, 2026
f980d7b
docs: record rejected inline pos state candidate
fglock Sep 14, 2026
eeb5d25
docs: record rejected captureless region candidate
fglock Sep 14, 2026
b5d85d0
docs: record rejected literal search candidate
fglock Sep 14, 2026
7690167
docs: record rejected substr snapshot candidate
fglock Sep 14, 2026
b2fa0d9
docs: identify Life scalar transport boundary
fglock Sep 14, 2026
0ffe20f
perf: avoid unobservable leaf lexical bindings
fglock Sep 14, 2026
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
27 changes: 27 additions & 0 deletions dev/bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ perl dev/bench/benchmark_closure.pl
./jperl dev/bench/benchmark_closure.pl
```

## Portfolio runner

`run_performance_portfolio.pl` is the reproducible performance authority for
issue #1196. It runs system Perl and PerlOnJava in alternating fresh-process
pairs and writes a JSON evidence bundle. Its defaults are intentionally long:

```bash
perl dev/bench/run_performance_portfolio.pl
```

For a non-authoritative smoke test of one workload:

```bash
perl dev/bench/run_performance_portfolio.pl --workload closure --pairs 1 \
--warmup-min 1 --warmup-max 1 --windows 1
```

For call-boundary attribution, add `--call-layer-diagnostics`. This is an
instrumented diagnostic run, not an acceptance benchmark: it writes a compact
per-process JSON report with inclusive and exclusive nanoseconds and allocated
bytes per operation for the shared-argument facade and the two general instance
call paths. The files are stored beside `portfolio.json`; extract the required
summary and remove the diagnostic directory after the investigation.

See `dev/design/performance-over-perl.md` for the acceptance contract and
evidence requirements.

## See Also

- `dev/design/optimization.md` — optimization design decisions
Expand Down
111 changes: 111 additions & 0 deletions dev/bench/analyze_performance_portfolio.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env perl

# Summarize a portfolio evidence bundle without ever upgrading an
# inconclusive run into an authoritative performance claim.
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use JSON::PP;

my %option = (bootstrap => 10_000);
my @SCORED_WORKLOADS = qw(closure method numeric string regex life json);
my %SCORED_WORKLOAD = map { $_ => 1 } @SCORED_WORKLOADS;
GetOptions('input=s' => \$option{input}, 'output=s' => \$option{output},
'bootstrap=i' => \$option{bootstrap}, 'allow-noisy-host!' => \$option{allow_noisy_host},
'help' => \$option{help}) or usage(2);
usage(0) if $option{help};
die "--input is required\n" unless defined $option{input};
die "--bootstrap must be positive\n" unless $option{bootstrap} > 0;

my $portfolio = decode_file($option{input});
die "not a performance portfolio\n" unless ($portfolio->{kind} // '') eq 'perlonjava-performance-portfolio';
my @workloads;
for my $entry (@{$portfolio->{results} || []}) {
my @ratios;
for my $pair (@{$entry->{pairs} || []}) {
my $perl = median([map { $_->{throughput} } @{$pair->{engines}{perl}{windows} || []}]);
my $pj = median([map { $_->{throughput} } @{$pair->{engines}{perlonjava}{windows} || []}]);
die "missing positive window throughput for $entry->{workload}\n" unless $perl > 0 && $pj > 0;
push @ratios, $pj / $perl;
}
die "need at least two pairs for $entry->{workload}\n" unless @ratios >= 2;
push @workloads, { workload => $entry->{workload}, pair_ratios => \@ratios,
median_ratio => median(\@ratios), geometric_mean_ratio => geometric_mean(\@ratios),
confidence_interval => bootstrap_ci(\@ratios, $option{bootstrap}) };
}
die "no workload results\n" unless @workloads;
my @anchors = grep { $_->{workload} eq 'closure' || $_->{workload} eq 'life' } @workloads;
my $strict_authority = ($portfolio->{protocol_compliant} && $portfolio->{conclusive}) ? JSON::PP::true : JSON::PP::false;
my $noisy_paired = ($portfolio->{protocol_compliant} && $option{allow_noisy_host}) ? JSON::PP::true : JSON::PP::false;
my $portfolio_ci = portfolio_bootstrap_ci(\@workloads, $option{bootstrap});
my $negative = $noisy_paired && $portfolio_ci->{upper} < 1.00
? JSON::PP::true : JSON::PP::false;
my $report = {
schema_version => 1, kind => 'perlonjava-performance-portfolio-report',
evidence => { input => $option{input}, generated_at_utc => $portfolio->{generated_at_utc},
source_commit => $portfolio->{engines}{source_commit}, protocol_compliant => $portfolio->{protocol_compliant},
conclusive => $portfolio->{conclusive}, allow_noisy_host => $option{allow_noisy_host} ? JSON::PP::true : JSON::PP::false },
# A noisy paired run can establish a one-sided negative conclusion, but it
# must never become an authoritative baseline or pass an acceptance gate.
authoritative => $strict_authority,
measurement_quality => $strict_authority ? 'stable' : ($noisy_paired ? 'noisy-paired' : 'inconclusive'),
decisive_negative_result => $negative, workloads => \@workloads,
portfolio_geometric_mean_ratio => geometric_mean([map { $_->{median_ratio} } @workloads]),
portfolio_confidence_interval => $portfolio_ci,
minimum_workload_ratio => (sort { $a <=> $b } map { $_->{median_ratio} } @workloads)[0],
acceptance => acceptance($strict_authority, \@workloads, \@anchors, $portfolio_ci),
};
my $json = JSON::PP->new->canonical->pretty->encode($report);
if (defined $option{output}) { open my $fh, '>:raw', $option{output} or die "cannot write $option{output}: $!\n"; print {$fh} $json; close $fh or die "cannot close $option{output}: $!\n"; }
print $json;

sub acceptance {
my ($authority, $workloads, $anchors, $portfolio_ci) = @_;
return { passed => JSON::PP::false, reason => 'input is protocol-inconclusive; not an authoritative baseline' } unless $authority;
return { passed => JSON::PP::false, reason => 'scored workload set is incomplete' }
unless workload_set_complete($workloads);
my $portfolio = geometric_mean([map { $_->{median_ratio} } @$workloads]);
return { passed => JSON::PP::false, reason => 'portfolio geometric mean is below 1.05x Perl' } if $portfolio < 1.05;
return { passed => JSON::PP::false, reason => 'portfolio confidence interval is not wholly above 1.00x Perl' }
if $portfolio_ci->{lower} <= 1.00;
return { passed => JSON::PP::false, reason => 'a scored workload is below 0.90x Perl' }
if grep { $_->{median_ratio} < .90 } @$workloads;
return { passed => JSON::PP::false, reason => 'closure or Life anchor is below 1.05x Perl' }
if @$anchors != 2 || grep { $_->{median_ratio} < 1.05 } @$anchors;
return { passed => JSON::PP::false, reason => 'closure or Life confidence interval is not wholly above 1.00x Perl' }
if grep { $_->{confidence_interval}{lower} <= 1.00 } @$anchors;
return { passed => JSON::PP::true, reason => 'all performance gates passed' };
}
sub bootstrap_ci {
my ($values, $count) = @_;
srand(1196); my @samples;
for (1 .. $count) { push @samples, geometric_mean([map { $values->[int rand @$values] } 1 .. @$values]); }
@samples = sort { $a <=> $b } @samples;
return { lower => $samples[int(.025 * $#samples)], upper => $samples[int(.975 * $#samples)] };
}
sub portfolio_bootstrap_ci {
my ($workloads, $count) = @_;
srand(1196); my @samples;
for (1 .. $count) {
push @samples, geometric_mean([
map { $_->{pair_ratios}[int rand @{$_->{pair_ratios}}] } @$workloads
]);
}
@samples = sort { $a <=> $b } @samples;
return { lower => $samples[int(.025 * $#samples)], upper => $samples[int(.975 * $#samples)] };
}
sub workload_set_complete {
my ($workloads) = @_;
return 0 unless @$workloads == @SCORED_WORKLOADS;
my %seen;
for my $workload (@$workloads) {
my $name = $workload->{workload};
return 0 unless defined $name && $SCORED_WORKLOAD{$name};
return 0 if $seen{$name}++;
}
return !grep { !$seen{$_} } @SCORED_WORKLOADS;
}
sub median { my ($v) = @_; my @v = sort { $a <=> $b } @$v; return $v[@v / 2] if @v % 2; return ($v[@v / 2 - 1] + $v[@v / 2]) / 2 }
sub geometric_mean { my ($v) = @_; my $sum = 0; $sum += log $_ for @$v; return exp($sum / @$v) }
sub decode_file { my ($path) = @_; open my $fh, '<:raw', $path or die "cannot read $path: $!\n"; local $/; return JSON::PP->new->decode(<$fh>) }
sub usage { my ($s) = @_; print "usage: $0 --input portfolio.json [--output report.json] [--bootstrap N] [--allow-noisy-host]\n"; exit $s }
128 changes: 128 additions & 0 deletions dev/bench/performance_workload.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env perl

# Emits deterministic, per-window measurements for one portfolio workload.
# It intentionally contains no engine-selection logic; run_performance_portfolio.pl
# owns fresh-process ordering and evidence collection.
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use JSON::PP;
use Time::HiRes qw(time);

my %option = (window_seconds => 1, windows => 15, warmup_min => 0, warmup_max => 0);
GetOptions(
'workload=s' => \$option{workload},
'window-seconds=i' => \$option{window_seconds},
'windows=i' => \$option{windows},
'warmup-min=i' => \$option{warmup_min},
'warmup-max=i' => \$option{warmup_max},
) or die "invalid options\n";
die "--workload is required\n" unless defined $option{workload};
die "window length must be positive\n" unless $option{window_seconds} > 0;
die "window count must be positive\n" unless $option{windows} > 0;
die "warmup maximum must be at least warmup minimum\n"
if $option{warmup_max} < $option{warmup_min};

my ($operation, $operations_per_iteration, $checksum) = workload($option{workload});
$checksum = $operation->() unless defined $checksum;
my @warmup;
for my $window (1 .. $option{warmup_max}) {
push @warmup, run_window($operation, $operations_per_iteration,
$option{window_seconds}, $window);
last if warmup_stabilized(\@warmup) && $window >= $option{warmup_min};
}
my @windows = map { run_window($operation, $operations_per_iteration,
$option{window_seconds}, $_) } 1 .. $option{windows};

print JSON::PP->new->canonical->encode({
schema_version => 1,
kind => 'perlonjava-performance-workload',
workload => $option{workload},
warmup_stabilized => warmup_stabilized(\@warmup),
semantic_checksum => "$checksum",
operations_per_iteration => $operations_per_iteration,
warmup_windows => \@warmup,
windows => \@windows,
}), "\n";

sub run_window {
my ($operation, $operations_per_iteration, $seconds, $window) = @_;
my ($iterations, $value) = (0, 0);
my $started = time;
my $cpu_started = process_cpu_seconds();
do {
my $result = $operation->();
die "workload semantic checksum changed\n" if $result != $checksum;
$value ^= $result;
++$iterations;
} while (time - $started < $seconds);
my $elapsed = time - $started;
my $cpu_elapsed = process_cpu_seconds() - $cpu_started;
return {
index => $window,
elapsed_seconds => 0 + $elapsed,
process_cpu_seconds => 0 + $cpu_elapsed,
iterations => $iterations,
operations => $iterations * $operations_per_iteration,
throughput => ($iterations * $operations_per_iteration) / $elapsed,
rolling_value => 0 + $value,
};
}

sub process_cpu_seconds {
my @times = times;
return $times[0] + $times[1];
}

sub warmup_stabilized {
my ($samples) = @_;
return JSON::PP::false if @$samples < 5;
my @rates = map { $_->{throughput} } @$samples[-5 .. -1];
my $mean = sum(\@rates) / @rates;
my $cv = sqrt(sum([map { ($_ - $mean) ** 2 } @rates]) / @rates) / $mean;
my $slope = abs($rates[-1] - $rates[0]) / $mean;
return ($cv < .03 && $slope < .02) ? JSON::PP::true : JSON::PP::false;
}

sub sum { my ($values) = @_; my $sum = 0; $sum += $_ for @$values; return $sum }

sub workload {
my ($name) = @_;
if ($name eq 'closure') {
my ($a, $b, $c) = (1, 2, 3);
my $make = sub { my ($x, $y, $z) = @_; my ($u, $v, $w) = ($x + 1, $y + 2, $z + 3); return sub { $u + $v + $w + $a + $b + $c } };
my $f = $make->(10, 20, 30);
return (sub { my $sum = 0; $sum += $f->() for 1 .. 128; return $sum }, 128, undef);
}
if ($name eq 'method') {
my $class = 'PortfolioMethod';
no strict 'refs'; ## no critic
*{"${class}::new"} = sub { bless { x => 1, y => 2 }, shift };
*{"${class}::add"} = sub { my ($self, $n) = @_; $self->{x} += $n; $self->{y} += $n; return $self->{x} + $self->{y} };
return (sub { my $o = $class->new; my $sum = 0; $sum += $o->add(1) for 1 .. 64; return $sum }, 64, 4352);
}
if ($name eq 'numeric') {
our $global;
return (sub { $global = 7; my $lexical = 11; for (1 .. 2048) { $lexical = ($lexical * 33 + $_) % 1_000_003; $global = ($global + $lexical) % 1_000_003 } return $lexical ^ $global }, 2048, undef);
}
if ($name eq 'string') {
return (sub { my $s = 'PerlOnJava'; for (1 .. 256) { $s = substr($s . ':' . $_, -24) } return length($s) }, 256, 24);
}
if ($name eq 'regex') {
my $text = join ':', qw(alpha beta 42 gamma delta 42 epsilon zeta);
return (sub { my $count = 0; for (1 .. 256) { pos($text) = 0; ++$count while $text =~ /(?:42|gamma|epsilon)/g } return $count }, 768, undef);
}
if ($name eq 'json') {
my $json = JSON::PP->new->canonical;
my $input = { alpha => [1, 2, 3], beta => { enabled => JSON::PP::true, text => 'PerlOnJava' } };
return (sub { my $text = $json->encode($input); my $out = $json->decode($text); return scalar @{$out->{alpha}} + length($out->{beta}{text}) }, 2, 13);
}
if ($name eq 'life') {
# A fixed flat word-level kernel. The full application's parallel and
# flat layouts remain companion diagnostics; this kernel is
# deterministic and window-friendly.
my @seed = map { (($_ * 2_654_435_761) ^ 0x5a5a5a5a) & 0xffff_ffff } 1 .. 128;
return (sub { my @grid = @seed; for (1 .. 16) { my @next; for my $i (0 .. $#grid) { my $left = $grid[($i - 1) % @grid]; my $cell = $grid[$i]; my $right = $grid[($i + 1) % @grid]; $next[$i] = ((($cell << 1) | ($left >> 31)) ^ (($cell >> 1) | (($right & 1) << 31)) ^ ($left & $right)) & 0xffff_ffff } @grid = @next } my $sum = 0; $sum ^= $_ for @grid; return $sum }, 2048, undef);
}
die "unknown workload '$name' (expected closure, method, numeric, string, regex, life, or json)\n";
}
Loading
Loading