You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/08/20 05:07:54 UTC
(groovy-website) branch asf-site updated (6f49928 -> 1b925e5)
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a change to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
from 6f49928 minor release note tweaks
new a64cda6 fix flag typo
new 1b925e5 update for Groovy 6
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../blog/classifying-iris-flowers-with-deep.adoc | 178 +++++++++++++++------
site/src/site/blog/groovy6-ai-friendly.adoc | 2 +-
2 files changed, 127 insertions(+), 53 deletions(-)
(groovy-website) 02/02: update for Groovy 6
Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
commit 1b925e53cefcdf24dfc1a9ff3a7bfbc006345dce
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Aug 20 15:07:42 2026 +1000
update for Groovy 6
---
.../blog/classifying-iris-flowers-with-deep.adoc | 178 +++++++++++++++------
1 file changed, 126 insertions(+), 52 deletions(-)
diff --git a/site/src/site/blog/classifying-iris-flowers-with-deep.adoc b/site/src/site/blog/classifying-iris-flowers-with-deep.adoc
index 8319ada..53d470d 100644
--- a/site/src/site/blog/classifying-iris-flowers-with-deep.adoc
+++ b/site/src/site/blog/classifying-iris-flowers-with-deep.adoc
@@ -1,7 +1,7 @@
= Classifying Iris Flowers with Deep Learning, Groovy™ and GraalVM
Paul King
:revdate: 2022-06-25T10:52:59+00:00
-:updated: 2022-06-27T11:16:59+00:00
+:updated: 2026-08-20T10:00:00+00:00
:keywords: groovy, machine learning, artificial intelligence, iris, neural networks, tribuo, deep netts, encog, eclipse deeplearning4j, data science, deep learning, graalvm, native
:description: This post looks at classifying Iris flowers using traditional and neural net based approaches \
using Eclipse DeepLearning4j, Encog, Deep Netts and GraalVM.
@@ -55,6 +55,10 @@ The libraries we are going to use do much of the work for us. They typically pro
in a somewhat declarative way, the layers in our network.
Just before exploring our examples, we should pre-warn folks that while we do time running the examples, no attempt was made to rigorously ensure that the examples were identical across the different technologies. The different technologies support slightly different ways to set up their respective network layers. The parameters were tweaked so that when run there was typically at most one or two errors in the validation. Also, the initial parameters for the runs can be set with random o [...]
+One more caveat since this post was first written: the Encog and DeepLearning4J transcripts below
+are still the original 2022 runs on a Linux box, while the Deep Netts and GraalVM sections have been
+re-run on an Apple Silicon machine with Groovy 6.0.0-beta-2 and GraalVM CE 25. Compare numbers within
+a section, not across them.
== Encog
@@ -252,103 +256,165 @@ When we run this command we see:
[subs="quotes"]
----
-[lime]#paulk@pop-os#:[blue]##/extra/projects/iris_encog##$ time groovy -cp "build/lib/*" Iris.groovy
-16:49:27.089 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
-16:49:27.091 [main] INFO deepnetts.core.DeepNetts - TRAINING NEURAL NETWORK
-16:49:27.091 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
-16:49:27.100 [main] INFO deepnetts.core.DeepNetts - Epoch:1, Time:6ms, TrainError:0.8584314, TrainErrorChange:0.8584314, TrainAccuracy: 0.5252525
-16:49:27.103 [main] INFO deepnetts.core.DeepNetts - Epoch:2, Time:3ms, TrainError:0.52278274, TrainErrorChange:-0.33564866, TrainAccuracy: 0.52820516
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/Iris##$ DEPS=$(ls build/lib/*.jar | grep -v groovy- | paste -sd: -)
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/Iris##$ time groovy -cp "$DEPS" NNFF_DeepNetts.groovy
+[main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
+[main] INFO deepnetts.core.DeepNetts - TRAINING NEURAL NETWORK
+[main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
+[main] INFO deepnetts.core.DeepNetts - Epoch:1, Time:1ms, TrainError:0.76851964, TrainErrorChange:0.76851964, TrainAccuracy: 0.53125
+[main] INFO deepnetts.core.DeepNetts - Epoch:2, Time:0ms, TrainError:0.4646519, TrainErrorChange:-0.30386773, TrainAccuracy: 0.53763443
...
-16:49:27.911 [main] INFO deepnetts.core.DeepNetts - Epoch:3031, Time:0ms, TrainError:0.029988592, TrainErrorChange:-0.015680967, TrainAccuracy: 1.0
+[main] INFO deepnetts.core.DeepNetts - Epoch:3293, Time:0ms, TrainError:0.03997116, TrainErrorChange:-0.0088177845, TrainAccuracy: 1.0
TRAINING COMPLETED
-16:49:27.911 [main] INFO deepnetts.core.DeepNetts - Total Training Time: 820ms
-16:49:27.911 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
+[main] INFO deepnetts.core.DeepNetts - Total Training Time: 187ms
+[main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
CLASSIFIER EVALUATION METRICS
-Accuracy: 0.95681506 (How often is classifier correct in total)
-Precision: 0.974359 (How often is classifier correct when it gives positive prediction)
-F1Score: 0.974359 (Harmonic average (balance) of precision and recall)
-Recall: 0.974359 (When it is actually positive class, how often does it give positive prediction)
+Accuracy: 1.0 (How often is classifier correct in total)
+Precision: 1.0 (How often is classifier correct when it gives positive prediction)
+F1Score: 1.0 (Harmonic average (balance) of precision and recall)
+Recall: 1.0 (When it is actually positive class, how often does it give positive prediction)
CONFUSION MATRIX
none Iris-setosaIris-versicolor Iris-virginica
none 0 0 0 0
Iris-setosa 0 14 0 0
-Iris-versicolor 0 0 18 1
+Iris-versicolor 0 0 19 0
Iris-virginica 0 0 0 12
-real 0m3.160s
-user 0m10.156s
-sys 0m0.483s
+real 0m0.975s
+user 0m2.434s
+sys 0m0.153s
----
-This is faster than DeepLearning4j and similar to Encog. This is to be expected given our small data set and isn't indicative of performance for larger problems.
+(We leave the Groovy jar out of `-cp` here since the `groovy` launcher already puts it on the
+classpath; Groovy 6 warns about the duplication if you don't.)
+
+This run happened to classify the whole test set correctly, and the whole thing -- JVM startup,
+Groovy compilation of the script, 3293 training epochs and evaluation -- takes just under a second.
+This is to be expected given our small data set and isn't indicative of performance for larger problems.
Another plus is the dependency list. It isn't quite the single jar situation as we saw with Encog but not far off.
-There is the Encog jar, the JSR381 VisRec API which is in a separate jar, and a handful of logging jars.
+There is the Deep Netts jar, the JSR381 VisRec API which is in a separate jar, and a handful of logging jars.
== Deep Netts with GraalVM
-Another technology we might want to consider if performance is important to us is https://www.graalvm.org/[GraalVM]. GraalVM is a high-performance JDK distribution designed to speed up the execution of applications written in Java and other JVM languages. We'll look at creating a native version of our Iris Deep Netts application. We used GraalVM 22.1.0 Java 17 CE and Groovy 4.0.3. We'll cover just the basic steps but there are other places for additional setup info and troubleshooting he [...]
+Another technology we might want to consider if performance is important to us is https://www.graalvm.org/[GraalVM]. GraalVM is a high-performance JDK distribution designed to speed up the execution of applications written in Java and other JVM languages. We'll look at creating a native version of our Iris Deep Netts application. We used GraalVM CE 25 (JDK 25) and Groovy 6.0.0-beta-2. We'll cover just the basic steps but there are other places for additional setup info and troubleshootin [...]
Groovy has two natures. Its dynamic nature supports adding methods at runtime through metaprogramming
and interacting with method dispatch processing through missing method interception and other tricks.
Some of these tricks make heavy use of reflection and dynamic class loading and cause problems for
GraalVM which is trying to determine as much information as it can at compile time. Groovy's static
nature has a more limited set of metaprogramming capabilities but allows bytecode much closer to
-Java to be produced. Luckily, we aren't relying on any dynamic Groovy tricks for our example.
-We'll compile it up using static mode:
+Java to be produced. When this post was first written, the recipe was to compile the script in
+static mode (`groovyc --compile-static`) and hope that the libraries in play went along with it.
+
+*That step is no longer needed.* Groovy 6.0.0-beta-2 added an
+https://groovy.apache.org/releasenotes/groovy-6.0.html#native-image[AOT link mode] for
+`invokedynamic` dispatch (https://issues.apache.org/jira/browse/GROOVY-12234[GROOVY-12234],
+with https://issues.apache.org/jira/browse/GROOVY-12227[GROOVY-12227] covering closures).
+A native image can't retarget a call site once it has been linked, and retargeting is exactly how
+Groovy's dynamic dispatch installs its inline caches -- which is what made dynamic Groovy a
+non-starter here. In AOT link mode each site instead links once, permanently, and cache freshness
+travels as data. The mode is detected automatically inside a native image, so there is nothing to
+switch on. Because it changes _linking_ rather than _compilation_, it also means already-published
+indy-compiled jars become native-capable without being recompiled.
+
+So we compile our script the ordinary way -- no annotations, no flags, fully dynamic:
[subs="quotes"]
----
-[lime]#paulk@pop-os#:[blue]##/extra/projects/iris_encog##$ groovyc -cp "build/lib/*" --compile-static Iris.groovy
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/IrisGraalVM##$ groovyc -cp "build/lib/deepnetts-core-1.13.4.jar:build/lib/visrec-api-1.0.5.jar" iris.groovy
----
-Next we build our native application:
+Next we run the application once under the `native-image-agent`, which records the reflective
+lookups and resource reads Groovy performs while registering its metaclasses. This step is still
+required -- without the generated metadata the binary builds successfully but then fails
+initializing `GroovySystem`:
[subs="quotes"]
----
-[lime]#paulk@pop-os#:[blue]##/extra/projects/iris_encog##$ native-image --report-unsupported-elements-at-runtime \
- --initialize-at-run-time=groovy.grape.GrapeIvy,deepnetts.net.weights.RandomWeights \
- --initialize-at-build-time --no-fallback -H:ConfigurationFileDirectories=conf/ -cp ".:build/lib/*" Iris
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/IrisGraalVM##$ java -agentlib:native-image-agent=config-output-dir=conf/ \
+ -cp ".:build/lib/*" -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN iris
+...
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/IrisGraalVM##$ ls conf/
+reachability-metadata.json
----
-We told GraalVM to initialize `GrapeIvy` at runtime (to avoid needing Ivy jars in the classpath
-since Groovy will lazily load those classes only if we use `@Grab` statements).
-We also did the same for the `RandomWeights` class to avoid it being locked into a random seed fixed at compile time.
+Then we build our native application:
+
+[subs="quotes"]
+----
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/IrisGraalVM##$ native-image -H:ConfigurationFileDirectories=conf/ -cp ".:build/lib/*" iris
+GraalVM Native Image: Generating 'iris' (executable)...
+ - Java version: 25.0.4+7, vendor version: GraalVM CE 25.2.4+7.1
+...
+ 29.45MiB in total image size, 29.45MiB in total file size
+Finished generating 'iris' in 23.2s.
+----
+
+That command is considerably shorter than the one this post used to show. Along with
+`--compile-static`, the following have all gone away:
+`--report-unsupported-elements-at-runtime` (a no-op for several GraalVM releases now),
+`--no-fallback` (now explicitly deprecated: "No effect, no replacement available"), and the two
+`--initialize-at-run-time` entries for `GrapeIvy` and `RandomWeights` (run-time initialization is
+the default for application classes these days). The blanket `--initialize-at-build-time` has to go
+too, and not merely for tidiness -- it now *fails the build*, because build-initializing Groovy's
+call-site cache would place its `PIC-Cleaner` thread into the image heap.
Now we are ready to run our application:
[subs="quotes"]
----
-[lime]#paulk@pop-os#:[blue]##/extra/projects/iris_encog##$ time ./iris
-...
+[lime]#paulk@quoll#:[blue]##~/Projects/groovy-data-science/subprojects/IrisGraalVM##$ time ./iris -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN
CLASSIFIER EVALUATION METRICS
-Accuracy: 0.93460923 (How often is classifier correct in total)
-Precision: 0.96491224 (How often is classifier correct when it gives positive prediction)
-F1Score: 0.96491224 (Harmonic average (balance) of precision and recall)
-Recall: 0.96491224 (When it is actually positive class, how often does it give positive prediction)
+Accuracy: 1.0 (How often is classifier correct in total)
+Precision: 1.0 (How often is classifier correct when it gives positive prediction)
+F1Score: 1.0 (Harmonic average (balance) of precision and recall)
+Recall: 1.0 (When it is actually positive class, how often does it give positive prediction)
CONFUSION MATRIX
none Iris-setosaIris-versicolor Iris-virginica
none 0 0 0 0
- Iris-setosa 0 21 0 0
-Iris-versicolor 0 0 20 2
- Iris-virginica 0 0 0 17
+ Iris-setosa 0 14 0 0
+Iris-versicolor 0 0 19 0
+ Iris-virginica 0 0 0 12
-real 0m0.131s
-user 0m0.096s
-sys 0m0.029s
+real 0m0.048s
+user 0m0.036s
+sys 0m0.009s
----
-We can see here that the speed has dramatically increased. This is great,
-but we should note, that using GraalVM often involves some tricky
-investigation especially for Groovy which by default has its dynamic
-nature. There are a few features of Groovy which won't be available
-when using Groovy's static nature and some libraries might be
-problematical. As an example, Deep Netts has log4j2 as one of its
-dependencies. At the time of writing, there are still issues using
-log4j2 with GraalVM. We excluded the `log4j-core` dependency and used
-`log4j-to-slf4j` backed by `logback-classic` to sidestep this problem.
+(Note that the log level is now passed when _running_ the binary. A `-D` option given to
+`native-image` no longer bakes a system property into the image the way it once did.)
+
+For the same work, on the same machine, that is 48ms against roughly 420ms for the same pre-compiled
+classes on the JVM, and around 900ms to run the script from source. And unlike the 2022 version of this post, we
+got there without giving up any of Groovy's dynamic nature. It is worth being concrete about how
+much has changed: repeating this exact recipe with Groovy 5.0.6 produces a binary that builds
+cleanly and then dies on the first dynamic call, and adding `--compile-static` back doesn't save it
+either -- it fails inside the `tap` closure, whose delegate-based property assignment stays dynamic
+no matter how the enclosing script is compiled:
+
+----
+Exception in thread "main" java.lang.BootstrapMethodError: java.lang.NullPointerException
+ at iris$_run_closure1.doCall(iris.groovy:34)
+ at ...
+ at org.codehaus.groovy.vmplugin.v8.IndyInterface.bootstrap(IndyInterface.java:229)
+----
+
+The honest limit of the new mode is steady-state dispatch rather than startup: with no JIT, every
+dynamic call site crossed in a native image costs a few microseconds, so a genuinely hot inner loop
+still wants `@CompileStatic` (statically compiled code crosses no dynamic sites at all). Training
+this little network doesn't come close to that threshold. Runtime compilation -- `GroovyShell`,
+`Eval` and friends -- and dynamic proxy generation remain off the table in a native image
+regardless; the AOT link mode addresses dispatch, not code generation.
+
+One caveat from the original version of this post has also expired. Deep Netts has log4j2 as one of
+its dependencies, and back then there were still issues using log4j2 with GraalVM, so we excluded
+`log4j-core` and routed through `log4j-to-slf4j` to sidestep the problem. `log4j-core` now ships its
+own GraalVM reachability metadata, and building this example against the stock dependency works
+fine. We still make the swap, but now only because `slf4j-simple` gives us a one-property knob for
+quietening the training log, as seen above.
== Conclusion
@@ -360,4 +426,12 @@ through to options which scale to massive computing farms in the cloud.
.Update history
****
*27/Sep/2022*: I put the Deep Netts GraalVM _iris_ application with some more detailed instructions into its own https://github.com/paulk-asert/groovy-data-science/tree/master/subprojects/IrisGraalVM[subproject].
+
+*20/Aug/2026*: Updated to Groovy 6.0.0-beta-2 and GraalVM CE 25 (JDK 25), and re-ran the Deep Netts
+and GraalVM examples. The headline change: `--compile-static`/`@CompileStatic` is no longer needed to
+build the native image, thanks to Groovy 6's AOT link mode for dynamic dispatch. Several
+`native-image` flags the old recipe relied on are now unnecessary or actively harmful, and the log4j2
+workaround is obsolete. The deep learning libraries themselves have barely moved: Encog 3.4 and
+DeepLearning4J 1.0.0-M2.1 are both still the most recent published releases, and Deep Netts went
+from 1.13.2 to 1.13.4.
****
(groovy-website) 01/02: fix flag typo
Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
commit a64cda685542b84cfdf742646c49eeef30fe0609
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Aug 20 15:06:31 2026 +1000
fix flag typo
---
site/src/site/blog/groovy6-ai-friendly.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/site/src/site/blog/groovy6-ai-friendly.adoc b/site/src/site/blog/groovy6-ai-friendly.adoc
index c84a182..19fa644 100644
--- a/site/src/site/blog/groovy6-ai-friendly.adoc
+++ b/site/src/site/blog/groovy6-ai-friendly.adoc
@@ -1067,7 +1067,7 @@ covering closures) is that retargeting only ever installs caches — the
dispatch semantics live entirely in method selection. So under a native
image each site links once, permanently, and cache freshness travels as
data. The mode switches on automatically under a native-image build
-(there is a `-Dgroovy.indy.aot=true` diagnostic flag for trying it on a
+(there is a `-Dgroovy.indy.aot.link=true` diagnostic flag for trying it on a
plain JVM), and because it changes linking rather than compilation,
already-published indy-compiled jars become native-capable without
being recompiled. With that in place, the policy server above builds as