perf: continue eval STRING optimization work (follow-up to #1940)
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Facilidade para iniciantes
- 52/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Domínio
- backend, performance
Direção de pesquisa
Start with src/main/java/org/perlonjava/frontend/parser/Parser.java and the listed allocation hotspots; run dev/bench/benchmark_eval_string.pl to establish a baseline. The issue lists five possible optimizations across parser, emitter, lexer, runtime, and symbol table, so pick one and measure its impact. Run nice -n 19 make and verify both backends with the provided eval commands; done means the change passes and improves the benchmark without breaking those checks.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Context
PR #1940 reduced eval STRING overhead from 6.8× to 6.0× slower than system Perl on dev/bench/benchmark_eval_string.pl:
| ops/sec | |
|---|---|
| System Perl | 263,000/s |
| PerlOnJava (post-PR #1940) | 43,700/s |
| Ratio | 6.0× |
All optimizations in PR #1940 targeted the real pipeline per unique eval string (no eval-string caching). The remaining gap is structural — JVM overhead vs native C.
Remaining opportunities (in estimated impact order)
1. Lazy indexSourceLines() in Parser — high impact
File: src/main/java/org/perlonjava/frontend/parser/Parser.java (~lines 383–394)
indexSourceLines() is called from both constructors and scans the entire token list to build a line-number index array. For eval strings, no source-line lookup is needed at runtime unless an exception is thrown.
Fix: Remove the call from both constructors. Build the int[] lazily on the first sourceLineAt() call (add a boolean lineIndexBuilt field, or check lineIndex == null).
Evidence: This was the #1 allocation hotspot in a JFR profile of 500k iterations (416 samples). Saving one int[] allocation + O(tokens) scan per eval.
2. Lazy EmitterContext.contextCache and JavaClassInfo maps — medium impact
Files:
src/main/java/org/perlonjava/backend/jvm/EmitterContext.javasrc/main/java/org/perlonjava/backend/jvm/JavaClassInfo.java
EmitterContext.contextCache is a HashMap allocated eagerly in the constructor even for evals that never write to it.
JavaClassInfo has 4 pin/literal maps allocated eagerly.
Fix: Initialize each to null; create on first write via a private accessor. Readers check for null (return empty or no-op).
Savings: 5 map allocations per eval.
3. Lexer sawBegin flag to skip BEGIN aliasing loop — medium impact
Files:
src/main/java/org/perlonjava/frontend/lexer/Lexer.javasrc/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java
evalStringWithInterpreter() (RuntimeCode.java around line 5330) iterates ALL visible variables on every eval to support BEGIN {} inside eval strings. BEGIN in eval is extremely rare.
Fix: Add boolean sawBegin to Lexer. In nextToken(), when creating an IDENTIFIER token, check if text.equals("BEGIN") and set sawBegin = true. After tokenize(), evalStringWithInterpreter() checks lexer.sawBegin — if false, skip the entire alias loop.
Savings: O(visible_vars) String allocations + HashMap lookups eliminated on the common path.
4. Skip BitSet.clone() for empty BitSets in enterScope — low impact
File: src/main/java/org/perlonjava/frontend/semantic/ScopedSymbolTable.java
enterScope() unconditionally clones 3 BitSets even when they are empty (the common case for eval strings with no warning pragmas).
Fix: Replace existing.clone() with new BitSet() when existing.isEmpty().
5. Pool common LexerToken instances — low impact
File: src/main/java/org/perlonjava/frontend/lexer/Lexer.java
NEWLINE and common single-char operator tokens (,, ;, (, ), {, }) are allocated fresh on every tokenization. These are stateless value objects.
Fix: Static flyweight instances; return pooled instance from nextToken() for these common cases.
Measurement guidance
Benchmark command:
# Warm up JVM + measure
timeout 120 ./jperl dev/bench/benchmark_eval_string.pl
# System Perl baseline
perl dev/bench/benchmark_eval_string.pl
# Quick smoke (no JVM warmup noise):
timeout 30 ./jperl -e 'use Benchmark qw(timethis); timethis(500_000, sub { eval "1" })'
For each change, run nice -n 19 make first (full test suite). Both backends must still pass:
./jperl -e 'eval "my \$x = 1"'
./jperl --interpreter -e 'eval "my \$x = 1"'
Profile guidance
The JFR profile that informed PR #1940 used:
./jperl -XX:+UnlockDiagnosticVMOptions -XX:StartFlightRecording=... -e '
for (1..500_000) { eval "my \$x = 1 + 2; \$x" }
'
A fresh profile after PR #1940 merges will show whether the allocation hotspot has shifted.
Notes
- The
a59926c1a/442ce8c09eval-string cache experiment was tried and removed — it caused correctness issues and the cache key was too coarse. Do not re-add a simple string-equality cache for eval. - The
PerlRuntime.bind()re-entrant fast-path skipssynchronized lifecycleMonitorbut still pushes aBindingFrame; the LIFO invariant inPerlRuntimeTestmust stay green.
- Linguagem predominante
- Perl
- Estrelas
- 65
- Forks
- 8
- Merge médio
- 5h 54min
- PRs com merge (30d)
- 184
Preparar o ambiente
- Inclui um Dockerfile ou arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de fglock/PerlOnJava
-
area:runtime documentation enhancement
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
fglock/PerlOnJava#1579 ·
Mantenedores costumam responder em até 1 dia
-
area:backend bug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 55/100
fglock/PerlOnJava#1981 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
area:runtime bug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 57/100
fglock/PerlOnJava#1980 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
area:parser bug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
fglock/PerlOnJava#1979 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
Fix Java TLS socket reads for chunked HTTPS response bodiesTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. Abertaarea:io bug
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 56/100
fglock/PerlOnJava#1957 · 6 comentários ·
Mantenedores costumam responder em até 1 dia
Todas as issues de fglock/PerlOnJava
Issues semelhantes
-
check_keywords.pl dies before reporting an unknown keyword categoryTalvez já em andamento @zhaohai666 assumiu hoje. Aberta
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 87/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
libwww-perl/HTTP-Message#236 ·
-
1.severity: security
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
wave-followup
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 74/100