Skip to content

Commit d490def

Browse files
committed
tinkering with C loops to extract a bit of runtime saving
1 parent 47d5cc0 commit d490def

1 file changed

Lines changed: 78 additions & 62 deletions

File tree

src/guillotine/core/_solver.c

Lines changed: 78 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#define PY_SSIZE_T_CLEAN // Ensure Py_ssize_t is defined before including Python.h
22
#include <Python.h> // Python C API header
3-
#include <stdint.h> // for fixed-width integer types like uint32_t, uint16_t
3+
#include <stdint.h> // for fixed-width integer types like int32_t, int16_t
44

55
/* Decision type constants - must match constants.py */
66
#define DECISION_EMPTY 0
@@ -10,24 +10,33 @@
1010
#define DECISION_DEFECT 4
1111
#define DECISION_PURE 5
1212

13+
/* Defect array access macros (each defect entry has NR_DEFECT_FIELDS fields) */
14+
#define NR_DEFECT_FIELDS 6
15+
#define DEF_X(d) defects[(d)*NR_DEFECT_FIELDS + 0] /* x start of defect bounding box */
16+
#define DEF_Y(d) defects[(d)*NR_DEFECT_FIELDS + 1] /* y start of defect bounding box */
17+
#define DEF_W(d) defects[(d)*NR_DEFECT_FIELDS + 2] /* width of defect bounding box */
18+
#define DEF_H(d) defects[(d)*NR_DEFECT_FIELDS + 3] /* height of defect bounding box */
19+
#define DEF_X_END(d) defects[(d)*NR_DEFECT_FIELDS + 4] /* x end of defect bounding box (exclusive) */
20+
#define DEF_Y_END(d) defects[(d)*NR_DEFECT_FIELDS + 5] /* y end of defect bounding box (exclusive) */
21+
1322
/*
14-
* Bit layout for Fd_packed (uint16_t):
23+
* Bit layout for Fd_packed (int16_t):
1524
* bits 15-13 : decision type (DECISION_* constants, values 0-5, fits in 3 bits)
1625
* bits 12-0 : cut parameter (cut position z, max value 8191, covers any practical sheet)
1726
*
18-
* Pack: packed = ((uint16_t)type << 13) | ((uint16_t)param & 0x1FFF)
27+
* Pack: packed = ((int16_t)type << 13) | ((int16_t)param & 0x1FFF)
1928
* Unpack: type = (packed >> 13) & 0x7
2029
* param = packed & 0x1FFF
2130
*/
22-
#define PACK_FD(type, param) (((uint16_t)(type) << 13) | ((uint16_t)(param) & 0x1FFF))
31+
#define PACK_FD(type, param) (((int16_t)(type) << 13) | ((int16_t)(param) & 0x1FFF))
2332
#define UNPACK_TYPE(packed) (((packed) >> 13) & 0x7)
2433
#define UNPACK_PARAM(packed) ((packed) & 0x1FFF)
2534

2635
/*
2736
* Array index macros.
2837
* prefix, F_values are 2D: shape (W0+1, H0+1)
29-
* Fd_values is 4D uint32_t: shape (W0+1, H0+1, W0+1, H0+1)
30-
* Fd_packed is 4D uint16_t: shape (W0+1, H0+1, W0+1, H0+1), same layout
38+
* Fd_values is 4D int32_t: shape (W0+1, H0+1, W0+1, H0+1)
39+
* Fd_packed is 4D int16_t: shape (W0+1, H0+1, W0+1, H0+1), same layout
3140
*/
3241
#define IDX_2D(arr, stride1, i, j) ((arr)[(i) * (stride1) + (j)])
3342
#define IDX_4D(arr, stride0, stride1, stride2, w, h, x, y) ((arr)[(w) * (stride0) + (h) * (stride1) + (x) * (stride2) + (y)])
@@ -96,20 +105,26 @@ static void fill_F_core(
96105
int stride = H0 + 1;
97106

98107
for (int w = 1; w <= W0; w++) {
99-
int nx = np_x_len[w];
108+
int nx = np_x_len[w];
109+
int w_off = w * stride;
110+
int x_cut_base = w * max_cuts_x;
111+
int half_w = w >> 1;
112+
100113
for (int h = 1; h <= H0; h++) {
101-
int ny = np_y_len[h];
114+
int ny = np_y_len[h];
115+
int y_cut_base = h * max_cuts_y;
116+
int half_h = h >> 1;
102117

103-
int32_t best_val = g_values[w * stride + h];
104-
int best_type = (g_indices[w * stride + h] >= 0) ? DECISION_FILL : DECISION_EMPTY;
105-
int32_t best_param = (g_indices[w * stride + h] >= 0) ? g_indices[w * stride + h] : 0;
118+
int32_t g_idx = g_indices[w_off + h];
119+
int32_t best_val = g_values [w_off + h];
120+
int best_type = (g_idx >= 0) ? DECISION_FILL : DECISION_EMPTY;
121+
int32_t best_param = (g_idx >= 0) ? g_idx : 0;
106122

107123
/* Vertical cuts — symmetry: only z <= w/2 */
108-
int half_w = w >> 1;
109124
for (int i = 0; i < nx; i++) {
110-
int z = np_x_arr[w * max_cuts_x + i];
125+
int z = np_x_arr[x_cut_base + i];
111126
if (z > half_w) break;
112-
int32_t total = F_values[z * stride + h] + F_values[(w - z) * stride + h];
127+
int32_t total = F_values[z * stride + h] + F_values[w_off - z * stride + h];
113128
if (total > best_val) {
114129
best_val = total;
115130
best_type = DECISION_CUT_X;
@@ -118,21 +133,20 @@ static void fill_F_core(
118133
}
119134

120135
/* Horizontal cuts — symmetry: only z <= h/2 */
121-
int half_h = h >> 1;
122136
for (int i = 0; i < ny; i++) {
123-
int z = np_y_arr[h * max_cuts_y + i];
137+
int z = np_y_arr[y_cut_base + i];
124138
if (z > half_h) break;
125-
int32_t total = F_values[w * stride + z] + F_values[w * stride + (h - z)];
139+
int32_t total = F_values[w_off + z] + F_values[w_off + (h - z)];
126140
if (total > best_val) {
127141
best_val = total;
128142
best_type = DECISION_CUT_Y;
129143
best_param = z;
130144
}
131145
}
132146

133-
F_values[w * stride + h] = best_val;
134-
F_type [w * stride + h] = (int8_t)best_type;
135-
F_param [w * stride + h] = best_param;
147+
F_values[w_off + h] = best_val;
148+
F_type [w_off + h] = (int8_t)best_type;
149+
F_param [w_off + h] = best_param;
136150
}
137151
}
138152
}
@@ -142,17 +156,17 @@ static void fill_F_core(
142156
* All arrays are passed as raw pointers — numpy guarantees contiguous memory.
143157
*
144158
* Memory layout vs previous version:
145-
* Fd_values: int32 → uint32 (values are always non-negative, doubles max representable value)
146-
* Fd_type + Fd_param → Fd_packed uint16 (saves 1 array, 33% less memory for Fd tables)
159+
* Fd_values: int32_t (values are always non-negative)
160+
* Fd_type + Fd_param → Fd_packed int16_t (saves 1 array, 33% less memory for Fd tables)
147161
*/
148162
static void fill_Fd_core(
149163
int W0, int H0,
150-
int32_t *prefix,
151-
int32_t *F_values,
152-
uint32_t *Fd_values,
153-
int32_t *np_x_arr, int32_t *np_x_len, int max_cuts_x,
154-
int32_t *np_y_arr, int32_t *np_y_len, int max_cuts_y,
155-
int32_t *defects, int n_def
164+
int32_t *prefix,
165+
int32_t *F_values,
166+
int32_t *Fd_values,
167+
int32_t *np_x_arr, int32_t *np_x_len, int max_cuts_x,
168+
int32_t *np_y_arr, int32_t *np_y_len, int max_cuts_y,
169+
int32_t *defects, int n_def
156170
) {
157171
int stride_p = H0 + 1;
158172
int stride_F = H0 + 1;
@@ -176,54 +190,58 @@ static void fill_Fd_core(
176190
+ IDX_2D(prefix, stride_p, x, y );
177191

178192
if (defect_count == 0) {
179-
IDX_4D(Fd_values, stride0, stride1, stride2, w, h, x, y) = (uint32_t)IDX_2D(F_values, stride_F, w, h);
193+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h, x, y) = IDX_2D(F_values, stride_F, w, h);
180194
continue;
181195
}
182196

183-
uint32_t best_val = 0;
197+
int32_t best_val = 0;
184198

185-
/* X cuts */
199+
/* X cuts — normal pattern positions */
186200
for (int i = 0; i < nx; i++) {
187201
int z = np_x_arr[w * max_cuts_x + i];
188-
uint32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, z, h, x, y) +
189-
IDX_4D(Fd_values, stride0, stride1, stride2, w-z, h, x+z, y);
202+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, z, h, x, y) +
203+
IDX_4D(Fd_values, stride0, stride1, stride2, w-z, h, x+z, y);
190204
if (total > best_val) best_val = total;
191205
}
192206

207+
/* X cuts — defect-aligned positions */
193208
for (int d = 0; d < n_def; d++) {
194-
int dx = defects[d*6 + 0], dx_end = defects[d*6 + 4];
195-
if (x >= dx_end || dx >= x+w) continue;
196-
int z1 = dx - x;
197-
int z2 = dx_end - x;
198-
int cuts[2] = { z1, z2 };
199-
for (int ci = 0; ci < 2; ci++) {
200-
int z = cuts[ci];
201-
if (z <= 0 || z >= w) continue;
202-
uint32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, z, h, x, y) +
203-
IDX_4D(Fd_values, stride0, stride1, stride2, w-z, h, x+z, y);
209+
if (x >= DEF_X_END(d) || DEF_X(d) >= x + w) continue;
210+
int z1 = DEF_X(d) - x;
211+
int z2 = DEF_X_END(d) - x;
212+
if (z1 > 0 && z1 < h) {
213+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z1, x, y) +
214+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z1, x, y+z1);
215+
if (total > best_val) best_val = total;
216+
}
217+
if (z2 > 0 && z2 < h) {
218+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z2, x, y) +
219+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z2, x, y+z2);
204220
if (total > best_val) best_val = total;
205221
}
206222
}
207223

208-
/* Y cuts */
224+
/* Y cuts — normal pattern positions */
209225
for (int i = 0; i < ny; i++) {
210226
int z = np_y_arr[h * max_cuts_y + i];
211-
uint32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z, x, y) +
212-
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z, x, y+z);
227+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z, x, y) +
228+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z, x, y+z);
213229
if (total > best_val) best_val = total;
214230
}
215231

232+
/* Y cuts — defect-aligned positions */
216233
for (int d = 0; d < n_def; d++) {
217-
int dy = defects[d*6 + 1], dy_end = defects[d*6 + 5];
218-
if (y >= dy_end || dy >= y+h) continue;
219-
int z1 = dy - y;
220-
int z2 = dy_end - y;
221-
int cuts[2] = { z1, z2 };
222-
for (int ci = 0; ci < 2; ci++) {
223-
int z = cuts[ci];
224-
if (z <= 0 || z >= h) continue;
225-
uint32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z, x, y) +
226-
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z, x, y+z);
234+
if (y >= DEF_Y_END(d) || DEF_Y(d) >= y + h) continue;
235+
int z1 = DEF_Y(d) - y;
236+
int z2 = DEF_Y_END(d) - y;
237+
if (z1 > 0 && z1 < h) {
238+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z1, x, y) +
239+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z1, x, y+z1);
240+
if (total > best_val) best_val = total;
241+
}
242+
if (z2 > 0 && z2 < h) {
243+
int32_t total = IDX_4D(Fd_values, stride0, stride1, stride2, w, z2, x, y) +
244+
IDX_4D(Fd_values, stride0, stride1, stride2, w, h-z2, x, y+z2);
227245
if (total > best_val) best_val = total;
228246
}
229247
}
@@ -240,9 +258,8 @@ static PyObject* py_fill_Fd(PyObject *self, PyObject *args) {
240258
int W0, H0, max_cuts_x, max_cuts_y, n_def;
241259
PyObject *o_pre, *o_F, *o_Fd, *o_nx, *o_nlx, *o_ny, *o_nly, *o_def;
242260

243-
/* FIXED: Format string "iiOOOOOOOOiii" now correctly counts 8 objects and 5 ints */
244-
if (!PyArg_ParseTuple(args, "iiOOOOOOOOiii", &W0, &H0, &o_pre, &o_F, &o_Fd,
245-
&o_nx, &o_nlx, &o_ny, &o_nly, &o_def,
261+
if (!PyArg_ParseTuple(args, "iiOOOOOOOOiii", &W0, &H0, &o_pre, &o_F, &o_Fd,
262+
&o_nx, &o_nlx, &o_ny, &o_nly, &o_def,
246263
&max_cuts_x, &max_cuts_y, &n_def)) return NULL;
247264

248265
Py_buffer b_pre, b_F, b_Fd, b_nx, b_nlx, b_ny, b_nly, b_def;
@@ -255,8 +272,8 @@ static PyObject* py_fill_Fd(PyObject *self, PyObject *args) {
255272
PyObject_GetBuffer(o_nly, &b_nly, PyBUF_SIMPLE);
256273
PyObject_GetBuffer(o_def, &b_def, PyBUF_SIMPLE);
257274

258-
fill_Fd_core(W0, H0, (int32_t*)b_pre.buf, (int32_t*)b_F.buf, (uint32_t*)b_Fd.buf,
259-
(int32_t*)b_nx.buf, (int32_t*)b_nlx.buf, max_cuts_x,
275+
fill_Fd_core(W0, H0, (int32_t*)b_pre.buf, (int32_t*)b_F.buf, (int32_t*)b_Fd.buf,
276+
(int32_t*)b_nx.buf, (int32_t*)b_nlx.buf, max_cuts_x,
260277
(int32_t*)b_ny.buf, (int32_t*)b_nly.buf, max_cuts_y, (int32_t*)b_def.buf, n_def);
261278

262279
PyBuffer_Release(&b_pre); PyBuffer_Release(&b_F); PyBuffer_Release(&b_Fd);
@@ -381,7 +398,6 @@ static PyObject* py_fill_F(PyObject *self, PyObject *args)
381398
return NULL;
382399
}
383400

384-
/* method table */
385401
/* method table */
386402
static PyMethodDef SolverMethods[] = {
387403
{"fill_g", py_fill_g, METH_VARARGS, "Precompute best single-item tiling (g tables)."},

0 commit comments

Comments
 (0)