Skip to content

v6.10.2

Latest

Choose a tag to compare

@wallneradam wallneradam released this 20 Sep 17:53
· 1 commit to main since this release

Performance

Load-time code transformation

  • Trivial builtin wrappers are inlined into the call site. A new call
    inlining pass rewrites calls of stateless one-expression builtins — math.abs,
    pow, sqrt, sign, floor, ceil, exp, log, log10, the
    trigonometric functions, todegrees/toradians, two- and three-operand
    max/min, and array.get/array.size — as the wrapper's own body,
    written out as a single expression. The expression is derived from the
    wrapper's source AST, so it runs the same operations in the same order and
    stays bit-identical; a wrapper whose body leaves the derivable shape simply
    keeps its call. Arguments are bound once, evaluation order is preserved, and
    call sites where the callee is not provably the library function are left
    alone.
  • typing.cast calls and TYPE_CHECKING blocks are erased at load time. A
    type erasure pass replaces typing.cast(T, x) with x and resolves
    if TYPE_CHECKING: to its else body. It runs in the @pyne pipeline and, as
    the only pass, over PyneCore's own plain modules, where the hot-path casts
    live. A name is trusted only when a module-level typing import is its sole
    binding in the module, so a rebound name or ctypes.cast is untouched.

Number formatting

  • str.tostring and str.format no longer go through Decimal on the
    common paths.
    A fast path handles a native finite double with a digit mask
    and str.tostring with format.mintick; the mask is parsed once per pattern
    string. The previous implementation stays as the semantic reference and the
    fallback for exponential reprs, negative zero, non-float input, the other
    format types and malformed subpatterns, so the output string and the
    exception type are unchanged for every input. Per call: str.tostring
    2.2 us -> 0.7-0.9 us, str.format 2.3 us -> 0.9 us.

Call-site routing and rollback

  • Plain calls for stateless library functions. The function isolation
    whitelist missed most plain library functions, so their call sites paid an
    anchor slot and an identity check, and inside a loop a bind plus a builtin
    rollback on every iteration. All matrix.* functions, the cast_* helpers,
    the line/label/box/table/linefill/polyline/chart.point/ticker
    functions, the drawing array constructors, the request functions that open
    no context, the strategy risk rules, the footprint and volume row accessors,
    runtime.error, color.t and plotbar are now routed as plain calls. The
    array.percentile_nearest_rank entry was listed by its bare name and never
    matched; timestamp, an overload dispatcher that has to stay bound, and a
    color.a entry that names no function were removed.
  • Cheaper drawing and loop-site rollback. The drawing snapshot saves fields
    with a cached per-type attrgetter and restores only the objects whose fields
    changed. Loop-site builtin snapshots carry a state-vector epoch, so while no
    state vector was created since the snapshot the restore reuses the snapshot's
    machine list instead of re-walking the callee subtree.

strategy.exit

  • A resting exit leg is kept in place when the call would rebuild it
    unchanged
    , refreshing only its placement stamps. The shortcut applies to
    legs without trailing or tick fields that sit alone on each of their price
    levels, so a leg sharing a level is still re-queued behind its neighbours.
    The per-leg logic moved out of closures created on every call into
    module-level functions, and the na conversions and sibling reservation sums
    are now plain inline loops. Trade output is unchanged.

Bulk OHLCV writes

  • A bulk append publishes once instead of once per record. OHLCVWriter
    fsynced twice per record — the record bytes, then the header naming them —
    which is the right granularity for a live bar and far too fine for a feed
    being built or fetched in bulk. On Linux, where an fsync is a real device
    barrier, a script with three request.security() contexts over a 23 272-bar
    60m feed spent 58.3 s in the barriers alone. The append protocol is
    unchanged, only its granularity: a new batched() context manager defers
    publication to the end of a block and is taken by aggregate_ohlcv, the
    CSV/JSON converter, the demo generator and each provider save_ohlcv_data()
    call, so a download page is fsynced once while a live bar still publishes on
    its own. The same three-context script: 58.3 s -> 1.95 s, output
    byte-identical.

Fixes

Security child processes start on a pinned method

Security children were started with a bare multiprocessing.Process, so the
start method was the platform default: fork on Linux up to Python 3.13,
spawn on macOS, forkserver on Linux 3.14+. fork is the unsafe one here —
the live runner starts its provider thread and waits for the connection before
warmup, and children start lazily during the run, so every live security child
was forked from a multi-threaded parent and inherited locks no surviving thread
would release. Children now start on forkserver where the platform offers it
and spawn otherwise, and every primitive handed to a child comes from that one
context.

Two consequences of not forking from the runner any more are fixed with it: the
orphan watchdog compared os.getppid() against the spawning runner, which a
forkserver child never has, so a hard-killed runner went unnoticed — it now
waits on the runner's own process sentinel. And because a spawn carries no
environment, per-run PYNE* switches reached the chart but not the children on
a second run in one interpreter; each spawn now carries a snapshot of the
runner's PYNE* environment, replacing the prefix wholesale so a cleared switch
propagates too.

One table per chart position

table.new and table.set_position now evict the table already at the target
position, as TradingView does; the arriving table survives and an evicted handle
stays usable. The registry was unbounded before, so a script calling
table.new on every bar without var added one table per bar and every drawing
snapshot walked all of them, making the run cost grow quadratically with the bar
count. The drawing snapshot also rolls back table cells now: the cells dict and
the cell fields are changed in place, so a discarded calc_on_order_fills or
live re-execution used to leave its table edits behind.

Exits are deferred for a parked parent entry

The order sync engine no longer dispatches or amends an exit while the parent
entry's disposition is unresolved. Exits proceed as before once a fill has been
recorded.

Interrupts from the live runner reach the CLI

A KeyboardInterrupt is now propagated after streaming teardown, so an
interrupted live run is no longer reported as completed.

Durability fixes found along the way

  • A failed header fsync left the OHLCV file naming a record the caller then
    disowned. The header write and its fsync now share the rollback to the last
    published header, and if that rollback fails too the writer refuses further
    appends instead of trimming a sidecar row the on-disk header still counts.
  • Sidecar rows were only flushed, never fsynced, so a crash could commit records
    whose extra values were gone — a row-count mismatch the reader rejects. They
    are now synced before every publication, before the file replacement that
    publishes a schema promotion, and with the directory entry when the sidecar is
    created.
  • math.sqrt rejects negative input with a guard instead of catching
    ValueError. The result is unchanged except for a negative Python int beyond
    the double range, which returns na instead of raising OverflowError.