Skip to content

perf: build scalar-on-left results in a single pass (#258) - #351

Merged
devcrocod merged 1 commit into
developfrom
perf/258-scalar-on-left-single-pass
Sep 15, 2026
Merged

devcrocod merged 1 commit into
developfrom
perf/258-scalar-on-left-single-pass

Conversation

@devcrocod

Copy link
Copy Markdown
Collaborator

Fixes #258.

The 32 scalar-on-left operators copied the operand with deepCopy() and then walked the copy again. They now fill the result buffer in one traversal, reading the source's backing array directly: sequentially when the source is consistent, otherwise through a row-major odometer over offset/strides. Both paths are primitive and boxing-free.

The fix named in the issue would not have helped: deepCopy() already branches on consistent, and in that branch it does exactly what copy() does. The cost was the redundant pass.

Old vs new, same JVM, 1000x1000: contiguous 1.4x (ComplexFloat) - 7.0x (Byte); transposed view 1.7x - 4.6x.

Results are bit-identical. Public API unchanged; the per-dtype kernels are private.

Testing

:multik-core:jvmTest, :multik-kotlin:jvmTest, :multik-core:macosArm64Test, apiCheck korroCheck - all pass, multik-core.api unchanged. JS/WASM/iOS test sources compile (their test tasks are skipped in this repo, #247).

New ScalarArithmeticTest (19 tests, first coverage these operators have): 8 dtypes x 4 ops, contiguous and transposed/offset/stepped views, detachment from the source buffer, result layout, empty arrays, Byte/Short truncation, complex division by zero.

Checklist

  • Existing tests pass
  • New/updated tests for changed behavior
  • New/updated documentation if necessary

The 32 scalar-on-left operators in `_Arithmetic.kt` each copied the whole
operand with `deepCopy()` and then walked the copy a second time to apply
the operation.

The fix named in the issue - mirroring `if (consistent) copy() else
deepCopy()` from `_ArithmeticNDArray.kt` - would not have helped:
`deepCopy()` already branches on `consistent`, and in that branch it does
exactly what `copy()` does (`data.copyOf()`, offset 0, default strides),
so the two are interchangeable there. The cost was the redundant pass, not
the choice of copy.

Each operator now fills the result buffer in one traversal, reading the
source's backing array directly: sequentially when the source is
`consistent`, otherwise through a row-major odometer over offset/strides.
Both paths are primitive and boxing-free, so views no longer pay for the
boxed `Iterator<T>` that `deepCopy()` used.

Measured on 1000x1000 arrays, old vs new in the same JVM:

  contiguous source   1.4x (ComplexFloat) .. 7.0x (Byte)
  transposed view     1.7x (ComplexFloat) .. 4.6x (Int.minus)

Results are bit-identical: the lambdas keep each operand on the side the
previous implementation used, and Byte/Short keep their narrowing
conversions. Public API is unchanged; the new per-dtype kernels are
private. The 32 `as NDArray` unchecked casts are gone with them, so a
third-party `MultiArray` no longer fails with a `ClassCastException`.

Adds `ScalarArithmeticTest`, the first test coverage these operators have:
all 8 dtypes x 4 ops, contiguous sources and transposed/offset/stepped
views, detachment from the source buffer, result layout, empty arrays,
Byte/Short truncation, and complex division by zero.
Copilot AI lite review requested due to automatic review settings September 15, 2026 14:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Qualify the inaccurate deepCopy() rationale before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Optimizes scalar-on-left ndarray arithmetic with single-pass, primitive kernels for contiguous and non-contiguous layouts.

Changes:

  • Adds per-dtype scalar arithmetic kernels.
  • Adds tests for views, layouts, detachment, empty arrays, truncation, and complex division.
File summaries
File Summary
multik-core/src/commonTest/kotlin/org/jetbrains/kotlinx/multik/ndarray/operation/ScalarArithmeticTest.kt Tests arithmetic correctness, views, layouts, detachment, and edge cases.
multik-core/src/commonMain/kotlin/org/jetbrains/kotlinx/multik/ndarray/operations/_Arithmetic.kt Implements optimized single-pass scalar arithmetic kernels.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +283 to +284
// on every backend, with no boxing on either path. Neither the public `map` nor [NDArray.deepCopy]
// can be reused here: both go through `Iterator<T>`, which boxes every element.
@devcrocod
devcrocod merged commit a10f8ef into develop Sep 15, 2026
3 checks passed
@devcrocod
devcrocod deleted the perf/258-scalar-on-left-single-pass branch September 15, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scalar-on-left operators always deepCopy, ignoring consistent optimization

2 participants