Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 78 additions & 0 deletions dev/bench/benchmark_eval_string_stats.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use strict;
use warnings;
use Time::HiRes qw(time);

my $trials = 10;
my $n = 200_000;

my $count = 0;
my $k = 0;

sub run_uncached {
my @times;
for my $t (1 .. $trials) {
$k = 0;
$count = 0;
my $start = time();
for (1 .. $n) {
$k++;
my $str = '
while ( $k < 40 ) {
$k = $k + 1;
$count = $count + 1;
}
' . "# " . $k;
eval $str;
}
push @times, time() - $start;
}
return @times;
}

sub run_cached {
my @times;
my $str = 'my $x = 1';
for my $t (1 .. $trials) {
my $start = time();
for (1 .. $n) {
eval $str;
}
push @times, time() - $start;
}
return @times;
}

sub stats {
my @vals = @_;
my $sum = 0; $sum += $_ for @vals;
my $mean = $sum / @vals;
my $ss = 0; $ss += ($_ - $mean)**2 for @vals;
my $sd = sqrt($ss / (@vals - 1));
my $se = $sd / sqrt(scalar @vals);
return ($mean, $sd, $se);
}

sub report {
my ($label, $n_iter, @times) = @_;
my ($mean, $sd, $se) = stats(@times);
my $us = $mean / $n_iter * 1e6;
my $us_se = $se / $n_iter * 1e6;
printf " %-10s %d trials x %d iters: %.3f +/- %.3f s (%.2f +/- %.2f us/eval)\n",
$label, scalar @times, $n_iter, $mean, $se, $us, $us_se;
return ($us, $us_se);
}

print "=== eval STRING benchmark ($trials trials x $n iterations) ===\n\n";

print "Uncached (unique eval string each call):\n";
my @t = run_uncached();
my ($us, $us_se) = report("", $n, @t);

print "\nCached (same eval string each call):\n";
my @tc = run_cached();
my ($us_c, $us_c_se) = report("", $n, @tc);

my $engine = $ENV{JPERL_OPTS} ? "jperl" : ($^X =~ /jperl/ ? "jperl" : "perl");
printf "\nEngine: %s (%s)\n", $engine, $^X;
printf "Uncached: %.2f +/- %.2f us/eval\n", $us, $us_se;
printf "Cached: %.2f +/- %.2f us/eval\n", $us_c, $us_c_se;
91 changes: 91 additions & 0 deletions dev/bench/compare_eval_string.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash
# Compare eval STRING performance: Perl vs PerlOnJava
# Interleaves trials to account for CPU variability.
# Usage: ./dev/bench/compare_eval_string.sh [trials] [iters]

set -euo pipefail
cd "$(dirname "$0")/../.."

TRIALS=${1:-10}
N=${2:-200000}

cat > /tmp/bench_eval_trial.pl << 'PERL'
use strict; use warnings; use Time::HiRes qw(time);
my $n = $ARGV[0] || 200_000;
my $mode = $ARGV[1] || 'uncached';
my ($count, $k) = (0, 0);
my $start = time();
if ($mode eq 'cached') {
my $str = 'my $x = 1';
for (1 .. $n) { eval $str }
} else {
for (1 .. $n) {
$k++;
my $str = 'while ($k < 40) { $k = $k + 1; $count = $count + 1; }' . "# $k";
eval $str;
}
}
printf "%.6f\n", time() - $start;
PERL

run_trial() {
local engine=$1 mode=$2
if [ "$engine" = "perl" ]; then
perl /tmp/bench_eval_trial.pl "$N" "$mode"
else
timeout 300 ./jperl /tmp/bench_eval_trial.pl "$N" "$mode"
fi
}

echo "=== eval STRING: Perl vs PerlOnJava ($TRIALS trials x $N iters) ==="
echo ""

for mode in uncached cached; do
perl_times=()
jperl_times=()

# Warmup
run_trial perl "$mode" > /dev/null
run_trial jperl "$mode" > /dev/null

# Interleaved trials
for i in $(seq 1 "$TRIALS"); do
p=$(run_trial perl "$mode")
j=$(run_trial jperl "$mode")
perl_times+=("$p")
jperl_times+=("$j")
# Compute per-trial ratio
ratio=$(echo "$j / $p" | bc -l)
printf " trial %2d: perl=%.3fs jperl=%.3fs ratio=%.1fx\n" "$i" "$p" "$j" "$ratio"
done

# Compute stats with awk
echo ""
echo "${perl_times[@]}" | tr ' ' '\n' | awk -v n="$N" -v label="perl" '
{ a[NR]=$1; s+=$1 }
END {
m=s/NR; ss=0; for(i=1;i<=NR;i++) ss+=(a[i]-m)^2
sd=sqrt(ss/(NR-1)); se=sd/sqrt(NR)
us=m/n*1e6; us_se=se/n*1e6
printf " %-8s mean=%.3f +/- %.3f s (%.2f +/- %.2f us/eval)\n", label, m, se, us, us_se
}'
echo "${jperl_times[@]}" | tr ' ' '\n' | awk -v n="$N" -v label="jperl" '
{ a[NR]=$1; s+=$1 }
END {
m=s/NR; ss=0; for(i=1;i<=NR;i++) ss+=(a[i]-m)^2
sd=sqrt(ss/(NR-1)); se=sd/sqrt(NR)
us=m/n*1e6; us_se=se/n*1e6
printf " %-8s mean=%.3f +/- %.3f s (%.2f +/- %.2f us/eval)\n", label, m, se, us, us_se
}'

# Compute paired ratio with t-test
paste <(printf '%s\n' "${perl_times[@]}") <(printf '%s\n' "${jperl_times[@]}") | awk -v mode="$mode" '
{ r[NR]=$2/$1; s+=r[NR] }
END {
m=s/NR; ss=0; for(i=1;i<=NR;i++) ss+=(r[i]-m)^2
sd=sqrt(ss/(NR-1)); se=sd/sqrt(NR)
t=(m-1)/se
printf "\n %s ratio: %.2fx +/- %.2f (paired t vs 1.0: t=%.1f, df=%d)\n", mode, m, se, t, NR-1
}'
echo ""
done
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,7 @@ private void enterScope() {
st.warningFlagsStack.push((java.util.BitSet) st.warningFlagsStack.peek().clone());
st.warningFatalStack.push((java.util.BitSet) st.warningFatalStack.peek().clone());
st.warningDisabledStack.push((java.util.BitSet) st.warningDisabledStack.peek().clone());
st.invalidateWarningBitsCache();
}
}

Expand Down Expand Up @@ -1043,6 +1044,7 @@ private void exitScope(boolean flush) {
st.warningFlagsStack.pop();
st.warningFatalStack.pop();
st.warningDisabledStack.pop();
st.invalidateWarningBitsCache();
}
}
}
Expand Down Expand Up @@ -1273,6 +1275,7 @@ private void inheritPragmaFlags(BytecodeCompiler subCompiler) {
subCompiler.symbolTable.warningFatalStack.push((java.util.BitSet) parentST.warningFatalStack.peek().clone());
subCompiler.symbolTable.warningDisabledStack.pop();
subCompiler.symbolTable.warningDisabledStack.push((java.util.BitSet) parentST.warningDisabledStack.peek().clone());
subCompiler.symbolTable.invalidateWarningBitsCache();
}

/**
Expand Down Expand Up @@ -1533,6 +1536,7 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
symbolTable.strictOptionsStack.push(ctx.symbolTable.strictOptionsStack.peek());
symbolTable.featureFlagsStack.pop();
symbolTable.featureFlagsStack.push(ctx.symbolTable.featureFlagsStack.peek());
symbolTable.invalidateWarningBitsCache();
symbolTable.warningFlagsStack.pop();
symbolTable.warningFlagsStack.push((java.util.BitSet) ctx.symbolTable.warningFlagsStack.peek().clone());
symbolTable.warningFatalStack.pop();
Expand Down Expand Up @@ -9226,6 +9230,7 @@ public void visit(LabelNode node) {
public void visit(CompilerFlagNode node) {
if (emitterContext != null && emitterContext.symbolTable != null) {
ScopedSymbolTable st = emitterContext.symbolTable;
st.invalidateWarningBitsCache();
st.warningFlagsStack.pop();
st.warningFlagsStack.push((java.util.BitSet) node.getWarningFlags().clone());
st.warningFatalStack.pop();
Expand All @@ -9247,6 +9252,7 @@ public void visit(CompilerFlagNode node) {
symbolTable.warningFatalStack.push((java.util.BitSet) node.getWarningFatalFlags().clone());
symbolTable.warningDisabledStack.pop();
symbolTable.warningDisabledStack.push((java.util.BitSet) node.getWarningDisabledFlags().clone());
symbolTable.invalidateWarningBitsCache();

int warningBitsIdx = addToStringPool(symbolTable.getWarningBitsString());
emit(Opcodes.APPLY_COMPILER_FLAGS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ private static RuntimeList evalStringList(String perlCode,
symbolTable.strictOptionsStack.push(strictOpts);
symbolTable.featureFlagsStack.pop();
symbolTable.featureFlagsStack.push(featFlags);
symbolTable.invalidateWarningBitsCache();
symbolTable.warningFlagsStack.pop();
symbolTable.warningFlagsStack.push((java.util.BitSet) currentCode.warningFlags.clone());
WarningFlags.setWarningBitsFromString(symbolTable, siteWarningBits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static void emitCompilerFlag(EmitterContext ctx, CompilerFlagNode node) {
ScopedSymbolTable currentScope = ctx.symbolTable;

// Set the warning flags
currentScope.invalidateWarningBitsCache();
currentScope.warningFlagsStack.pop();
currentScope.warningFlagsStack.push((java.util.BitSet) node.getWarningFlags().clone());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ && isDirectLeafIntegerAddition(node.block, directLeafCaptures,
? value
: ctx.symbolTable.strictOptionsStack.peek();

newSymbolTable.invalidateWarningBitsCache();
newSymbolTable.warningFlagsStack.pop();
newSymbolTable.warningFlagsStack.push(definitionWarningFlags);
newSymbolTable.warningFatalStack.pop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,7 @@ private static void applyCompilerFlagNodes(EmitterContext ctx, Node ast) {
// Apply disabled warning flags
currentScope.warningDisabledStack.pop();
currentScope.warningDisabledStack.push((java.util.BitSet) node.getWarningDisabledFlags().clone());
currentScope.invalidateWarningBitsCache();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ public class SpecialBlockParser {
private static final ThreadLocal<Deque<ScopedSymbolTable>> compileTimePragmaMutationScopes =
ThreadLocal.withInitial(ArrayDeque::new);

private static Stack<BitSet> cloneBitSetStack(Stack<BitSet> source) {
Stack<BitSet> copy = new Stack<>();
private static ScopedSymbolTable.FastStack<BitSet> cloneBitSetStack(ScopedSymbolTable.FastStack<BitSet> source) {
ScopedSymbolTable.FastStack<BitSet> copy = new ScopedSymbolTable.FastStack<>();
for (BitSet flags : source) {
copy.push((BitSet) flags.clone());
}
return copy;
}

private static Stack<Integer> cloneIntegerStack(Stack<Integer> source) {
Stack<Integer> copy = new Stack<>();
private static ScopedSymbolTable.FastStack<Integer> cloneIntegerStack(ScopedSymbolTable.FastStack<Integer> source) {
ScopedSymbolTable.FastStack<Integer> copy = new ScopedSymbolTable.FastStack<>();
copy.addAll(source);
return copy;
}

private static <T> void restoreStack(Stack<T> target, Stack<T> source) {
private static <T> void restoreStack(ScopedSymbolTable.FastStack<T> target, ScopedSymbolTable.FastStack<T> source) {
target.clear();
target.addAll(source);
}
Expand Down Expand Up @@ -507,11 +507,11 @@ static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block,
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("Special block captures " + parser.ctx.symbolTable.getAllVisibleVariables());
RuntimeList result;
boolean preserveCallerPragmas = !blockPhase.equals("BEGIN");
Stack<BitSet> savedWarningFlagsStack = null;
Stack<BitSet> savedWarningFatalStack = null;
Stack<BitSet> savedWarningDisabledStack = null;
Stack<Integer> savedFeatureFlagsStack = null;
Stack<Integer> savedStrictOptionsStack = null;
ScopedSymbolTable.FastStack<BitSet> savedWarningFlagsStack = null;
ScopedSymbolTable.FastStack<BitSet> savedWarningFatalStack = null;
ScopedSymbolTable.FastStack<BitSet> savedWarningDisabledStack = null;
ScopedSymbolTable.FastStack<Integer> savedFeatureFlagsStack = null;
ScopedSymbolTable.FastStack<Integer> savedStrictOptionsStack = null;
if (preserveCallerPragmas) {
savedWarningFlagsStack = cloneBitSetStack(parser.ctx.symbolTable.warningFlagsStack);
savedWarningFatalStack = cloneBitSetStack(parser.ctx.symbolTable.warningFatalStack);
Expand Down
21 changes: 11 additions & 10 deletions src/main/java/org/perlonjava/frontend/parser/StatementParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,21 +173,21 @@ private static void markInsideGiven(Node node) {
if (node instanceof IfNode conditional) { markInsideGiven(conditional.condition); markInsideGiven(conditional.thenBranch); markInsideGiven(conditional.elseBranch); return; }
if (node instanceof TernaryOperatorNode ternary) { markInsideGiven(ternary.condition); markInsideGiven(ternary.trueExpr); markInsideGiven(ternary.falseExpr); }
}
private static Stack<BitSet> cloneBitSetStack(Stack<BitSet> source) {
Stack<BitSet> copy = new Stack<>();
private static ScopedSymbolTable.FastStack<BitSet> cloneBitSetStack(ScopedSymbolTable.FastStack<BitSet> source) {
ScopedSymbolTable.FastStack<BitSet> copy = new ScopedSymbolTable.FastStack<>();
for (BitSet flags : source) {
copy.push((BitSet) flags.clone());
}
return copy;
}

private static Stack<Integer> cloneIntegerStack(Stack<Integer> source) {
Stack<Integer> copy = new Stack<>();
private static ScopedSymbolTable.FastStack<Integer> cloneIntegerStack(ScopedSymbolTable.FastStack<Integer> source) {
ScopedSymbolTable.FastStack<Integer> copy = new ScopedSymbolTable.FastStack<>();
copy.addAll(source);
return copy;
}

private static <T> void restoreStack(Stack<T> target, Stack<T> source) {
private static <T> void restoreStack(ScopedSymbolTable.FastStack<T> target, ScopedSymbolTable.FastStack<T> source) {
target.clear();
target.addAll(source);
}
Expand Down Expand Up @@ -1383,6 +1383,7 @@ public static Node parseUseDeclaration(Parser parser, LexerToken token) {
for (int i = 0; i < parser.ctx.symbolTable.warningFlagsStack.size(); i++) {
parser.ctx.symbolTable.warningFlagsStack.set(i, (java.util.BitSet) currentWarnings.clone());
}
parser.ctx.symbolTable.invalidateWarningBitsCache();
}
}
}
Expand Down Expand Up @@ -1544,11 +1545,11 @@ public static Node parseUseDeclaration(Parser parser, LexerToken token) {
// context. Its final lexical pragma state must not be copied back over the
// surrounding file scope; the actual pragma effect of a `use` statement happens
// below when import()/unimport() is invoked against parser.ctx.symbolTable.
Stack<BitSet> savedWarningFlagsStack = cloneBitSetStack(ctx.symbolTable.warningFlagsStack);
Stack<BitSet> savedWarningFatalStack = cloneBitSetStack(ctx.symbolTable.warningFatalStack);
Stack<BitSet> savedWarningDisabledStack = cloneBitSetStack(ctx.symbolTable.warningDisabledStack);
Stack<Integer> savedFeatureFlagsStack = cloneIntegerStack(ctx.symbolTable.featureFlagsStack);
Stack<Integer> savedStrictOptionsStack = cloneIntegerStack(ctx.symbolTable.strictOptionsStack);
ScopedSymbolTable.FastStack<BitSet> savedWarningFlagsStack = cloneBitSetStack(ctx.symbolTable.warningFlagsStack);
ScopedSymbolTable.FastStack<BitSet> savedWarningFatalStack = cloneBitSetStack(ctx.symbolTable.warningFatalStack);
ScopedSymbolTable.FastStack<BitSet> savedWarningDisabledStack = cloneBitSetStack(ctx.symbolTable.warningDisabledStack);
ScopedSymbolTable.FastStack<Integer> savedFeatureFlagsStack = cloneIntegerStack(ctx.symbolTable.featureFlagsStack);
ScopedSymbolTable.FastStack<Integer> savedStrictOptionsStack = cloneIntegerStack(ctx.symbolTable.strictOptionsStack);
RuntimeList args;
try {
// The implicit BEGIN wrapper that evaluates a use/no
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2571,13 +2571,14 @@ && isLexicalSubStorageReferenced(explicitlyUsedVars, entry.name())
compilationBlock.setAnnotation("compiledSubroutineName", fullName);

// Clone warning flags (critical for 'no warnings' pragmas)
filteredSnapshot.invalidateWarningBitsCache();
filteredSnapshot.warningFlagsStack.pop(); // Remove the initial value pushed by enterScope
filteredSnapshot.warningFlagsStack.push(definitionWarningFlags);

// Clone fatal warning flags (critical for 'use warnings FATAL' pragmas)
filteredSnapshot.warningFatalStack.pop();
filteredSnapshot.warningFatalStack.push(definitionWarningFatalFlags);

// Clone disabled warning flags (critical for 'no warnings' pragmas)
filteredSnapshot.warningDisabledStack.pop();
filteredSnapshot.warningDisabledStack.push(definitionWarningDisabledFlags);
Expand Down
Loading
Loading