contrek 1.4.0 → 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: 8d3c8751ab1e2182443227811912d1b0ad864dde99b62aae50c51b1265314bf3
4
- data.tar.gz: d3a1ad93edd90e4ebb41a4e1371cd0ed5a2c134fbc093f898a920b8e81e8c249
3
+ metadata.gz: eada79f09d6eede09420a2f933496d8839152f741d9c90e7d6b63fc7537bec29
4
+ data.tar.gz: 5652df8b513dfb26db08a1640cf59e2642a0feba7df2f9ce62eec0fe4e6dc445
5
5
  SHA512:
6
- metadata.gz: e423133c5e9dfe2f490c84e2c47bdd585d9771949ccf8dbd1afd21b254dea876c7bc18f5cd1e71e327d50cec3de2a7abe5bcb39029f578ab043b43c9a785b607
7
- data.tar.gz: 3b343824bbe74a380e6e3f084d628c00a7b3efeac373cf65a80b50de66827d5c62b44a4e33ed248b3144336a35c7712e96681b8f754e1dbcee280680b3caf9ab
6
+ metadata.gz: 8dc280b616db1de56688ca94f5c1bcef1bf02cb81ff820638ebdf0bed821ba37745799a579a2660da6dc0934afa46a94aae51affdcb3b6fd987710a84f5f85d9
7
+ data.tar.gz: c815702b9abafa8b5e6de4e1f2e05ab6b03ed91248efe0786807307db6ebff19a7e2c9d002fadf94a12e2c9f41a8c07fe0ca05c01f5a838722090a737c888983
data/CHANGELOG.md CHANGED
@@ -149,7 +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
153
 
154
154
  ## [1.4.0] - 2026-08-13
155
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.4.0)
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
 
@@ -600,7 +601,8 @@ Typical information includes
600
601
  benchmarks:{...},
601
602
  groups:1,
602
603
  treemap:[],
603
- options:{...}
604
+ options:{...},
605
+ number_of_threads: 4
604
606
  }
605
607
  ```
606
608
 
@@ -717,27 +719,6 @@ Instead, it repeatedly combines smaller topologically consistent pieces into a l
717
719
  This strategy makes it possible to process images whose full size would otherwise exceed the available memory.
718
720
 
719
721
 
720
- ## Merge order
721
-
722
- The merge order is intentionally left to the thread scheduler.
723
-
724
- For three stripes, both of the following execution orders are valid:
725
-
726
- ```
727
- (B1 + B2) + B3
728
- ```
729
-
730
- or
731
-
732
- ```
733
- B1 + (B2 + B3)
734
- ```
735
-
736
- Although the intermediate merge sequence changes, the reconstructed polygons remain geometrically equivalent.
737
-
738
- The exact ordering of coordinates may differ because polygon simplification is applied after merging.
739
-
740
-
741
722
  ## Polygon reconstruction
742
723
 
743
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
 
@@ -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;
@@ -69,6 +69,7 @@ struct ProcessResult {
69
69
  int groups;
70
70
  int width, height;
71
71
  int versus;
72
+ int number_of_threads;
72
73
  bool has_bounds = false;
73
74
  std::map<std::string, double> benchmarks;
74
75
  std::list<Polygon> polygons;
@@ -166,6 +166,7 @@ ProcessResult* Finder::process_info() {
166
166
  pr->has_bounds = this->options_.bounds;
167
167
  pr->versus = this->options_.versus;
168
168
  pr->options = this->input_options;
169
+ pr->number_of_threads = this->number_of_threads();
169
170
  FakeCluster fake_cluster(pr->polygons, this->options_);
170
171
  cpu_timer.start();
171
172
  fake_cluster.compress_coords(pr->polygons, this->options_);
@@ -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;
@@ -24,10 +24,10 @@ Tile::Tile(Finder *finder, int start_x, int end_x, std::string name, int index,
24
24
  : finder(finder),
25
25
  start_x_(start_x),
26
26
  end_x_(end_x),
27
- name_(name),
28
27
  index_(index),
29
- benchmarks(b),
30
- order_(order) {
28
+ order_(order),
29
+ name_(name),
30
+ benchmarks(b) {
31
31
  this->shapes_pool = new ShapePool();
32
32
  this->shapes_pool->set_owner(this);
33
33
  this->shapes_pools.push_back(this->shapes_pool);
@@ -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)
@@ -95,7 +95,8 @@ module Contrek
95
95
  width: @maximum_width,
96
96
  height: @height,
97
97
  versus: options[:versus],
98
- options: options
98
+ options: options,
99
+ number_of_threads: @number_of_threads
99
100
  }
100
101
  metadata[:treemap] = @whole_tile.compute_treemap if options[:treemap]
101
102
  Contrek::Finder::Result.new(raw_polygons, metadata)
@@ -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.4.0"
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.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni