Skip to content

Allow GC progress while other threads run long native regular expression matches #330

Closed

Description

@dimensionscape

A long EReg match keeps its thread inside the managed execution zone for the entire PCRE2 call. A collection requested by another thread cannot start until matching returns. This adds the remaining match time to a process-wide GC pause, even when the collector itself has very little work.

Reproduced on Windows x64, Haxe 4.3.7, MSVC 19.44.35213, default GC, libhxcpp 920fa85. PCRE JIT was not enabled.

The subject contains only ASCII a characters and the expression is ^[a-z]+$. This is a successful linear match. A second Haxe thread waits 5 ms, records a timestamp, calls cpp.vm.Gc.run(true), and records completion. The control uses the same retained subject and expression but calls Sys.sleep(0.3) instead of matching. Full collection precedes each trial, and expression creation is outside the matching timer. Seven samples per size, alternating match/control order:

Subject Median match duration Median collection request duration during match Median collection request duration during sleep
32 MiB 19.0928 ms 13.7827 ms 0.0857 ms
64 MiB 39.3879 ms 33.6590 ms 0.0843 ms
128 MiB 73.3406 ms 68.0030 ms 0.0853 ms

The collection request starts roughly 5 ms after matching begins and completes just after matching ends. The last two columns include the collection itself, not just the wait for the matching thread. These are large-subject measurements intended to isolate scheduling behavior, not typical request sizes or an application throughput estimate.

pcredata::run in src/hx/libs/regexp/RegExp.cpp calls both pcre2_match_8 and pcre2_match_16 directly, including the JIT fallback, without entering a GC-free zone or providing safepoints during matching. While the collector waits for that thread, other allocating workers can also stop at the collection handshake.

Investigate allowing collections during native matching, entirely within libhxcpp. Simply wrapping the current calls in a GC-free zone is insufficient: PCRE holds a pointer into the subject, which must remain valid under moving GC, and the owning expression, match data and cached UTF-16 subject must stay alive. Account for copying or pinning costs, keep match positions and captures unchanged, and include the interpreter and optional JIT paths. The performance benefit is reduced interference between independent workers, not faster PCRE execution.

A local conventional regression, test/regression/RegexpConcurrentCollection, checks ASCII and UTF-16 matches, captures and positions while another thread repeatedly forces collections. It passes with default GC and HXCPP_GC_MOVING. It provides correctness coverage for a future change; it deliberately has no timing assertion and does not currently fail. The timing probe above is separate. No runtime implementation changes were made.

Keep the timing workload in performance coverage so a future implementation can compare collection latency and match throughput together. No compiler or stdlib change is needed.

Activity

  1. added a commit that references this issue on Oct 27, 2026
    2984d0e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions