contrek 1.3.9 → 1.4.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b6523977828bd1e9398d438e045ed1335826630c472a8d1caff6cf0b09ed94e
4
- data.tar.gz: 247332cbd56024ef7e7847294c1ffa32bc59d0a13ef65dc23f5bd6641126b993
3
+ metadata.gz: eada79f09d6eede09420a2f933496d8839152f741d9c90e7d6b63fc7537bec29
4
+ data.tar.gz: 5652df8b513dfb26db08a1640cf59e2642a0feba7df2f9ce62eec0fe4e6dc445
5
5
  SHA512:
6
- metadata.gz: c632b70f8e0130d39a70c7d13e37f0a0fbec38e045a0c681e7327adc9cf62655e9d35897e0b0f0963777297fd04d4be171f1204a4b9b6974c03344d8318910fd
7
- data.tar.gz: 8941c3749e72b0d3fbdf90fb5a5516b8e7730c3b71a826e6332a87809333b48d9dd18d2136e3917693d6c0009a035ffce4c904295263a1163fa81e542de2fd7c
6
+ metadata.gz: 8dc280b616db1de56688ca94f5c1bcef1bf02cb81ff820638ebdf0bed821ba37745799a579a2660da6dc0934afa46a94aae51affdcb3b6fd987710a84f5f85d9
7
+ data.tar.gz: c815702b9abafa8b5e6de4e1f2e05ab6b03ed91248efe0786807307db6ebff19a7e2c9d002fadf94a12e2c9f41a8c07fe0ca05c01f5a838722090a737c888983
data/CHANGELOG.md CHANGED
@@ -149,4 +149,10 @@ All notable changes to this project will be documented in this file.
149
149
  - Fixed issue found on treemap determination.
150
150
 
151
151
  ## [1.3.9] - 2026-08-09
152
- - Removed the curses development dependency along with the old TerminalTracker; actually Bitmaps::Rendering.to_window renders visually better results.
152
+ - Removed the curses development dependency along with the old TerminalTracker; actually Contrek::Bitmaps::PngBitmap.to_window renders visually better results.
153
+
154
+ ## [1.4.0] - 2026-08-13
155
+ - Added deterministic merge mode usefull when you need a byte-for-byte reproducible outputs across different runs (by default, the merge stage is non-deterministic to maximize performance and thread utilization).
156
+
157
+ ## [1.4.1] - 2026-08-23
158
+ - The number_of_threads effective value is now returned inside metadata output struct too.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.3.9)
4
+ contrek (1.4.1)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
data/PERFORMANCE.md CHANGED
@@ -1,4 +1,4 @@
1
- # Contrek Performance Tuning
1
+ # Contrek Performance Tuning
2
2
 
3
3
  This document describes optional dependencies and configuration tips to get the best performance out of Contrek on large images.
4
4
 
@@ -41,7 +41,7 @@ sudo make install
41
41
  sudo ldconfig
42
42
  ```
43
43
 
44
- > ⚠️ The `-DZLIB_COMPAT=ON` flag is mandatory. Without it, zlib-ng uses a different ABI and CMake's `find_package(ZLIB)` won't detect it.
44
+ > The `-DZLIB_COMPAT=ON` flag is mandatory. Without it, zlib-ng uses a different ABI and CMake's `find_package(ZLIB)` won't detect it.
45
45
 
46
46
  **macOS:**
47
47
  ```bash
@@ -136,9 +136,45 @@ Consider this depends your system. Probably is better not to saturate all cores
136
136
 
137
137
  ---
138
138
 
139
- ## 4. Combining All Optimizations
139
+ ## 4. Transparent Huge Pages (THP) — Kernel Memory Allocation
140
140
 
141
- Install zlib-ng and tcmalloc, then configure:
141
+ When processing high-resolution images, Contrek performs dense dynamic memory allocations. By default, Linux manages memory in standard 4 KiB pages, which creates heavy kernel overhead from frequent page faults and Translation Lookaside Buffer (TLB) misses under multithreaded loads.
142
+
143
+ Enabling **Transparent Huge Pages (`always`)** forces the Linux kernel to allocate memory in **2 MiB blocks**, reducing page table lookup entries by a factor of 512 and dramatically speeding up memory access.
144
+
145
+ ### Temporary Activation
146
+
147
+ To enable it immediately for your current Linux session:
148
+
149
+ ```bash
150
+ echo always | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
151
+ ```
152
+
153
+ ### Permanent Activation
154
+ To ensure this setting persists across system reboots:
155
+
156
+ Open /etc/default/grub in an editor:
157
+
158
+ ```bash
159
+ sudo nano /etc/default/grub
160
+ ```
161
+ Add transparent_hugepage=always to GRUB_CMDLINE_LINUX_DEFAULT:
162
+
163
+ ```Plaintext
164
+ GRUB_CMDLINE_LINUX_DEFAULT="quiet splash transparent_hugepage=always"
165
+ ```
166
+
167
+ Update GRUB:
168
+
169
+ ```bash
170
+ sudo update-grub
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 5. Combining All Optimizations
176
+
177
+ Install zlib-ng and tcmalloc, enable THP, then configure:
142
178
 
143
179
  ```ruby
144
180
  # Ruby
data/README.md CHANGED
@@ -166,7 +166,8 @@ Metadata includes timing information (expressed in milliseconds), the number of
166
166
  },
167
167
  :groups=>2,
168
168
  :named_sequence=>"",
169
- :treemap=>[]
169
+ :treemap=>[],
170
+ :number_of_threads=>0
170
171
  }
171
172
  ```
172
173
 
@@ -376,11 +377,9 @@ The native implementation uses the number of available CPU cores reported by the
376
377
 
377
378
  ## A note about determinism
378
379
 
379
- The tracing stage itself is deterministic.
380
+ By default, the merge stage is **non-deterministic** to maximize performance and thread utilization.
380
381
 
381
- The merge stage is not.
382
-
383
- Each stripe is processed independently and adjacent stripes are merged as soon as they become available. Depending on thread scheduling, the merge order may differ from one execution to another.
382
+ Each stripe is processed independently, and adjacent stripes are merged opportunistically as soon as they become available. Depending on thread scheduling, the merge order may differ between runs.
384
383
 
385
384
  For example, given three stripes:
386
385
 
@@ -400,7 +399,13 @@ while another execution may produce:
400
399
  B1 + (B2 + B3)
401
400
  ```
402
401
 
403
- Both executions generate equivalent polygons, but the order of intermediate merge operations is differentso the final coordinate sequence is not guaranteed to be byte-for-byte identical across executions.
402
+ Both executions generate equivalent polygons, but the order of intermediate merge operations is different so the final coordinate sequence is not guaranteed to be byte-for-byte identical across executions.
403
+
404
+ If you require byte-for-byte reproducible outputs across different runs, you can enable the **deterministic merge mode** (setting `deterministic: true`).
405
+
406
+ In deterministic mode, stripes are merged strictly according to a fixed binary reduction tree based on their order and index.
407
+
408
+ > **Requirement:** Deterministic mode requires an **even number of stripes/tiles** to process correctly.
404
409
 
405
410
  ## Native execution
406
411
 
@@ -596,7 +601,8 @@ Typical information includes
596
601
  benchmarks:{...},
597
602
  groups:1,
598
603
  treemap:[],
599
- options:{...}
604
+ options:{...},
605
+ number_of_threads: 4
600
606
  }
601
607
  ```
602
608
 
@@ -713,27 +719,6 @@ Instead, it repeatedly combines smaller topologically consistent pieces into a l
713
719
  This strategy makes it possible to process images whose full size would otherwise exceed the available memory.
714
720
 
715
721
 
716
- ## Merge order
717
-
718
- The merge order is intentionally left to the thread scheduler.
719
-
720
- For three stripes, both of the following execution orders are valid:
721
-
722
- ```
723
- (B1 + B2) + B3
724
- ```
725
-
726
- or
727
-
728
- ```
729
- B1 + (B2 + B3)
730
- ```
731
-
732
- Although the intermediate merge sequence changes, the reconstructed polygons remain geometrically equivalent.
733
-
734
- The exact ordering of coordinates may differ because polygon simplification is applied after merging.
735
-
736
-
737
722
  ## Polygon reconstruction
738
723
 
739
724
  Internally, polygons crossing a stripe boundary are temporarily divided into smaller pieces.
@@ -21,6 +21,11 @@
21
21
 
22
22
  namespace Contrek {
23
23
 
24
+ enum class Versus {
25
+ A, // Anticlockwise
26
+ O // Clockwise
27
+ };
28
+
24
29
  enum class MatchMode {
25
30
  NOT_COLOR, // Tracks border of what is not target color
26
31
  EXACT_COLOR // Tracks border of what exactly matchs target color
@@ -31,15 +36,22 @@ enum class Connectivity {
31
36
  OMNIDIRECTIONAL = 8 // 8 directions
32
37
  };
33
38
 
39
+
34
40
  struct Config {
35
41
  int threads = 4;
36
42
  int tiles = 2;
43
+ Versus versus = Versus::A;
37
44
  bool compress_unique = false;
38
45
  bool compress_linear = false;
39
46
  bool compress_raster = false;
40
47
  bool compress_douglas_peucker = false;
41
48
  bool compress_visvalingam = false;
49
+ float compress_visvalingam_tolerance = 0.0f;
42
50
  bool treemap = false;
51
+ bool bounds = false;
52
+ bool named_sequences = false;
53
+ bool unsafe_mode = false;
54
+ bool deterministic = false;
43
55
  int32_t target_color = -1;
44
56
  MatchMode mode = MatchMode::NOT_COLOR;
45
57
  Connectivity connectivity_mode = Connectivity::ORTHOGONAL;
@@ -81,19 +93,33 @@ inline TraceContext trace(const std::string& image_path, const Config& cfg = Con
81
93
  ctx.matcher = std::make_unique<RGBMatcher>(color_to_match);
82
94
  }
83
95
 
96
+ std::string versus_str = (cfg.versus == Versus::O) ? "o" : "a";
84
97
  ctx.internal_args = {
85
- {"versus", Identifier{"a"}},
98
+ {"versus", Identifier{versus_str}},
99
+ {"number_of_tiles", static_cast<int64_t>(cfg.tiles)},
100
+ {"treemap", cfg.treemap},
101
+ {"bounds", cfg.bounds},
102
+ {"named_sequences", cfg.named_sequences},
103
+ {"unsafe_mode", cfg.unsafe_mode},
104
+ {"deterministic", cfg.deterministic}
86
105
  };
87
- Options compression_opts;
88
-
89
- if (cfg.compress_unique) compression_opts["uniq"] = true;
90
- if (cfg.compress_linear) compression_opts["linear"] = true;
91
- if (cfg.compress_visvalingam) compression_opts["visvalingam"] = true;
92
- if (cfg.compress_unique || cfg.compress_linear || cfg.compress_visvalingam) ctx.internal_args["compression"] = compression_opts;
93
- ctx.internal_args["number_of_tiles"] = cfg.tiles;
94
106
  if (cfg.connectivity_mode == Connectivity::OMNIDIRECTIONAL) {
95
- ctx.internal_args["connectivity"] = 8;
107
+ ctx.internal_args["connectivity"] = static_cast<int64_t>(8);
96
108
  }
109
+ Options compress_opts;
110
+ if (cfg.compress_unique) compress_opts["uniq"] = true;
111
+ if (cfg.compress_linear) compress_opts["linear"] = true;
112
+ if (cfg.compress_raster) compress_opts["raster"] = true;
113
+ if (cfg.compress_douglas_peucker) compress_opts["douglas_peucker"] = true;
114
+ if (cfg.compress_visvalingam) compress_opts["visvalingam"] = true;
115
+ if (cfg.compress_visvalingam_tolerance > 0.0f) {
116
+ compress_opts["visvalingam_tolerance"] = static_cast<double>(cfg.compress_visvalingam_tolerance);
117
+ }
118
+ if (cfg.compress_unique || cfg.compress_linear || cfg.compress_raster ||
119
+ cfg.compress_douglas_peucker || cfg.compress_visvalingam) {
120
+ ctx.internal_args["compress"] = compress_opts;
121
+ }
122
+
97
123
  ctx.finder = std::make_unique<Finder>(cfg.threads, ctx.bitmap.get(), ctx.matcher.get(), ctx.internal_args);
98
124
  ctx.result = std::unique_ptr<ProcessResult>(ctx.finder->process_info());
99
125
 
@@ -28,6 +28,7 @@ void FinderUtils::sanitize_options(pf_Options& options, const Options& incoming_
28
28
  options.named_sequences);
29
29
  options.bounds = incoming_options.get<bool>("bounds", options.bounds);
30
30
  options.unsafe_mode = incoming_options.get<bool>("unsafe_mode", options.unsafe_mode);
31
+ options.deterministic = incoming_options.get<bool>("deterministic", options.deterministic);
31
32
 
32
33
  if (const Options* compress = incoming_options.get_options("compress")) {
33
34
  options.compress_uniq = compress->get<bool>("uniq", options.compress_uniq);
@@ -110,6 +110,7 @@ ProcessResult* PolygonFinder::process_info() {
110
110
  pr->has_bounds = this->node_cluster->options->bounds;
111
111
  pr->versus = this->options.versus;
112
112
  pr->options = this->incoming_options_;
113
+ pr->number_of_threads = 0;
113
114
 
114
115
  if (this->node_cluster->options->named_sequences && typeid(*this->source_bitmap) == typeid(Bitmap))
115
116
  { std::string sequence;
@@ -53,6 +53,7 @@ struct pf_Options {
53
53
  bool named_sequences = false;
54
54
  bool unsafe_mode = false;
55
55
  bool bounds = false;
56
+ bool deterministic = false;
56
57
  int connectivity_offset = 0;
57
58
  float compress_visvalingam_tolerance = 10.0;
58
59
  int number_of_tiles = 1;
@@ -68,6 +69,7 @@ struct ProcessResult {
68
69
  int groups;
69
70
  int width, height;
70
71
  int versus;
72
+ int number_of_threads;
71
73
  bool has_bounds = false;
72
74
  std::map<std::string, double> benchmarks;
73
75
  std::list<Polygon> polygons;
@@ -135,16 +135,17 @@ Tile* Cluster::merge_tiles() {
135
135
  }
136
136
  }
137
137
  }
138
-
139
- double past_tot_outer = tiles_.front()->benchmarks.outer + tiles_.back()->benchmarks.outer;
140
- double past_tot_inner = tiles_.front()->benchmarks.inner + tiles_.back()->benchmarks.inner;
138
+ Tile* first_tile = tiles_.front();
139
+ Tile* last_tile = tiles_.back();
140
+ double past_tot_outer = first_tile->benchmarks.outer + last_tile->benchmarks.outer;
141
+ double past_tot_inner = first_tile->benchmarks.inner + last_tile->benchmarks.inner;
141
142
  Benchmarks b{
142
143
  tot_outer + past_tot_outer,
143
144
  tot_inner + past_tot_inner
144
145
  };
145
146
 
146
147
  Tile* tile = new Tile(
147
- this->finder_, tiles_.front()->start_x(), tiles_.back()->end_x(), tiles_.front()->name() + tiles_.back()->name(), b);
148
+ this->finder_, first_tile->start_x(), last_tile->end_x(), first_tile->name() + last_tile->name(), first_tile->index() / 2, b, first_tile->order() + 1);
148
149
 
149
150
  tile->assign_shapes(new_shapes);
150
151
  for (const auto shape : detach_shapes) {
@@ -10,6 +10,7 @@
10
10
  #include <utility>
11
11
  #include <vector>
12
12
  #include <string>
13
+ #include <algorithm>
13
14
  #include "Finder.h"
14
15
  #include "../../bitmaps/Bitmap.h"
15
16
  #include "../../matchers/Matcher.h"
@@ -35,6 +36,9 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, const Op
35
36
  if (cw < 1.0) {
36
37
  throw std::runtime_error("One pixel tile width minimum!");
37
38
  }
39
+ if (this->options_.deterministic && (this->options_.number_of_tiles % 2 != 0)) {
40
+ throw std::invalid_argument("Deterministic mode requires an even number of tiles!");
41
+ }
38
42
  int x = 0;
39
43
  for (int tile_index = 0; tile_index < this->options_.number_of_tiles; tile_index++)
40
44
  { int tile_end_x = static_cast<int>(cw * (tile_index + 1));
@@ -65,14 +69,14 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, const Op
65
69
  }
66
70
 
67
71
  Tile* tile = new Tile(this, payload.tile_start_x, payload.tile_end_x,
68
- std::to_string(payload.tile_index), Benchmarks {0, 0});
72
+ std::to_string(payload.tile_index), payload.tile_index, Benchmarks {0, 0});
69
73
  tile->initial_process(finder);
70
74
  tiles_.queue_push(tile);
71
75
  });
72
76
 
73
77
  x = tile_end_x - 1;
74
78
  }
75
- this->process_tiles();
79
+ this->process_tiles(this->options_.deterministic);
76
80
  reports["init"] = cpu_timer.stop();
77
81
  }
78
82
 
@@ -82,7 +86,7 @@ Finder::Finder(int number_of_threads, const Options& options)
82
86
  reports["init"] = 0;
83
87
  }
84
88
 
85
- void Finder::process_tiles() {
89
+ void Finder::process_tiles(bool deterministic) {
86
90
  std::vector<Tile*> arriving_tiles;
87
91
 
88
92
  while (true) {
@@ -97,7 +101,14 @@ void Finder::process_tiles() {
97
101
  arriving_tiles.begin(),
98
102
  arriving_tiles.end(),
99
103
  [&](Tile* t) {
100
- return (t->start_x() == (tile->end_x() - 1)) || ((t->end_x() - 1) == tile->start_x());
104
+ bool is_adjacent = (t->start_x() == (tile->end_x() - 1)) ||
105
+ ((t->end_x() - 1) == tile->start_x());
106
+ if (!is_adjacent) return false;
107
+ if (deterministic && !this->last_couple(tile, t)) {
108
+ if (tile->order() != t->order()) return false;
109
+ if (std::min(t->index(), tile->index()) % 2 != 0) return false;
110
+ }
111
+ return true;
101
112
  });
102
113
 
103
114
  if (it != arriving_tiles.end()) {
@@ -124,6 +135,13 @@ void Finder::process_tiles() {
124
135
  }
125
136
  }
126
137
 
138
+ bool Finder::last_couple(const Tile* tile_a, const Tile* tile_b) const {
139
+ bool a_is_first = tile_a->index() < tile_b->index();
140
+ const Tile* first = a_is_first ? tile_a : tile_b;
141
+ const Tile* last = a_is_first ? tile_b : tile_a;
142
+ return (first->start_x() == 0) && (last->end_x() == this->maximum_width());
143
+ }
144
+
127
145
  Finder::~Finder() {
128
146
  if (this->whole_tile) {
129
147
  delete this->whole_tile; // last tile to be deleted (not owned by a cluster)
@@ -148,6 +166,7 @@ ProcessResult* Finder::process_info() {
148
166
  pr->has_bounds = this->options_.bounds;
149
167
  pr->versus = this->options_.versus;
150
168
  pr->options = this->input_options;
169
+ pr->number_of_threads = this->number_of_threads();
151
170
  FakeCluster fake_cluster(pr->polygons, this->options_);
152
171
  cpu_timer.start();
153
172
  fake_cluster.compress_coords(pr->polygons, this->options_);
@@ -40,13 +40,14 @@ class Finder : public Poolable {
40
40
  std::mutex finders_mutex;
41
41
  std::map<std::string, double> reports;
42
42
  CpuTimer cpu_timer;
43
+ bool last_couple(const Tile* tile_a, const Tile* tile_b) const;
43
44
 
44
45
  protected:
45
46
  Queue<Tile*> tiles_;
46
47
  int maximum_width_;
47
48
  int height = 0;
48
49
  Tile* whole_tile = nullptr;
49
- void process_tiles();
50
+ void process_tiles(bool deterministic = false);
50
51
 
51
52
  public:
52
53
  using Poolable::Poolable;
@@ -48,7 +48,7 @@ void Merger::add_tile(ProcessResult& result)
48
48
  }
49
49
 
50
50
  int end_x = this->current_x + result.width;
51
- Tile* tile = new Tile(this, this->current_x, end_x, std::to_string(tiles_.size()), Benchmarks {0, 0});
51
+ Tile* tile = new Tile(this, this->current_x, end_x, std::to_string(tiles_.size()), tiles_.size(), Benchmarks {0, 0});
52
52
  tile->assign_raw_polygons(result.polygons, result.treemap);
53
53
  tiles_.queue_push(tile);
54
54
 
@@ -38,6 +38,7 @@ class Poolable {
38
38
  }
39
39
  }
40
40
  void wait();
41
+ int number_of_threads() const { return number_of_threads_; }
41
42
 
42
43
  private:
43
44
  std::vector<std::thread> workers;
@@ -20,10 +20,12 @@
20
20
  class Shape;
21
21
  class Polyline;
22
22
 
23
- Tile::Tile(Finder *finder, int start_x, int end_x, std::string name, const Benchmarks& b)
23
+ Tile::Tile(Finder *finder, int start_x, int end_x, std::string name, int index, const Benchmarks& b, int order)
24
24
  : finder(finder),
25
25
  start_x_(start_x),
26
26
  end_x_(end_x),
27
+ index_(index),
28
+ order_(order),
27
29
  name_(name),
28
30
  benchmarks(b) {
29
31
  this->shapes_pool = new ShapePool();
@@ -29,11 +29,13 @@ class Tile {
29
29
  Finder *finder;
30
30
  int start_x_;
31
31
  int end_x_;
32
+ int index_;
33
+ int order_;
32
34
  std::string name_;
33
35
  std::vector<Shape*> shapes_;
34
36
 
35
37
  public:
36
- Tile(Finder *finder, int start_x, int end_x, std::string name, const Benchmarks& b);
38
+ Tile(Finder *finder, int start_x, int end_x, std::string name, int index, const Benchmarks& b, int order = 0);
37
39
  virtual ~Tile();
38
40
  Tile *prev = nullptr;
39
41
  Tile *next = nullptr;
@@ -41,6 +43,8 @@ class Tile {
41
43
  Cluster *cluster = nullptr;
42
44
  int start_x() const { return start_x_; }
43
45
  int end_x() const { return end_x_; }
46
+ int index() const { return index_; }
47
+ int order() const { return order_; }
44
48
  std::string name() const { return name_; }
45
49
  const std::vector<Shape*>& shapes() const { return shapes_; }
46
50
  std::vector<Shape*>& shapes() { return shapes_; }
@@ -232,6 +232,7 @@ class To_Ruby<ProcessResult*>
232
232
  return_me[Symbol("height")] = pr->height;
233
233
  return_me[Symbol("versus")] = Symbol(pr->versus == Node::O ? "o" : "a");
234
234
  return_me[Symbol("options")] = options_to_ruby(pr->options);
235
+ return_me[Symbol("number_of_threads")] = pr->number_of_threads;
235
236
 
236
237
  Rice::Array out;
237
238
  for (Polygon& x : pr->polygons)
@@ -106,6 +106,8 @@ module Contrek
106
106
  start_x: @tiles.first.start_x,
107
107
  end_x: @tiles.last.end_x,
108
108
  benchmarks: {outer: tot_outer + past_tot_outer, inner: tot_inner + past_tot_inner},
109
+ index: @tiles.first.index / 2,
110
+ order: @tiles.first.order + 1,
109
111
  name: @tiles.first.name + @tiles.last.name
110
112
  )
111
113
 
@@ -39,6 +39,7 @@ module Contrek
39
39
  x = 0
40
40
  current_versus = options[:versus]
41
41
  raise "Define versus!" if current_versus.nil?
42
+ raise ArgumentError, "Deterministic mode requires an even number of tiles!" if options[:deterministic] && @number_of_tiles.odd?
42
43
 
43
44
  @number_of_tiles.times do |tile_index|
44
45
  tile_end_x = (cw * (tile_index + 1)).to_i
@@ -62,7 +63,7 @@ module Contrek
62
63
  finder: self,
63
64
  start_x: payload[:tile_start_x],
64
65
  end_x: payload[:tile_end_x],
65
- name: payload[:tile_index].to_s
66
+ index: payload[:tile_index]
66
67
  )
67
68
  tile.initial_process!(finder)
68
69
  @tiles << tile
@@ -70,7 +71,7 @@ module Contrek
70
71
 
71
72
  x = tile_end_x - 1
72
73
  end
73
- process_tiles!(bitmap, height: bitmap.h)
74
+ process_tiles!(bitmap, height: bitmap.h, deterministic: @options[:deterministic])
74
75
  end.real
75
76
  end
76
77
 
@@ -94,7 +95,8 @@ module Contrek
94
95
  width: @maximum_width,
95
96
  height: @height,
96
97
  versus: options[:versus],
97
- options: options
98
+ options: options,
99
+ number_of_threads: @number_of_threads
98
100
  }
99
101
  metadata[:treemap] = @whole_tile.compute_treemap if options[:treemap]
100
102
  Contrek::Finder::Result.new(raw_polygons, metadata)
@@ -106,7 +108,12 @@ module Contrek
106
108
 
107
109
  private
108
110
 
109
- def process_tiles!(bitmap, height:)
111
+ def last_couple?(tile_a, tile_b)
112
+ ordereds = [tile_a, tile_b].sort_by(&:index)
113
+ ordereds.first.start_x == 0 && ordereds.last.end_x == maximum_width
114
+ end
115
+
116
+ def process_tiles!(bitmap, height:, deterministic: false)
110
117
  arriving_tiles = []
111
118
  loop do
112
119
  tile = @tiles.pop
@@ -114,7 +121,17 @@ module Contrek
114
121
  @whole_tile = tile
115
122
  return
116
123
  end
117
- if (twin_tile = arriving_tiles.find { |b| (b.start_x == (tile.end_x - 1)) || ((b.end_x - 1) == tile.start_x) })
124
+ twin_tile = arriving_tiles.find do |t|
125
+ is_adjacent = (t.start_x == (tile.end_x - 1)) || ((t.end_x - 1) == tile.start_x)
126
+ next false unless is_adjacent
127
+ if deterministic && !last_couple?(tile, t)
128
+ next false if tile.order != t.order
129
+ next false if [t.index, tile.index].min.odd?
130
+ end
131
+ true
132
+ end
133
+
134
+ if twin_tile
118
135
  cluster = Cluster.new(finder: self, height: height)
119
136
  if twin_tile.start_x == (tile.end_x - 1)
120
137
  cluster.add(tile)
@@ -126,13 +143,12 @@ module Contrek
126
143
  enqueue!(cluster: cluster) do |payload|
127
144
  merged_tile = payload[:cluster].merge_tiles!
128
145
  @tiles << merged_tile
129
- # usefull external access to each merged_tile
130
- @block&.call(merged_tile, bitmap)
146
+ @block&.call(merged_tile, bitmap) # usefull external access to each merged_tile
131
147
  end
132
148
  arriving_tiles.delete(twin_tile)
133
- next
149
+ else
150
+ arriving_tiles << tile
134
151
  end
135
- arriving_tiles << tile
136
152
  end
137
153
  end
138
154
  end
@@ -35,7 +35,7 @@ module Contrek
35
35
  finder: self,
36
36
  start_x: @current_x,
37
37
  end_x: end_x,
38
- name: @tiles.size.to_s
38
+ index: @tiles.size
39
39
  )
40
40
  tile.assign_raw_polygons!(result[:polygons])
41
41
 
@@ -3,14 +3,16 @@
3
3
  module Contrek
4
4
  module Concurrent
5
5
  class Tile
6
- attr_reader :start_x, :end_x, :benchmarks, :shapes, :name
6
+ attr_reader :start_x, :end_x, :benchmarks, :shapes, :name, :index, :order
7
7
  attr_accessor :prev, :next, :circular_next, :cluster
8
8
 
9
- def initialize(finder:, start_x:, end_x:, name:, benchmarks: {})
9
+ def initialize(finder:, start_x:, end_x:, index:, name: nil, benchmarks: {}, order: 0)
10
10
  @finder = finder
11
11
  @start_x = start_x
12
12
  @end_x = end_x
13
- @name = name
13
+ @index = index
14
+ @order = order
15
+ @name = name || index.to_s
14
16
  @prev = nil
15
17
  @next = nil
16
18
  @benchmarks = {outer: 0, inner: 0}.merge(benchmarks)
@@ -54,7 +54,8 @@ module Contrek
54
54
  height: @source_bitmap.h,
55
55
  treemap: (@node_cluster.treemap if @options.has_key?(:treemap)),
56
56
  options: @ori_options,
57
- versus: @options[:versus]
57
+ versus: @options[:versus],
58
+ number_of_threads: 0
58
59
  }
59
60
  Result.new(@node_cluster.polygons, metadata)
60
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Contrek
4
- VERSION = "1.3.9"
4
+ VERSION = "1.4.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.9
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni