Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,45 @@ Judged **against the noise floor above**, not in isolation:

Since jsfeatNext is a port of jsfeat, ratios near **1.0× are the expected, healthy state** — the two are doing the same arithmetic. A *sustained* move is interesting; a single-run move is not.

## Open finding: the YAPE detectors are consistently slower
## YAPE detectors: `yape` RESOLVED, `yape06` still open

> **Partly resolved, and the original hypothesis was wrong.**
>
> This section long carried the theory that importing
> `hessian_min_eigen_value` across modules blocked V8 inlining. Profiling
> `yape06.detect` refuted it: **96.6% of the time is in `detect` itself and
> ~0.6% in the helpers**, and the two implementations' inner loops are
> character-for-character identical.
>
> A structural probe then replicated `detect`'s body verbatim under four
> shapes, verified to find identical corner counts. Across three clean runs,
> two effects reproduced — a class method is slower than an object-literal
> property, and calling an ESM **imported binding** from a hot loop is slower
> than calling a plain module-scope `const` holding the same function
> (imported bindings are live, so each access carries an indirection). The
> second gained 12–28% every run, and is a one-line change.
>
> Aliasing the helpers to module-scope consts in `yape06.ts` and `yape.ts`
> gave a **split result**:
>
> | case | before (8 samples) | after (4 samples) | status |
> | --- | --- | --- | --- |
> | **`yape`** | 8/8 jsfeat, 1.23–1.53 | 1.02\* / 1.05\* / 1.14 / 1.06\* | **resolved** — sign now flips |
> | `yape06` | 8/8 jsfeat, 1.11–1.45 | 1.14 / 1.47 / 1.41 / 1.24 | **unchanged, still open** |
>
> \* = jsfeatNext faster in that run.
>
> The split is consistent with the profile rather than at odds with it:
> `yape06` calls `compute_laplacian` **once per frame** and
> `hessian_min_eigen_value` only for candidate pixels — which is exactly why
> the helpers showed 0.6%. `yape`'s helpers sit in the per-pixel loop, so
> aliasing them matters there. `yape06`'s alias is kept for consistency but
> measured no benefit; **its cause remains unknown**, and the class-vs-object
> effect the probe also showed is the remaining untested candidate.
>
> The measurements below are the pre-fix record.

### Original finding (pre-fix record)

The first real signal this harness produced. Eight samples, pooled from two
separate sessions on an idle machine (power connected, browser and editor
Expand Down
15 changes: 13 additions & 2 deletions src/yape/yape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ import { third_check, is_local_maxima, perform_one_point, lev_table_t } from "./
import { matrix_t } from "../matrix_t/matrix_t";
import { keypoint_t } from "../keypoint_t/keypoint_t";

/**
* Module-scope aliases for the three helpers `detect` calls in its per-pixel
* loop. Calling an ESM imported binding is slower than calling a plain
* `const` holding the same function -- imported bindings are live, so each
* access carries an indirection. Measured under #86; see `bench/README.md`
* for the numbers, which are deliberately not repeated here.
*/
const thirdCheck = third_check;
const isLocalMaxima = is_local_maxima;
const performOnePoint = perform_one_point;

/**
* YAPE ("Yet Another Point Extractor") interest-point detector: scores each
* pixel by comparing it against a precomputed circle of samples at the given
Expand Down Expand Up @@ -129,7 +140,7 @@ export class yape {
if (im < img[rowx + R] && img[rowx + R] < ip && im < img[rowx - R] && img[rowx - R] < ip) {
scores[rowx] = 0;
} else {
perform_one_point(img, rowx, scores, im, ip, dirs, opposite, dirs_count);
performOnePoint(img, rowx, scores, im, ip, dirs, opposite, dirs_count);
}
}
}
Expand All @@ -144,7 +155,7 @@ export class yape {
// if this pixel is 0, the next one will not be good enough. Skip it.
(++x, ++rowx);
} else {
if (third_check(scores, rowx, w) >= 3 && is_local_maxima(scores, rowx, score, hw, R)) {
if (thirdCheck(scores, rowx, w) >= 3 && isLocalMaxima(scores, rowx, score, hw, R)) {
pt = points[number_of_points];
((pt.x = x), (pt.y = y), (pt.score = abs_score));
++number_of_points;
Expand Down
20 changes: 18 additions & 2 deletions src/yape06/yape06.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ import { matrix_t } from "../matrix_t/matrix_t";
import { keypoint_t } from "../keypoint_t/keypoint_t";
import { compute_laplacian, hessian_min_eigen_value } from "./yape06_utils";

/**
* Module-scope aliases for the two helpers, mirroring `yape.ts`.
*
* Calling an ESM imported binding is slower than calling a plain `const`
* holding the same function -- imported bindings are live, so each access
* carries an indirection. That effect is real and measurable where a helper
* runs per pixel (it resolved the `yape` finding), but it did **not** measure
* any benefit here: `compute_laplacian` runs once per frame and
* `hessian_min_eigen_value` only for candidate pixels, so neither is called
* often enough for the indirection to matter. Kept for consistency with
* `yape.ts`, not because it speeds this module up. `yape06`'s own gap against
* original jsfeat is still unexplained -- see `bench/README.md`.
*/
const computeLaplacian = compute_laplacian;
const hessianMinEigenValue = hessian_min_eigen_value;

/**
* YAPE06 interest-point detector: thresholds a Laplacian response map, then
* rejects edge-like responses via the minimum eigenvalue of the local
Expand Down Expand Up @@ -109,7 +125,7 @@ export class yape06 extends jsfeatNext {
while (--x >= 0) {
laplacian[x] = 0;
}
compute_laplacian(srd_d, laplacian, w, Dxx, Dyy, sx, sy, ex, ey);
computeLaplacian(srd_d, laplacian, w, Dxx, Dyy, sx, sy, ex, ey);

row = (sy * w + sx) | 0;
for (y = sy; y < ey; ++y, row += w) {
Expand All @@ -135,7 +151,7 @@ export class yape06 extends jsfeatNext {
lv > laplacian[rowx - w + 1] &&
lv > laplacian[rowx + w + 1])
) {
min_eigen_value = hessian_min_eigen_value(srd_d, rowx, lv, Dxx, Dyy, Dxy, Dyx);
min_eigen_value = hessianMinEigenValue(srd_d, rowx, lv, Dxx, Dyy, Dxy, Dyx);
if (min_eigen_value > eigen_thresh) {
pt = points[number_of_points];
((pt.x = x), (pt.y = y), (pt.score = min_eigen_value));
Expand Down