[ 
https://issues.apache.org/jira/browse/GROOVY-12288?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Daniel Sun updated GROOVY-12288:
--------------------------------
    Description: 
Bytecode generation uses an ASM {{ClassWriter}} with {{{}COMPUTE_FRAMES{}}}. 
Frame computation calls {{getCommonSuperClass}} at every control-flow merge. 
Groovy overrides that method so types still being compiled are resolved through 
{{ClassNode}} ({{{}CompileUnit{}}}, generated inner classes, 
{{{}ClassNodeResolver{}}}) rather than {{{}Class.forName{}}}.

{{COMPUTE_FRAMES}} asks for the same binary-name pairs many times inside one 
class. Each call converts slashes to dots, resolves two \{{ClassNode}}s, and 
walks superclasses with isDerivedFrom. Class generation is about half of 
compile wall time.
h3. Approach

Memoize both steps on the {{ClassWriter}} created by 
{{{}CompilationUnit.createClassVisitor{}}}. One writer is allocated per 
generated class and discarded afterwards, so the maps cannot go stale across 
classes.
||Cache||Key||Value||
|{{classNodeByName}}|binary name (dot form)|{{ClassNode}} (successful lookups 
only)|
|{{commonSuperByPair}}|canonical pair of internal names|internal name of the 
common superclass|

The key is order-independent: {{(A,B)}} and {{(B,A)}} share one entry. The 
common-superclass algorithm is unchanged.
h3. Impact

Compile-time only. {{getCommonSuperClass}} results and generated bytecode stay 
the same.

 

  was:
Bytecode generation uses an ASM {{ClassWriter}} with {{{}COMPUTE_FRAMES{}}}. 
Frame computation calls {{getCommonSuperClass}} at every control-flow merge. 
Groovy overrides that method so types still being compiled are resolved through 
{{ClassNode}} ({{{}CompileUnit{}}}, generated inner classes, 
{{{}ClassNodeResolver{}}}) rather than {{{}Class.forName{}}}.

{{COMPUTE_FRAMES}} asks for the same binary-name pairs many times inside one 
class. Each call converts slashes to dots, resolves two \{{ClassNode}}s, and 
walks superclasses with isDerivedFrom. Class generation is about half of 
compile wall time.
h3. Approach

Memoize both steps on the {{ClassWriter}} created by 
{{{}CompilationUnit.createClassVisitor{}}}. One writer is allocated per 
generated class and discarded afterwards, so the maps cannot go stale across 
classes.
||Cache||Key||Value||
|{{classNodeByName}}|binary name (dot form)|{{ClassNode}} (successful lookups 
only)|
|{{commonSuperByPair}}|canonical pair of internal names|internal name of the 
common superclass|

The key is order-independent: {{(A,B)}} and {{(B,A)}} share one entry. Names 
are joined with {{{}>{}}}, which cannot appear in a JVM internal name.
{code:java}
// before (every merge point)
ClassNode a = getClassNode(arg1.replace('/', '.'));
ClassNode b = getClassNode(arg2.replace('/', '.'));
return getCommonSuperClassNode(a, b).getName().replace('.', '/');

// after
String key = arg1.compareTo(arg2) < 0 ? arg1 + '>' + arg2 : arg2 + '>' + arg1;
String cached = commonSuperByPair.get(key);
if (cached != null) {
    return cached;
}
// resolve, compute, store, return
{code}
The common-superclass algorithm is unchanged.
h3. Impact

Compile-time only. {{getCommonSuperClass}} results and generated bytecode stay 
the same.

 


> Cache ClassWriter getCommonSuperClass lookups per class
> -------------------------------------------------------
>
>                 Key: GROOVY-12288
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12288
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> Bytecode generation uses an ASM {{ClassWriter}} with {{{}COMPUTE_FRAMES{}}}. 
> Frame computation calls {{getCommonSuperClass}} at every control-flow merge. 
> Groovy overrides that method so types still being compiled are resolved 
> through {{ClassNode}} ({{{}CompileUnit{}}}, generated inner classes, 
> {{{}ClassNodeResolver{}}}) rather than {{{}Class.forName{}}}.
> {{COMPUTE_FRAMES}} asks for the same binary-name pairs many times inside one 
> class. Each call converts slashes to dots, resolves two \{{ClassNode}}s, and 
> walks superclasses with isDerivedFrom. Class generation is about half of 
> compile wall time.
> h3. Approach
> Memoize both steps on the {{ClassWriter}} created by 
> {{{}CompilationUnit.createClassVisitor{}}}. One writer is allocated per 
> generated class and discarded afterwards, so the maps cannot go stale across 
> classes.
> ||Cache||Key||Value||
> |{{classNodeByName}}|binary name (dot form)|{{ClassNode}} (successful lookups 
> only)|
> |{{commonSuperByPair}}|canonical pair of internal names|internal name of the 
> common superclass|
> The key is order-independent: {{(A,B)}} and {{(B,A)}} share one entry. The 
> common-superclass algorithm is unchanged.
> h3. Impact
> Compile-time only. {{getCommonSuperClass}} results and generated bytecode 
> stay the same.
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to