contrek 1.3.9 → 1.4.0
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 +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +9 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +5 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +22 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +2 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +4 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +5 -1
- data/lib/contrek/finder/concurrent/cluster.rb +2 -0
- data/lib/contrek/finder/concurrent/finder.rb +23 -8
- data/lib/contrek/finder/concurrent/merger.rb +1 -1
- data/lib/contrek/finder/concurrent/tile.rb +5 -3
- data/lib/contrek/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d3c8751ab1e2182443227811912d1b0ad864dde99b62aae50c51b1265314bf3
|
|
4
|
+
data.tar.gz: d3a1ad93edd90e4ebb41a4e1371cd0ed5a2c134fbc093f898a920b8e81e8c249
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e423133c5e9dfe2f490c84e2c47bdd585d9771949ccf8dbd1afd21b254dea876c7bc18f5cd1e71e327d50cec3de2a7abe5bcb39029f578ab043b43c9a785b607
|
|
7
|
+
data.tar.gz: 3b343824bbe74a380e6e3f084d628c00a7b3efeac373cf65a80b50de66827d5c62b44a4e33ed248b3144336a35c7712e96681b8f754e1dbcee280680b3caf9ab
|
data/CHANGELOG.md
CHANGED
|
@@ -150,3 +150,6 @@ All notable changes to this project will be documented in this file.
|
|
|
150
150
|
|
|
151
151
|
## [1.3.9] - 2026-08-09
|
|
152
152
|
- Removed the curses development dependency along with the old TerminalTracker; actually Bitmaps::Rendering.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).
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -376,11 +376,9 @@ The native implementation uses the number of available CPU cores reported by the
|
|
|
376
376
|
|
|
377
377
|
## A note about determinism
|
|
378
378
|
|
|
379
|
-
|
|
379
|
+
By default, the merge stage is **non-deterministic** to maximize performance and thread utilization.
|
|
380
380
|
|
|
381
|
-
|
|
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.
|
|
381
|
+
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
382
|
|
|
385
383
|
For example, given three stripes:
|
|
386
384
|
|
|
@@ -400,7 +398,13 @@ while another execution may produce:
|
|
|
400
398
|
B1 + (B2 + B3)
|
|
401
399
|
```
|
|
402
400
|
|
|
403
|
-
Both executions generate equivalent polygons, but the order of intermediate merge operations is
|
|
401
|
+
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.
|
|
402
|
+
|
|
403
|
+
If you require byte-for-byte reproducible outputs across different runs, you can enable the **deterministic merge mode** (setting `deterministic: true`).
|
|
404
|
+
|
|
405
|
+
In deterministic mode, stripes are merged strictly according to a fixed binary reduction tree based on their order and index.
|
|
406
|
+
|
|
407
|
+
> **Requirement:** Deterministic mode requires an **even number of stripes/tiles** to process correctly.
|
|
404
408
|
|
|
405
409
|
## Native execution
|
|
406
410
|
|
|
@@ -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);
|
|
@@ -135,16 +135,17 @@ Tile* Cluster::merge_tiles() {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
double
|
|
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_,
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
|
@@ -20,12 +20,14 @@
|
|
|
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
27
|
name_(name),
|
|
28
|
-
|
|
28
|
+
index_(index),
|
|
29
|
+
benchmarks(b),
|
|
30
|
+
order_(order) {
|
|
29
31
|
this->shapes_pool = new ShapePool();
|
|
30
32
|
this->shapes_pool->set_owner(this);
|
|
31
33
|
this->shapes_pools.push_back(this->shapes_pool);
|
|
@@ -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_; }
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -106,7 +107,12 @@ module Contrek
|
|
|
106
107
|
|
|
107
108
|
private
|
|
108
109
|
|
|
109
|
-
def
|
|
110
|
+
def last_couple?(tile_a, tile_b)
|
|
111
|
+
ordereds = [tile_a, tile_b].sort_by(&:index)
|
|
112
|
+
ordereds.first.start_x == 0 && ordereds.last.end_x == maximum_width
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def process_tiles!(bitmap, height:, deterministic: false)
|
|
110
116
|
arriving_tiles = []
|
|
111
117
|
loop do
|
|
112
118
|
tile = @tiles.pop
|
|
@@ -114,7 +120,17 @@ module Contrek
|
|
|
114
120
|
@whole_tile = tile
|
|
115
121
|
return
|
|
116
122
|
end
|
|
117
|
-
|
|
123
|
+
twin_tile = arriving_tiles.find do |t|
|
|
124
|
+
is_adjacent = (t.start_x == (tile.end_x - 1)) || ((t.end_x - 1) == tile.start_x)
|
|
125
|
+
next false unless is_adjacent
|
|
126
|
+
if deterministic && !last_couple?(tile, t)
|
|
127
|
+
next false if tile.order != t.order
|
|
128
|
+
next false if [t.index, tile.index].min.odd?
|
|
129
|
+
end
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if twin_tile
|
|
118
134
|
cluster = Cluster.new(finder: self, height: height)
|
|
119
135
|
if twin_tile.start_x == (tile.end_x - 1)
|
|
120
136
|
cluster.add(tile)
|
|
@@ -126,13 +142,12 @@ module Contrek
|
|
|
126
142
|
enqueue!(cluster: cluster) do |payload|
|
|
127
143
|
merged_tile = payload[:cluster].merge_tiles!
|
|
128
144
|
@tiles << merged_tile
|
|
129
|
-
# usefull external access to each merged_tile
|
|
130
|
-
@block&.call(merged_tile, bitmap)
|
|
145
|
+
@block&.call(merged_tile, bitmap) # usefull external access to each merged_tile
|
|
131
146
|
end
|
|
132
147
|
arriving_tiles.delete(twin_tile)
|
|
133
|
-
|
|
148
|
+
else
|
|
149
|
+
arriving_tiles << tile
|
|
134
150
|
end
|
|
135
|
-
arriving_tiles << tile
|
|
136
151
|
end
|
|
137
152
|
end
|
|
138
153
|
end
|
|
@@ -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:,
|
|
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
|
-
@
|
|
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)
|
data/lib/contrek/version.rb
CHANGED