This is an automated email from the ASF dual-hosted git repository.
zhengruifeng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 6ae31964161f [SPARK-58870][ML] Make KMeans distance measures singleton
6ae31964161f is described below
commit 6ae31964161f2a5843e9767c843113a80a45a92b
Author: Ruifeng Zheng <[email protected]>
AuthorDate: Thu Aug 20 08:27:47 2026 +0800
[SPARK-58870][ML] Make KMeans distance measures singleton
### What changes were proposed in this pull request?
This PR updates mllib KMeans distance measure implementations to use
singleton objects instead of allocating a new instance when decoding the
configured distance measure.
It also changes KMeansModel's decoded distanceMeasureInstance from a
transient lazy val to a private val, and updates internal/test call sites to
reference the Euclidean distance measure singleton directly.
### Why are the changes needed?
This is part of SPARK-58584. KMeans only has stateless distance measure
implementations, so allocating per decode is unnecessary. Reusing singleton
instances avoids per-model helper allocation and lets KMeansModel hold the
decoded helper directly.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
- build/sbt -java-home /usr/lib/jvm/java-17-openjdk-amd64 mllib/compile
- build/sbt -java-home /usr/lib/jvm/java-17-openjdk-amd64 'mllib/testOnly
org.apache.spark.mllib.clustering.KMeansSuite'
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex
Closes #58122 from
zhengruifeng/spark-58870-mllib-kmeans-distance-measure-singleton.
Authored-by: Ruifeng Zheng <[email protected]>
Signed-off-by: Ruifeng Zheng <[email protected]>
---
.../org/apache/spark/mllib/clustering/DistanceMeasure.scala | 11 ++++-------
.../scala/org/apache/spark/mllib/clustering/KMeansModel.scala | 2 +-
.../scala/org/apache/spark/mllib/clustering/LocalKMeans.scala | 2 +-
.../scala/org/apache/spark/mllib/clustering/KMeansSuite.scala | 2 +-
4 files changed, 7 insertions(+), 10 deletions(-)
diff --git
a/mllib/src/main/scala/org/apache/spark/mllib/clustering/DistanceMeasure.scala
b/mllib/src/main/scala/org/apache/spark/mllib/clustering/DistanceMeasure.scala
index 5b0fb5ef18c8..f6bec405554c 100644
---
a/mllib/src/main/scala/org/apache/spark/mllib/clustering/DistanceMeasure.scala
+++
b/mllib/src/main/scala/org/apache/spark/mllib/clustering/DistanceMeasure.scala
@@ -258,8 +258,8 @@ object DistanceMeasure {
private[spark] def decodeFromString(distanceMeasure: String):
DistanceMeasure =
distanceMeasure match {
- case EUCLIDEAN => new EuclideanDistanceMeasure
- case COSINE => new CosineDistanceMeasure
+ case EUCLIDEAN => EuclideanDistanceMeasure
+ case COSINE => CosineDistanceMeasure
case _ => throw new IllegalArgumentException(s"distanceMeasure must be
one of: " +
s"$EUCLIDEAN, $COSINE. $distanceMeasure provided.")
}
@@ -278,7 +278,7 @@ object DistanceMeasure {
k.toLong * k * numFeatures < 1000000
}
-private[spark] class EuclideanDistanceMeasure extends DistanceMeasure {
+private[spark] object EuclideanDistanceMeasure extends DistanceMeasure {
/**
* Statistics used in triangle inequality to obtain useful bounds to find
closest centers.
@@ -400,10 +400,7 @@ private[spark] class EuclideanDistanceMeasure extends
DistanceMeasure {
centroid: VectorWithNorm): Double = {
EuclideanDistanceMeasure.fastSquaredDistance(point, centroid)
}
-}
-
-private[spark] object EuclideanDistanceMeasure {
/**
* @return the squared Euclidean distance between two vectors computed by
* [[org.apache.spark.mllib.util.MLUtils#fastSquaredDistance]].
@@ -415,7 +412,7 @@ private[spark] object EuclideanDistanceMeasure {
}
}
-private[spark] class CosineDistanceMeasure extends DistanceMeasure {
+private[spark] object CosineDistanceMeasure extends DistanceMeasure {
/**
* Statistics used in triangle inequality to obtain useful bounds to find
closest centers.
diff --git
a/mllib/src/main/scala/org/apache/spark/mllib/clustering/KMeansModel.scala
b/mllib/src/main/scala/org/apache/spark/mllib/clustering/KMeansModel.scala
index e5c0b27072d0..ad2b812684a0 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/clustering/KMeansModel.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/clustering/KMeansModel.scala
@@ -43,7 +43,7 @@ class KMeansModel (@Since("1.0.0") val clusterCenters:
Array[Vector],
private[spark] val numIter: Int)
extends Saveable with Serializable with PMMLExportable {
- @transient private lazy val distanceMeasureInstance: DistanceMeasure =
+ private val distanceMeasureInstance: DistanceMeasure =
DistanceMeasure.decodeFromString(distanceMeasure)
@transient private lazy val clusterCentersWithNorm =
diff --git
a/mllib/src/main/scala/org/apache/spark/mllib/clustering/LocalKMeans.scala
b/mllib/src/main/scala/org/apache/spark/mllib/clustering/LocalKMeans.scala
index ac31c0d3be47..2fe15a59f4c7 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/clustering/LocalKMeans.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/clustering/LocalKMeans.scala
@@ -75,7 +75,7 @@ private[mllib] object LocalKMeans extends Logging {
}
- val distanceMeasureInstance = new EuclideanDistanceMeasure
+ val distanceMeasureInstance = EuclideanDistanceMeasure
// Run up to maxIterations iterations of Lloyd's algorithm
val oldClosest = Array.fill(points.length)(-1)
diff --git
a/mllib/src/test/scala/org/apache/spark/mllib/clustering/KMeansSuite.scala
b/mllib/src/test/scala/org/apache/spark/mllib/clustering/KMeansSuite.scala
index 94afcb8c8e5c..80ed707aec63 100644
--- a/mllib/src/test/scala/org/apache/spark/mllib/clustering/KMeansSuite.scala
+++ b/mllib/src/test/scala/org/apache/spark/mllib/clustering/KMeansSuite.scala
@@ -92,7 +92,7 @@ class KMeansSuite extends SparkFunSuite with
MLlibTestSparkContext {
.setInitializationSteps(10)
.setSeed(seed)
- val distanceMeasureInstance = new EuclideanDistanceMeasure
+ val distanceMeasureInstance = EuclideanDistanceMeasure
val initialCenters = km.initKMeansParallel(normedData,
distanceMeasureInstance).map(_.vector)
assert(initialCenters.length === initialCenters.distinct.length)
assert(initialCenters.length <= numDistinctPoints)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]