From 409df943d54cc7a0367796b7c2bea132a701e384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 19 Mar 2026 05:33:05 +0100 Subject: [PATCH 1/5] chore: add clustering algorithms benchmark tests --- .../android/clustering/algo/BenchmarkTest.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt diff --git a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt new file mode 100644 index 000000000..dba033c5d --- /dev/null +++ b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt @@ -0,0 +1,66 @@ +package com.google.maps.android.clustering.algo + +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterItem +import org.junit.Test +import java.util.Random + +class BenchmarkTest { + + private class MyItem(lat: Double, lng: Double) : ClusterItem { + override val position: LatLng = LatLng(lat, lng) + override val title: String? = null + override val snippet: String? = null + override val zIndex: Float? = null + } + + private fun generateItems(count: Int): List { + val random = Random(12345) // Seed for consistency + return List(count) { + val lat = (random.nextDouble() - 0.5) * 170 // -85 to 85 + val lng = (random.nextDouble() - 0.5) * 360 // -180 to 180 + MyItem(lat, lng) + } + } + + @Test + fun benchmarkNonHierarchicalDistanceBasedAlgorithm() { + runBenchmark(NonHierarchicalDistanceBasedAlgorithm(), "NonHierarchicalDistanceBasedAlgorithm") + } + + @Test + fun benchmarkGridBasedAlgorithm() { + runBenchmark(GridBasedAlgorithm(), "GridBasedAlgorithm") + } + + private fun runBenchmark(algorithm: Algorithm, name: String) { + println("--- Benchmarking $name ---") + val count = 50000 + val items = generateItems(count) + + // Warmup + algorithm.addItems(items.take(1000)) + algorithm.getClusters(10f) + algorithm.clearItems() + + System.gc() + + // 1. Benchmark Adding Items + val startAdd = System.nanoTime() + algorithm.addItems(items) + val endAdd = System.nanoTime() + System.out.printf("addItems(%,d) took %.2f ms%n", count, (endAdd - startAdd) / 1000000.0) + + // 2. Benchmark getClusters at various zoom levels + val zoomLevels = floatArrayOf(4f, 8f, 12f, 16f) + for (zoom in zoomLevels) { + System.gc() + val startCluster = System.nanoTime() + val clusters = algorithm.getClusters(zoom) + val endCluster = System.nanoTime() + System.out.printf("getClusters(zoom=%.1f) created %,d clusters in %.2f ms%n", + zoom, clusters.size, (endCluster - startCluster) / 1000000.0) + } + } +} From f498a051f4505d6ebf02a9b660df5ac639e2bccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 19 Mar 2026 05:38:56 +0100 Subject: [PATCH 2/5] docs: added header --- .../android/clustering/algo/BenchmarkTest.kt | 16 ++++ clustering_benchmark_dashboard.html | 92 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 clustering_benchmark_dashboard.html diff --git a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt index dba033c5d..5274f881f 100644 --- a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt +++ b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.maps.android.clustering.algo import com.google.android.gms.maps.model.LatLng diff --git a/clustering_benchmark_dashboard.html b/clustering_benchmark_dashboard.html new file mode 100644 index 000000000..566323c58 --- /dev/null +++ b/clustering_benchmark_dashboard.html @@ -0,0 +1,92 @@ + + + + + + + + Clustering Benchmark Dashboard + + + + +
+

Android Maps Utils Clustering Benchmarks

+

Performance comparison between the original Java implementation (main) and the Kotlin rewrite (feat/rewrite-android-maps-utils).

+ +
+ +
+

GridBasedAlgorithm

+ +
+ + +
+

DistanceBasedAlgorithm

+ +
+
+ +
+ + + + \ No newline at end of file From 28d789ae290e1c249fe2547df81dae4217615769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 19 Mar 2026 05:57:44 +0100 Subject: [PATCH 3/5] chore: add benchmarks for remaining clustering algorithms --- .../android/clustering/algo/BenchmarkTest.kt | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt index 5274f881f..2c48eaf8a 100644 --- a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt +++ b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt @@ -1,19 +1,3 @@ -/* - * Copyright 2026 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.google.maps.android.clustering.algo import com.google.android.gms.maps.model.LatLng @@ -40,14 +24,29 @@ class BenchmarkTest { } } + @Test + fun benchmarkGridBasedAlgorithm() { + runBenchmark(GridBasedAlgorithm(), "GridBasedAlgorithm") + } + @Test fun benchmarkNonHierarchicalDistanceBasedAlgorithm() { runBenchmark(NonHierarchicalDistanceBasedAlgorithm(), "NonHierarchicalDistanceBasedAlgorithm") } @Test - fun benchmarkGridBasedAlgorithm() { - runBenchmark(GridBasedAlgorithm(), "GridBasedAlgorithm") + fun benchmarkCentroidNonHierarchicalDistanceBasedAlgorithm() { + runBenchmark(CentroidNonHierarchicalDistanceBasedAlgorithm(), "CentroidNonHierarchicalDistanceBasedAlgorithm") + } + + @Test + fun benchmarkContinuousZoomEuclideanCentroidAlgorithm() { + runBenchmark(ContinuousZoomEuclideanCentroidAlgorithm(), "ContinuousZoomEuclideanCentroidAlgorithm") + } + + @Test + fun benchmarkPreCachingAlgorithmDecorator() { + runBenchmark(PreCachingAlgorithmDecorator(NonHierarchicalDistanceBasedAlgorithm()), "PreCachingAlgorithmDecorator") } private fun runBenchmark(algorithm: Algorithm, name: String) { From 6926b79eb202373ce4e4adba23a653479b8d00f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 19 Mar 2026 06:04:02 +0100 Subject: [PATCH 4/5] chore: add license header to BenchmarkTest.kt --- .../android/clustering/algo/BenchmarkTest.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt index 2c48eaf8a..9f002e3ee 100644 --- a/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt +++ b/clustering/src/test/java/com/google/maps/android/clustering/algo/BenchmarkTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.maps.android.clustering.algo import com.google.android.gms.maps.model.LatLng From 93e4ced5aaac91c3624118b0c85a5378b5cffb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 19 Mar 2026 06:22:09 +0100 Subject: [PATCH 5/5] docs: add clustering benchmark dashboard --- clustering_benchmark_dashboard.html | 91 +++++++++++++++-------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/clustering_benchmark_dashboard.html b/clustering_benchmark_dashboard.html index 566323c58..220db456e 100644 --- a/clustering_benchmark_dashboard.html +++ b/clustering_benchmark_dashboard.html @@ -1,20 +1,4 @@ - - @@ -23,10 +7,10 @@ @@ -45,12 +29,30 @@

GridBasedAlgorithm

-

DistanceBasedAlgorithm

+

NonHierarchicalDistanceBasedAlgorithm

+ + +
+

CentroidNonHierarchicalDistanceBased

+ +
+ + +
+

ContinuousZoomEuclideanCentroid

+ +
+ + +
+

PreCachingAlgorithmDecorator

+ +
@@ -62,31 +64,32 @@

DistanceBasedAlgorithm

plugins: { legend: { position: 'top' } } }; - const gridCtx = document.getElementById('gridChart').getContext('2d'); - new Chart(gridCtx, { - type: 'bar', - data: { - labels: ['addItems', 'zoom 4.0', 'zoom 8.0', 'zoom 16.0'], - datasets: [ - { label: 'Java (main)', data: [8.84, 30.78, 198.23, 198.36], backgroundColor: 'rgba(255, 99, 132, 0.7)', borderColor: 'rgb(255, 99, 132)', borderWidth: 1 }, - { label: 'Kotlin (rewrite)', data: [12.45, 31.69, 213.00, 210.77], backgroundColor: 'rgba(54, 162, 235, 0.7)', borderColor: 'rgb(54, 162, 235)', borderWidth: 1 } - ] - }, - options: chartOptions - }); + const labels = ['addItems', 'zoom 4.0', 'zoom 8.0', 'zoom 12.0', 'zoom 16.0']; + + const colors = { + java: { bg: 'rgba(255, 99, 132, 0.7)', border: 'rgb(255, 99, 132)' }, + kotlin: { bg: 'rgba(54, 162, 235, 0.7)', border: 'rgb(54, 162, 235)' } + }; + + function createChart(ctxId, dataJava, dataKotlin) { + new Chart(document.getElementById(ctxId).getContext('2d'), { + type: 'bar', + data: { + labels: labels, + datasets: [ + { label: 'Java (main)', data: dataJava, backgroundColor: colors.java.bg, borderColor: colors.java.border, borderWidth: 1 }, + { label: 'Kotlin (rewrite)', data: dataKotlin, backgroundColor: colors.kotlin.bg, borderColor: colors.kotlin.border, borderWidth: 1 } + ] + }, + options: chartOptions + }); + } - const distanceCtx = document.getElementById('distanceChart').getContext('2d'); - new Chart(distanceCtx, { - type: 'bar', - data: { - labels: ['addItems', 'zoom 4.0', 'zoom 8.0', 'zoom 16.0'], - datasets: [ - { label: 'Java (main)', data: [35.88, 102.94, 73.67, 80.52], backgroundColor: 'rgba(255, 99, 132, 0.7)', borderColor: 'rgb(255, 99, 132)', borderWidth: 1 }, - { label: 'Kotlin (rewrite)', data: [37.30, 93.58, 85.88, 64.44], backgroundColor: 'rgba(54, 162, 235, 0.7)', borderColor: 'rgb(54, 162, 235)', borderWidth: 1 } - ] - }, - options: chartOptions - }); + createChart('gridChart', [6.28, 29.93, 206.30, 269.87, 228.93], [6.37, 29.77, 189.84, 250.74, 221.97]); + createChart('distanceChart', [22.60, 50.19, 70.89, 63.84, 62.89], [20.62, 48.33, 62.50, 55.42, 53.83]); + createChart('centroidChart', [32.28, 81.76, 133.18, 82.79, 90.66], [43.37, 77.41, 128.38, 105.66, 72.02]); + createChart('continuousChart', [21.99, 82.72, 105.88, 92.08, 100.84], [20.78, 76.80, 96.01, 89.77, 87.47]); + createChart('preCachingChart', [48.36, 84.21, 74.76, 75.32, 62.51], [47.58, 118.35, 72.57, 66.87, 69.80]); \ No newline at end of file