tg_geometry 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "_support"
4
+
5
+ TGGeometryBench.say_header("distance_point_geom")
6
+
7
+ VERTEX_COUNTS = (TGGeometryBench.full? ? [16, 128, 1_024, 8_192] : [16, 128, 1_024]).freeze
8
+
9
+ def regular_polygon(vertex_count, radius: 1.0)
10
+ points = vertex_count.times.map do |i|
11
+ angle = (2.0 * Math::PI * i) / vertex_count
12
+ [Math.cos(angle) * radius, Math.sin(angle) * radius]
13
+ end
14
+ points << points.first
15
+ TG::Geometry.polygon(points)
16
+ end
17
+
18
+ def zigzag_line(vertex_count, scale: 1.0)
19
+ points = vertex_count.times.map { |i| [i.to_f * scale, (i.even? ? 0.0 : 1.0) * scale] }
20
+ TG::Geometry.line_string(points)
21
+ end
22
+
23
+ VERTEX_COUNTS.each do |vertices|
24
+ geometries = {
25
+ polygon_xy: regular_polygon(vertices, radius: 10.0),
26
+ line_xy: zigzag_line(vertices),
27
+ polygon_lnglat_meters: regular_polygon(vertices, radius: 0.01),
28
+ line_lnglat_meters: zigzag_line(vertices, scale: 0.0001)
29
+ }
30
+
31
+ geometries.each do |kind, geom|
32
+ method, args = case kind
33
+ when :polygon_xy then [:distance_to_xy, [20.0, 0.0]]
34
+ when :line_xy then [:distance_to_xy, [vertices / 2.0, 5.0]]
35
+ when :polygon_lnglat_meters then [:distance_to_lnglat_meters, [0.02, 0.0]]
36
+ when :line_lnglat_meters then [:distance_to_lnglat_meters, [vertices / 20_000.0, 0.002]]
37
+ end
38
+
39
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(1_000)) do |iter|
40
+ iter.times { geom.public_send(method, *args) }
41
+ end
42
+
43
+ TGGeometryBench.report(
44
+ "distance_point_geom",
45
+ { kind: kind, n: vertices, method: method, note: "distance methods keep the GVL" },
46
+ stats: stats
47
+ )
48
+ end
49
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "_support"
4
+
5
+ TGGeometryBench.say_header("distance_within_radius")
6
+
7
+ COUNTS = (TGGeometryBench.full? ? [1_000, 10_000, 50_000] : [1_000, 10_000]).freeze
8
+ # Contract-required honesty case: tiny index + radius covering the whole data extent.
9
+ # This shows the crossover zone where paying for the rtree prefilter may not help
10
+ # compared to a direct full scan. The result is empirical: on some Ruby/CPU builds
11
+ # rtree may still win, but the benchmark must expose the scenario instead of
12
+ # only showing selective-radius wins.
13
+ TINY_COUNTS = (TGGeometryBench.full? ? [50, 100, 250] : [50, 100]).freeze
14
+
15
+ def box(x, y, size = 0.8)
16
+ TG::Geometry.polygon([[x, y], [x + size, y], [x + size, y + size], [x, y + size], [x, y]])
17
+ end
18
+
19
+ def entries(count)
20
+ count.times.map do |i|
21
+ x = (i % 250).to_f * 2.0
22
+ y = (i / 250).to_f * 2.0
23
+ [i, box(x, y)]
24
+ end
25
+ end
26
+
27
+ def brute_force(entries, x, y, radius)
28
+ entries.filter_map do |id, geom|
29
+ distance = geom.distance_to_xy(x, y)
30
+ [id, distance] if distance <= radius
31
+ end
32
+ end
33
+
34
+ def measure_radius_case(data, index, count, query_name, x, y, radius, initial:)
35
+ [
36
+ [:rtree_prefilter_exact_filter, -> { index.within_distance_xy(x, y, radius) }],
37
+ [:brute_force_full_scan, -> { brute_force(data, x, y, radius) }]
38
+ ].each do |method, callable|
39
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(initial)) do |iterations|
40
+ iterations.times { callable.call }
41
+ end
42
+
43
+ TGGeometryBench.report(
44
+ "distance_within_radius",
45
+ { n: count, query: query_name, method: method, note: "prefilter vs full scan; distance radius search keeps the GVL" },
46
+ stats: stats
47
+ )
48
+ end
49
+ end
50
+
51
+ COUNTS.each do |count|
52
+ data = entries(count)
53
+ index = TG::Geometry::Index.build(data, via: :geom, strategy: :rtree)
54
+
55
+ {
56
+ selective: [10.5, 10.5, 1.0, 250],
57
+ broad: [150.0, 20.0, 180.0, 25]
58
+ }.each do |query_name, (x, y, radius, initial)|
59
+ measure_radius_case(data, index, count, query_name, x, y, radius, initial: initial)
60
+ end
61
+ end
62
+
63
+ TINY_COUNTS.each do |count|
64
+ data = entries(count)
65
+ index = TG::Geometry::Index.build(data, via: :geom, strategy: :rtree)
66
+
67
+ # Query at the tiny grid center with a radius that covers the whole generated extent.
68
+ max_x = ((count - 1) % 250).to_f * 2.0 + 0.8
69
+ max_y = ((count - 1) / 250).to_f * 2.0 + 0.8
70
+ x = max_x / 2.0
71
+ y = max_y / 2.0
72
+ radius = Math.hypot(max_x, max_y) + 1.0
73
+
74
+ measure_radius_case(data, index, count, :tiny_full_extent, x, y, radius, initial: 100)
75
+ end
@@ -1,28 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
3
  require_relative "_support"
5
4
 
5
+ TGGeometryBench.say_header("ewkb_roundtrip")
6
+
6
7
  geom = TG::Geometry.polygon([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], srid: 4326)
7
8
  ewkb = geom.to_ewkb
8
9
 
9
- Benchmark.bm(32) do |x|
10
- x.report("tg parse_wkb -> to_ewkb 100k") do
11
- 100_000.times { TG::Geometry.parse_wkb(ewkb).to_ewkb }
12
- end
10
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(5_000)) do |iterations|
11
+ iterations.times { TG::Geometry.parse_wkb(ewkb).to_ewkb }
13
12
  end
14
13
 
14
+ TGGeometryBench.report(
15
+ "ewkb_roundtrip",
16
+ { library: :tg_geometry, operation: :parse_wkb_to_ewkb, payload_bytes: ewkb.bytesize },
17
+ stats: stats
18
+ )
19
+
15
20
  if ENV["WITH_RGEO"]
16
21
  begin
17
22
  require "rgeo"
18
23
  factory = RGeo::Cartesian.factory(srid: 4326)
19
24
  rgeo_geom = factory.parse_wkt("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")
20
25
 
21
- Benchmark.bm(32) do |x|
22
- x.report("rgeo WKT parse 100k") do
23
- 100_000.times { factory.parse_wkt(rgeo_geom.as_text) }
24
- end
26
+ rgeo_stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(5_000)) do |iterations|
27
+ iterations.times { factory.parse_wkt(rgeo_geom.as_text) }
25
28
  end
29
+
30
+ TGGeometryBench.report(
31
+ "ewkb_roundtrip",
32
+ { library: :rgeo, operation: :wkt_parse, payload_bytes: rgeo_geom.as_text.bytesize },
33
+ stats: rgeo_stats
34
+ )
26
35
  rescue LoadError
27
36
  warn "rgeo is not installed"
28
37
  end
@@ -3,23 +3,30 @@
3
3
  require_relative "_support"
4
4
 
5
5
  TGGeometryBench.say_header("falcon_concurrency")
6
- puts "No Falcon dependency is used here. This is a thread-read baseline for the immutable Index model."
7
- puts "Falcon/Async behavior remains an Pending decision until Roman approves a dedicated dependency/setup."
6
+ TGGeometryBench.report("falcon_concurrency_note", note: "thread_read_baseline_only_no_falcon_dependency")
8
7
 
9
- entries = TGGeometryBench.compact_entries(1_000)
8
+ entries_count = TGGeometryBench.env_integer("TGEOMETRY_BENCH_ENTRIES", 1_000, min: 1)
9
+ threads = TGGeometryBench.env_integer("TGEOMETRY_BENCH_THREADS", 4, min: 1)
10
+ entries = TGGeometryBench.compact_entries(entries_count)
10
11
  index = TGGeometryBench.build_index(entries, strategy: :rtree)
11
- threads = Integer(ENV.fetch("TGEOMETRY_BENCH_THREADS", "4"))
12
- iterations = TGGeometryBench.iterations(10_000)
12
+ points = TGGeometryBench.points_for(:compact)
13
13
 
14
- elapsed = Benchmark.realtime do
15
- threads.times.map do
14
+ stats = TGGeometryBench.measure_counted(
15
+ initial_iterations: TGGeometryBench.initial_iterations(2_000),
16
+ operations_per_iteration: threads
17
+ ) do |iterations_per_thread|
18
+ threads.times.map do |thread_index|
16
19
  Thread.new do
17
- iterations.times do |i|
18
- lon, lat = TGGeometryBench.points_for(:compact)[i % 3]
20
+ iterations_per_thread.times do |i|
21
+ lon, lat = points[(i + thread_index) % points.length]
19
22
  index.find_covering(lon, lat)
20
23
  end
21
24
  end
22
25
  end.each(&:join)
23
26
  end
24
27
 
25
- puts "threads=#{threads} iterations_per_thread=#{iterations} total_queries=#{threads * iterations} seconds=%.6f qps=%.2f" % [elapsed, (threads * iterations) / elapsed]
28
+ TGGeometryBench.report(
29
+ "falcon_concurrency",
30
+ { threads: threads, entries: entries_count, strategy: :rtree, operation: :find_covering },
31
+ stats: stats
32
+ )
@@ -40,7 +40,7 @@ end
40
40
 
41
41
  TGGeometryBench.say_header("feature_source")
42
42
 
43
- sizes = ENV["TGEOMETRY_BENCH_FULL"] == "1" ? [100, 1_000, 10_000, 50_000] : [100, 1_000]
43
+ sizes = TGGeometryBench.full? ? [100, 1_000, 10_000, 50_000] : [100, 1_000, 10_000]
44
44
 
45
45
  sizes.each do |size|
46
46
  json = FeatureSourceBenchData.feature_collection(size)
@@ -54,38 +54,64 @@ sizes.each do |size|
54
54
  direct_index = nil
55
55
  roundtrip_index = nil
56
56
 
57
- ruby_time = Benchmark.realtime do
58
- ruby_entries = FeatureSourceBenchData.ruby_json_parse_entries(path)
57
+ ruby = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.30) do |iterations|
58
+ iterations.times { ruby_entries = FeatureSourceBenchData.ruby_json_parse_entries(path) }
59
59
  end
60
60
 
61
- read_entries_time = Benchmark.realtime do
62
- feature_entries = TG::Geometry::FeatureSource.read_entries_file(path, id: ["properties", "@id"])
61
+ read_entries = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.30) do |iterations|
62
+ iterations.times do
63
+ feature_entries = TG::Geometry::FeatureSource.read_entries_file(path, id: ["properties", "@id"])
64
+ end
63
65
  end
64
66
 
65
- read_features_time = Benchmark.realtime do
66
- feature_rows = TG::Geometry::FeatureSource.read_features_file(path, id: ["properties", "@id"])
67
+ read_features = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.30) do |iterations|
68
+ iterations.times do
69
+ feature_rows = TG::Geometry::FeatureSource.read_features_file(path, id: ["properties", "@id"])
70
+ end
67
71
  end
68
72
 
69
- direct_index_time = Benchmark.realtime do
70
- direct_index = TG::Geometry::FeatureSource.build_index_file(path, id: ["properties", "@id"], strategy: :rtree)
73
+ direct_index_stat = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.30) do |iterations|
74
+ iterations.times do
75
+ direct_index = TG::Geometry::FeatureSource.build_index_file(path, id: ["properties", "@id"], strategy: :rtree)
76
+ end
71
77
  end
72
78
 
73
- roundtrip_index_time = Benchmark.realtime do
74
- roundtrip_index = TG::Geometry::Index.build(feature_entries, via: :geojson, strategy: :rtree)
79
+ # Ensure the roundtrip benchmark does not accidentally include read_entries.
80
+ feature_entries ||= TG::Geometry::FeatureSource.read_entries_file(path, id: ["properties", "@id"])
81
+
82
+ roundtrip_index_stat = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.30) do |iterations|
83
+ iterations.times do
84
+ roundtrip_index = TG::Geometry::Index.build(feature_entries, via: :geojson, strategy: :rtree)
85
+ end
75
86
  end
76
87
 
77
- puts "n=#{size} ruby_json_parse_sec=%.6f read_entries_sec=%.6f read_features_sec=%.6f build_index_direct_sec=%.6f build_index_from_entries_sec=%.6f entries=%d features=%d direct_size=%d roundtrip_size=%d rss_kb=%d" % [
78
- ruby_time,
79
- read_entries_time,
80
- read_features_time,
81
- direct_index_time,
82
- roundtrip_index_time,
83
- ruby_entries.length,
84
- feature_rows.length,
85
- direct_index.size,
86
- roundtrip_index.size,
87
- TGGeometryBench.rss_kb
88
- ]
88
+ read_plus_roundtrip_sec = read_entries[:median_sec] + roundtrip_index_stat[:median_sec]
89
+ read_plus_roundtrip_ops_per_sec = read_plus_roundtrip_sec.positive? ? read_entries[:iterations] / read_plus_roundtrip_sec : 0.0
90
+
91
+ common = {
92
+ n: size,
93
+ json_bytes: json.bytesize,
94
+ ruby_entries: ruby_entries.length,
95
+ feature_entries: feature_entries.length,
96
+ feature_rows: feature_rows.length,
97
+ direct_size: direct_index.size,
98
+ roundtrip_size: roundtrip_index.size,
99
+ rss_kb: TGGeometryBench.rss_kb
100
+ }
101
+
102
+ TGGeometryBench.report("feature_source", common.merge(mode: :ruby_json_parse_entries), stats: ruby)
103
+ TGGeometryBench.report("feature_source", common.merge(mode: :read_entries_file), stats: read_entries)
104
+ TGGeometryBench.report("feature_source", common.merge(mode: :read_features_file), stats: read_features)
105
+ TGGeometryBench.report("feature_source", common.merge(mode: :build_index_direct), stats: direct_index_stat)
106
+ TGGeometryBench.report("feature_source", common.merge(mode: :build_index_from_prepared_entries), stats: roundtrip_index_stat)
107
+ TGGeometryBench.report(
108
+ "feature_source_end_to_end",
109
+ common.merge(
110
+ mode: :read_entries_plus_build_index,
111
+ median_sec: read_plus_roundtrip_sec,
112
+ ops_per_sec: read_plus_roundtrip_ops_per_sec
113
+ )
114
+ )
89
115
  ensure
90
116
  file.close!
91
117
  end
@@ -3,25 +3,67 @@
3
3
  require_relative "_support"
4
4
 
5
5
  TGGeometryBench.say_header("flat_vs_rtree")
6
- iterations = TGGeometryBench.iterations(1_000)
7
6
 
8
7
  %i[compact long_thin overlapping].each do |kind|
9
8
  TGGeometryBench.sizes.each do |size|
10
9
  entries = TGGeometryBench.entries_for(kind, size)
11
10
 
12
- build_flat = Benchmark.realtime { @flat = TGGeometryBench.build_index(entries, strategy: :flat) }
13
- build_rtree = Benchmark.realtime { @rtree = TGGeometryBench.build_index(entries, strategy: :rtree) }
11
+ flat = nil
12
+ rtree = nil
14
13
 
15
- TGGeometryBench.points_for(kind).each do |lon, lat|
16
- flat_time = Benchmark.realtime { iterations.times { @flat.find_covering(lon, lat) } }
17
- rtree_time = Benchmark.realtime { iterations.times { @rtree.find_covering(lon, lat) } }
18
- puts "kind=#{kind} n=#{size} query=point lon=#{lon} lat=#{lat} flat_sec=%.6f rtree_sec=%.6f flat_qps=%.2f rtree_qps=%.2f build_flat=%.6f build_rtree=%.6f" % [flat_time, rtree_time, iterations / flat_time, iterations / rtree_time, build_flat, build_rtree]
14
+ flat_build = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.25) do |iterations|
15
+ iterations.times { flat = TGGeometryBench.build_index(entries, strategy: :flat) }
19
16
  end
17
+ rtree_build = TGGeometryBench.measure_counted(initial_iterations: 1, min_seconds: 0.25) do |iterations|
18
+ iterations.times { rtree = TGGeometryBench.build_index(entries, strategy: :rtree) }
19
+ end
20
+
21
+ # Rebuild once outside the build benchmark so query numbers do not depend on
22
+ # the last object produced by calibration/repeats.
23
+ flat = TGGeometryBench.build_index(entries, strategy: :flat)
24
+ rtree = TGGeometryBench.build_index(entries, strategy: :rtree)
25
+
26
+ TGGeometryBench.report("flat_vs_rtree_build", { kind: kind, n: size, strategy: :flat }, stats: flat_build)
27
+ TGGeometryBench.report("flat_vs_rtree_build", { kind: kind, n: size, strategy: :rtree }, stats: rtree_build)
28
+
29
+ TGGeometryBench.points_for(kind).each_with_index do |(lon, lat), point_index|
30
+ flat_stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(2_000)) do |iterations|
31
+ iterations.times { flat.find_covering(lon, lat) }
32
+ end
33
+ rtree_stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(2_000)) do |iterations|
34
+ iterations.times { rtree.find_covering(lon, lat) }
35
+ end
36
+
37
+ TGGeometryBench.report(
38
+ "flat_vs_rtree_point",
39
+ { kind: kind, n: size, strategy: :flat, point_index: point_index, lon: lon, lat: lat },
40
+ stats: flat_stats
41
+ )
42
+ TGGeometryBench.report(
43
+ "flat_vs_rtree_point",
44
+ { kind: kind, n: size, strategy: :rtree, point_index: point_index, lon: lon, lat: lat },
45
+ stats: rtree_stats
46
+ )
47
+ end
48
+
49
+ TGGeometryBench.rects_for(kind).each_with_index do |rect, rect_index|
50
+ flat_stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(200)) do |iterations|
51
+ iterations.times { flat.intersecting_rect(*rect) }
52
+ end
53
+ rtree_stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(200)) do |iterations|
54
+ iterations.times { rtree.intersecting_rect(*rect) }
55
+ end
20
56
 
21
- TGGeometryBench.rects_for(kind).each do |rect|
22
- flat_time = Benchmark.realtime { iterations.times { @flat.intersecting_rect(*rect) } }
23
- rtree_time = Benchmark.realtime { iterations.times { @rtree.intersecting_rect(*rect) } }
24
- puts "kind=#{kind} n=#{size} query=rect rect=#{rect.join(',')} flat_sec=%.6f rtree_sec=%.6f flat_qps=%.2f rtree_qps=%.2f" % [flat_time, rtree_time, iterations / flat_time, iterations / rtree_time]
57
+ TGGeometryBench.report(
58
+ "flat_vs_rtree_rect",
59
+ { kind: kind, n: size, strategy: :flat, rect_index: rect_index, rect: rect },
60
+ stats: flat_stats
61
+ )
62
+ TGGeometryBench.report(
63
+ "flat_vs_rtree_rect",
64
+ { kind: kind, n: size, strategy: :rtree, rect_index: rect_index, rect: rect },
65
+ stats: rtree_stats
66
+ )
25
67
  end
26
68
  end
27
69
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
3
  require_relative "_support"
5
4
 
6
- COUNTS = [1_000, 10_000].freeze
7
- STRATEGIES = %i[flat rtree].freeze
5
+ TGGeometryBench.say_header("geom_query")
8
6
 
7
+ COUNTS = (TGGeometryBench.full? ? [1_000, 10_000, 50_000] : [1_000, 10_000]).freeze
8
+ STRATEGIES = %i[flat rtree].freeze
9
9
 
10
10
  def box(x, y, size = 1.0)
11
11
  TG::Geometry.polygon([[x, y], [x + size, y], [x + size, y + size], [x, y + size], [x, y]])
@@ -18,16 +18,26 @@ COUNTS.each do |count|
18
18
  [i, box(x, y)]
19
19
  end
20
20
 
21
- small_query = box(10.5, 10.5, 2.0)
22
- large_query = box(0.5, 0.5, 120.0)
21
+ queries = {
22
+ small: box(10.5, 10.5, 2.0),
23
+ large: box(0.5, 0.5, 120.0),
24
+ miss: box(-1_000.0, -1_000.0, 1.0)
25
+ }
23
26
 
24
- puts "\n#{count} polygons"
25
27
  STRATEGIES.each do |strategy|
26
28
  index = TG::Geometry::Index.build(entries, via: :geom, strategy: strategy)
27
29
 
28
- Benchmark.bm(32) do |x|
29
- x.report("#{strategy} small intersecting_geom_ids") { 10_000.times { index.intersecting_geom_ids(small_query) } }
30
- x.report("#{strategy} large intersecting_geom_ids") { 1_000.times { index.intersecting_geom_ids(large_query) } }
30
+ queries.each do |query_name, query_geom|
31
+ initial = query_name == :large ? 100 : 1_000
32
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(initial)) do |iterations|
33
+ iterations.times { index.intersecting_geom_ids(query_geom) }
34
+ end
35
+
36
+ TGGeometryBench.report(
37
+ "geom_query",
38
+ { n: count, strategy: strategy, query: query_name, method: :intersecting_geom_ids },
39
+ stats: stats
40
+ )
31
41
  end
32
42
  end
33
43
  end
@@ -3,12 +3,9 @@
3
3
  require_relative "_support"
4
4
 
5
5
  TGGeometryBench.say_header("gvl_threshold")
6
- puts "First release intentionally performs parse/write/batch/query with GVL held."
7
- puts "This harness records baseline parse wall time only; it does not enable no-GVL execution."
6
+ TGGeometryBench.report("gvl_threshold_note", note: "baseline_only_gvl_is_held_no_public_release_gvl_knob")
8
7
 
9
- # Build one valid WKT polygon close to the requested byte size. The previous
10
- # implementation accidentally benchmarked the same tiny 39-byte polygon for all
11
- # target sizes because it used Array(...).first.
8
+ # Build one valid WKT polygon close to the requested byte size.
12
9
  def polygon_wkt_at_least(target_bytes)
13
10
  points_count = [4, target_bytes / 38].max
14
11
 
@@ -27,15 +24,17 @@ def polygon_wkt_at_least(target_bytes)
27
24
  end
28
25
  end
29
26
 
30
- sizes = [128, 1_024, 16_384, 262_144]
31
- iterations = TGGeometryBench.iterations(2_000)
32
-
33
- sizes.each do |target_bytes|
27
+ [128, 1_024, 16_384, 262_144].each do |target_bytes|
34
28
  payload = polygon_wkt_at_least(target_bytes)
29
+ initial = target_bytes >= 262_144 ? 10 : 500
35
30
 
36
- time = Benchmark.realtime do
31
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(initial)) do |iterations|
37
32
  iterations.times { TG::Geometry.parse_wkt(payload) }
38
33
  end
39
34
 
40
- puts "target_bytes=#{target_bytes} payload_bytes=#{payload.bytesize} iterations=#{iterations} seconds=%.6f ops_per_sec=%.2f" % [time, iterations / time]
35
+ TGGeometryBench.report(
36
+ "gvl_threshold_parse_wkt",
37
+ { target_bytes: target_bytes, payload_bytes: payload.bytesize },
38
+ stats: stats
39
+ )
41
40
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
3
  require_relative "_support"
5
4
 
5
+ TGGeometryBench.say_header("nearest_segment")
6
+
6
7
  [100, 1_000, 10_000].each do |count|
7
8
  points = count.times.map do |i|
8
9
  angle = 2.0 * Math::PI * i / count
@@ -11,10 +12,13 @@ require_relative "_support"
11
12
  points << points.first
12
13
  ring = TG::Geometry.polygon(points).polygon.exterior_ring
13
14
 
14
- puts "\nring segments: #{ring.num_segments}"
15
- Benchmark.bm(28) do |x|
16
- x.report("1M nearest_segment calls") do
17
- 1_000_000.times { ring.nearest_segment(0.25, 0.33) }
18
- end
15
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(10_000)) do |iterations|
16
+ iterations.times { ring.nearest_segment(0.25, 0.33) }
19
17
  end
18
+
19
+ TGGeometryBench.report(
20
+ "nearest_segment",
21
+ { segments: ring.num_segments, query: "0.25:0.33" },
22
+ stats: stats
23
+ )
20
24
  end
@@ -8,10 +8,23 @@ TGGeometryBench.say_header("objectspace_memsize")
8
8
  %i[compact long_thin overlapping].each do |kind|
9
9
  TGGeometryBench.sizes.each do |size|
10
10
  entries = TGGeometryBench.entries_for(kind, size)
11
+ TGGeometryBench.gc_start
11
12
  flat = TGGeometryBench.build_index(entries, strategy: :flat)
13
+ TGGeometryBench.gc_start
12
14
  rtree = TGGeometryBench.build_index(entries, strategy: :rtree)
13
15
  geom = TG::Geometry.parse_geojson(entries.first.last)
14
16
 
15
- puts "kind=#{kind} n=#{size} geom_memsize=#{ObjectSpace.memsize_of(geom)} flat_memsize=#{ObjectSpace.memsize_of(flat)} rtree_memsize=#{ObjectSpace.memsize_of(rtree)}"
17
+ TGGeometryBench.report(
18
+ "objectspace_memsize",
19
+ {
20
+ kind: kind,
21
+ n: size,
22
+ geom_memsize: ObjectSpace.memsize_of(geom),
23
+ flat_memsize: ObjectSpace.memsize_of(flat),
24
+ rtree_memsize: ObjectSpace.memsize_of(rtree),
25
+ rtree_over_flat: ObjectSpace.memsize_of(rtree) - ObjectSpace.memsize_of(flat),
26
+ rss_kb: TGGeometryBench.rss_kb
27
+ }
28
+ )
16
29
  end
17
30
  end
@@ -21,18 +21,21 @@ geojson = '{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]
21
21
  wkb = TG::Geometry.parse_wkt(small).to_wkb
22
22
 
23
23
  cases = {
24
- "wkt:small" => [small, :wkt],
25
- "geojson:small" => [geojson, :geojson],
26
- "wkb:small" => [wkb, :wkb],
27
- "wkt:medium" => [medium, :wkt],
28
- "wkt:large" => [large, :wkt]
24
+ "wkt_small" => [small, :wkt, 5_000],
25
+ "geojson_small" => [geojson, :geojson, 5_000],
26
+ "wkb_small" => [wkb, :wkb, 5_000],
27
+ "wkt_medium" => [medium, :wkt, 500],
28
+ "wkt_large" => [large, :wkt, 50]
29
29
  }
30
30
 
31
- iterations = TGGeometryBench.iterations(10_000)
32
-
33
- cases.each do |name, (payload, format)|
34
- time = Benchmark.realtime do
31
+ cases.each do |name, (payload, format, initial)|
32
+ stats = TGGeometryBench.measure_counted(initial_iterations: TGGeometryBench.initial_iterations(initial)) do |iterations|
35
33
  iterations.times { TG::Geometry.parse(payload, format: format) }
36
34
  end
37
- puts "%s iterations=%d seconds=%.6f ops_per_sec=%.2f" % [name, iterations, time, iterations / time]
35
+
36
+ TGGeometryBench.report(
37
+ "parse_throughput",
38
+ { case: name, format: format, payload_bytes: payload.bytesize },
39
+ stats: stats
40
+ )
38
41
  end
@@ -4,27 +4,28 @@ require_relative "_support"
4
4
 
5
5
  TGGeometryBench.say_header("rss_stability")
6
6
 
7
- total_queries = Integer(ENV.fetch("TGEOMETRY_RSS_QUERIES", "10_000_000"))
8
- rebuilds = Integer(ENV.fetch("TGEOMETRY_RSS_REBUILDS", "100"))
9
- entries_count = Integer(ENV.fetch("TGEOMETRY_RSS_ENTRIES", "1_000"))
10
- max_drift_kb = Integer(ENV.fetch("TGEOMETRY_RSS_MAX_DRIFT_KB", "51_200"))
7
+ total_queries = TGGeometryBench.env_integer("TGEOMETRY_RSS_QUERIES", 10_000_000, min: 1)
8
+ rebuilds = TGGeometryBench.env_integer("TGEOMETRY_RSS_REBUILDS", 100, min: 1)
9
+ entries_count = TGGeometryBench.env_integer("TGEOMETRY_RSS_ENTRIES", 1_000, min: 1)
10
+ max_drift_kb = TGGeometryBench.env_integer("TGEOMETRY_RSS_MAX_DRIFT_KB", 51_200, min: 0)
11
11
 
12
12
  queries_per_rebuild = (total_queries / rebuilds).clamp(1, total_queries)
13
13
  entries = TGGeometryBench.compact_entries(entries_count)
14
14
  points = TGGeometryBench.points_for(:compact)
15
15
  packed_batch = TGGeometryBench.packed_points(points * 10)
16
16
 
17
- GC.start
18
- GC.start
17
+ TGGeometryBench.gc_start
18
+ TGGeometryBench.gc_start
19
19
  start_rss = TGGeometryBench.rss_kb
20
20
  peak_rss = start_rss
21
21
  samples = []
22
22
 
23
23
  queries_executed = 0
24
- started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
+ started_at = TGGeometryBench.monotonic
25
25
 
26
26
  rebuilds.times do |cycle|
27
- index = TGGeometryBench.build_index(entries, strategy: (cycle.even? ? :flat : :rtree))
27
+ strategy = cycle.even? ? :flat : :rtree
28
+ index = TGGeometryBench.build_index(entries, strategy: strategy)
28
29
 
29
30
  queries_per_rebuild.times do |q|
30
31
  lon, lat = points[(q + cycle) % points.length]
@@ -39,29 +40,39 @@ rebuilds.times do |cycle|
39
40
 
40
41
  index = nil
41
42
 
42
- if (cycle % 10).zero?
43
- GC.start
44
- rss = TGGeometryBench.rss_kb
45
- peak_rss = [peak_rss, rss].max
46
- samples << [cycle, queries_executed, rss]
47
- end
43
+ next unless (cycle % 10).zero?
44
+
45
+ TGGeometryBench.gc_start
46
+ rss = TGGeometryBench.rss_kb
47
+ peak_rss = [peak_rss, rss].max
48
+ samples << [cycle, queries_executed, rss]
48
49
  end
49
50
 
50
- GC.start
51
- GC.start
51
+ TGGeometryBench.gc_start
52
+ TGGeometryBench.gc_start
52
53
  finish_rss = TGGeometryBench.rss_kb
53
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
54
+ elapsed = TGGeometryBench.monotonic - started_at
54
55
  drift_kb = finish_rss - start_rss
55
56
 
56
- puts format(
57
- "queries=%d rebuilds=%d entries=%d elapsed_s=%.2f start_rss_kb=%d peak_rss_kb=%d finish_rss_kb=%d drift_kb=%d",
58
- queries_executed, rebuilds, entries_count, elapsed,
59
- start_rss, peak_rss, finish_rss, drift_kb
57
+ TGGeometryBench.report(
58
+ "rss_stability",
59
+ {
60
+ queries: queries_executed,
61
+ rebuilds: rebuilds,
62
+ entries: entries_count,
63
+ elapsed_sec: elapsed,
64
+ qps: queries_executed / elapsed,
65
+ start_rss_kb: start_rss,
66
+ peak_rss_kb: peak_rss,
67
+ finish_rss_kb: finish_rss,
68
+ drift_kb: drift_kb,
69
+ max_drift_kb: max_drift_kb,
70
+ sample_count: samples.length
71
+ }
60
72
  )
61
73
 
62
- if samples.length > 1
63
- puts "samples (cycle, queries, rss_kb):"
64
- samples.each { |row| puts " #{row.inspect}" }
74
+ samples.each do |cycle, queries, rss|
75
+ TGGeometryBench.report("rss_stability_sample", cycle: cycle, queries: queries, rss_kb: rss)
65
76
  end
66
77
 
67
78
  if max_drift_kb.positive? && drift_kb > max_drift_kb