<div dir="ltr">Hi,<br>(Please tell me if there's another proper ML than amber-dev)<br><div><br></div><div>I recently played around with StringTemplate.</div><div><br></div><div>I developed a StringTemplate Processor for JSON recently: <a href="https://github.com/bitterfox/json-string-template" target="_blank">https://github.com/bitterfox/json-string-template</a></div><div>This parses JSON format strings in fragments with placeholders with referring values array element.</div><div><br></div><div>In the first naive implementation, it tokenizes fragments and parses the tokens creating Json Objects taking elements from values, and putting the value as the Json Object key or value.</div><div>So this is the one-pass implementation, but it parses fragments every time the JSON String Template processing is called.</div><div><a href="https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonParserV1.java" target="_blank">https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonParserV1.java</a><br></div><div><br></div><div>In the second implementation, it parses fragments and creates AST for JSON.</div><div>After that, it reads the AST again and creates a Lambda expression for `(Object[] value) -> JSON` that puts an element of values to a part of JsonObject.</div><div><a href="https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonCompiler.java" target="_blank">https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonCompiler.java</a></div><div><br></div><div>Thanks to returning (Object[] value) -> JSON and immutability of fragments of the string template expression (as long as we use through the string template expression, it will be the same fragments for the same invocation),</div><div>We can now cache the lambda expression and skip parsing fragments to JSON AST.</div><div><br></div><div>This provides us a space for optimization of the performance of JSON String Template by caching, however, StringTemplate.Processor is called for the same receiver usually because we usually define StringTemplate.Processor instance as static final field and use string template expression through it.</div><div>So we need to implement a caching layer by ourselves.</div><div><a href="https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonStringTemplateProcessorV2CachedImpl.java" target="_blank">https://github.com/bitterfox/json-string-template/blob/main/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonStringTemplateProcessorV2CachedImpl.java</a><br></div><div><br></div><div>The downside of such caching implementation is</div><div>- The caching layer could be the bottleneck and cause another performance issue</div><div>- We need to depend on the implementation detail of Javac, and JDK for better performance of caching. For example, I assumed runtime always creates the same fragments reference</div><div>  - <a href="https://github.com/bitterfox/json-string-template/blob/b53898da2d2c70aeee9a36cdffc34ba31460ba28/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonStringTemplateProcessorV2CachedImpl.java#L63" target="_blank">https://github.com/bitterfox/json-string-template/blob/b53898da2d2c70aeee9a36cdffc34ba31460ba28/json-string-template-core/src/main/java/io/github/bitterfox/json/string/template/core/JsonStringTemplateProcessorV2CachedImpl.java#L63</a><br></div><div>  - This might not work on someday</div><div><br></div><div>Now I'm working on a third implementation with Java22 using ClassFileAPI.</div><div>Using ClassFileAPI, we can generate a Java class after parsing fragments that is</div><div>```</div><div>static JsonObject createJson(Object[] values) {</div><div>    return Json.createObjectBuilder().add("name", Json.createValue(value[0].toString()));</div><div>}</div><div>```</div><div>for JSON"{'name': \{name}}".</div><div>Once it's compiled to the Java class and loaded, calling it will have almost zero overhead even though we run it through string template expression.</div><div><br></div><div>However, as the same reason for the 2nd implementation, there's no caching mechanism in StringTemplate, still, we need to implement the caching layer as well as the 2nd approach with the same problems.</div><div><br></div><div>Suppose we can couple the compiled class (or (values: Object[]) -> T) to the invocation of string template processor at string template expression. </div><div>In that case, we can solve this issue and provide the best performance for every StringTemplate Processor in the Java world.</div><div><br></div><div>Now we already have a tool for it in JVM, invokedynamic.</div><div><br></div><div>What do you think about using invokedynamic not only for creating StringTemplate objects but also calling StringTemplate Processor invocation and providing a caching layer at Java spec?</div><div><br></div><div>I made a bytecode level PoC for StringTemplate Processor invocation using invokedynamic with caching for each StringTemplate expression.</div><div><br></div><div><a href="https://github.com/bitterfox/indy-string-template-processing/">https://github.com/bitterfox/indy-string-template-processing/</a></div><div><br></div><div>In the PoC, I changed the interface of StringTemplate Processor.</div><div>I added StringTemplateProcessorFactory: (String[] fragments) -> StringTemplateProcessor.</div><div>StringTemplateProcessor is (Object[] values) -> Object (i.e. T).</div><div>And it caches StringTemplateProcessor.</div><div>StringTemplateProcessorFactory has a method to indicate to cache the processor or create the processor every time to runtime.<br></div><div><br></div><div>StringTemplateRuntime runs the processing for StringTemplateProcessorFactory and StringTemplate(fragments and values).</div><div>If cache is required, it creates a processor only once.</div><div><br></div><div>StringTemplateRuntime is used for the MethodHandle coupled to the invokedynamic.</div><div>StringTemplateBSM#createStringTemplateRuntimeCallSite is a BSM for the invokedynamic.</div><div><br></div><div>If we compile</div><div>doStringTemplate() {<br></div><div>    Sting name = "duke";</div><div>    System.out.println(MY_STP."Hello \{name}");</div><div>}</div><div><br></div><div>It will generate classfile like</div><div><br></div><div>  public static void doStringTemplate();<br>    descriptor: ()V<br>    flags: (0x0009) ACC_PUBLIC, ACC_STATIC<br>    Code:<br>      stack=6, locals=2, args_size=0<br>         0: getstatic     #10                 // Field StringTemplateSTRDebug.STR:LStringTemplateProcessorFactory;<br>         3: iconst_2<br>         4: anewarray     #12                 // class java/lang/String<br>         7: dup<br>         8: iconst_0<br>         9: ldc           #14                 // String Hello<br>        11: aastore<br>        12: dup<br>        13: iconst_1<br>        14: ldc           #16                 // String<br>        16: aastore<br>        17: iconst_1<br>        18: anewarray     #18                 // class java/lang/Object<br>        21: dup<br>        22: iconst_0<br>        23: ldc           #20                 // String duke<br>        25: aastore<br>        26: invokedynamic #31,  0             // InvokeDynamic #0:process:(LStringTemplateProcessorFactory;[Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;<br>        31: astore_1<br>        32: getstatic     #37                 // Field java/lang/System.out:Ljava/io/PrintStream;<br>        35: aload_1<br>        36: invokevirtual #43                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V<br>        39: return<br></div><div><br></div><div>BootstrapMethods:<br>  0: #27 REF_invokeStatic StringTemplateBSM.createStringTemplateRuntimeCallSite:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;<br>    Method arguments:<br></div><div><br></div><div>This creates string template processor only once for this method</div><div>```</div><div>// call doStringTemplate</div><div>Create new processor, some processor may parse fragments, so slow</div><div>Process string template<br>Hello duke</div><div>// call doStringTemplate<br>Process string template<br>Hello duke</div><div>// call doStringTemplate<br>Process string template<br>Hello duke<br></div><div>```</div><div><br></div><div><br></div><div>This kind of mechanism will be useful for most of string template processors like</div><div>- FMT: This parses formatter string like %02d</div><div>- SQL: Same overhead with queryBuilder.select("*").from(...).where(...) is preferred</div><div>- LocalizationProcessor: should avoid loading resource bundle every time</div><div>- ...</div><div><br></div><div>Let me know your thoughts about this approach for StringTemplate Processor invocation</div><div><br></div><div>Best regards,</div><div>Shinya Yoshida (@bitter_fox, <a class="gmail_plusreply" id="m_-2534142646147599308plusReplyChip-0">@shinyafox)</a></div></div>