Skip to content

Dev#18

Merged
Nejcc merged 8 commits into
masterfrom
dev
May 28, 2026
Merged

Dev#18
Nejcc merged 8 commits into
masterfrom
dev

Conversation

@Nejcc

@Nejcc Nejcc commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added of() factory methods to numeric types for fast, cached instance retrieval.
    • Added first(), last(), and isEmpty() collection accessors.
    • Added fromTrusted() factory methods for arrays to skip validation when creating pre-validated collections.
  • Performance

    • Introduced optional validation-skipping path via trusted parameters for improved performance when safety is guaranteed.
  • Chores

    • Updated benchmark suite with new performance measurement tests for array and numeric type operations.

Review Change Stack

Nejcc added 8 commits October 29, 2024 07:12
Two bugs blocked composer benchmark out of the box:

- benchmarks/ wasn't in autoload-dev psr-4, so run_benchmarks.php
  failed with Class not found.
- IntegerBenchmark::benchmarkInt8Arithmetic multiplied 50 * 30 = 1500,
  overflowing Int8 (-128..127) and throwing OverflowException. Fixed
  to 5 * 3.

Also added two new benchmark cases used by the optimisation work that
follows: IntArray::fromTrusted and Int8::of.
Two changes to typed array construction:

1. Replace array_all/array_find closure validation with a single
   foreach loop in IntArray and the validateFloats/validateStrings/
   validateBytes helpers on ArrayAbstraction. The closure invocation
   per element was the dominant cost on construction of large arrays.
   IntArray(1000): 903us -> ~300us (3x faster).

2. Add an optional bool \$trusted = false to the constructor and a
   public static fromTrusted(array): self factory on IntArray,
   FloatArray, StringArray and ByteSlice. Callers who already know
   their input is well-typed (typed query results, output of
   array_map('intval', ...), etc.) can skip the per-element check
   entirely. IntArray::fromTrusted(1000): ~1.6us. Down from 6700x
   slower than a native array assignment to roughly 12x.
The arithmetic hot path used to go through performOperation(callable,
string) which invoked a [\$this, 'addValues'] array-callable per op.
Four call frames + a re-validation in setValue() for each add().

Three changes:

1. Inline add/subtract/multiply/divide/mod directly in
   NativeArithmeticOperationsTrait and BigArithmeticOperationsTrait.
   One method, native math, two bounds comparisons, one allocation.

2. Add optional bool \$trusted = false to AbstractNativeInteger and
   AbstractBigInteger constructors. When true, skips the MIN/MAX
   check in setValue(). Used internally by arithmetic ops which
   already pre-validate the result before constructing. Removes a
   duplicate bccomp pair per Int64 op.

3. Remove the now-unreachable performOperation() and the five
   addValues/subtractValues/multiplyValues/divideValues/modValues
   helpers from both abstracts, plus the abstract performOperation
   declaration from both traits. ~190 lines of dead code.

Public API unchanged. Same exceptions thrown
(Overflow/Underflow/DivisionByZero/UnexpectedValueException).

Numbers (100k iters):
  Int8 arithmetic:  11.67us -> 5.32us  (2.2x)
  Int64 arithmetic: 14.56us -> 6.82us  (2.1x)
The Int8 domain is exactly 256 values and instances are immutable, so
they can be safely shared. Add a lazy static cache keyed by value.

  Int8::of(42)   ~0.50us
  new Int8(42)   ~1.20us

2.4x faster on cache hits. Memory bounded at ~8KB worst case (256
instances). Useful in tight loops; for one-off construction the
allocator is already cheap enough.

The cache is process-lifetime, which suits long-running PHP workers
(Octane, Swoole, ReactPHP) and is effectively per-request under CGI.

Not applied to wider integer types (Int16+) on purpose: 65k+ instances
is too much for an unbounded cache. UInt8 and Byte could get the same
treatment in a follow-up.
Arithmetic ops (add/subtract/multiply/divide) used to return
new static(\$result) which re-ran setValue() — duplicate infinity +
MIN/MAX checks on every op.

Inline those checks once in the arithmetic methods, then construct
via new static(\$result, true) to skip the second pass. Same pattern
as AbstractNativeInteger/AbstractBigInteger.

Float32 arithmetic now ~4.0us/op. Same exception thrown
(OutOfRangeException) for the same conditions (INF, MIN/MAX overflow).
Mirrors Int8::of(). Both domains are exactly 256 values, instances
are immutable, so sharing is safe and bounded (~8KB worst case
per class).

  new UInt8(42)   ~0.94us
  UInt8::of(42)   ~0.51us   (1.8x)

  new Byte(42)    ~0.49us
  Byte::of(42)    ~0.46us   (marginal — Byte's constructor is
                              already a single comparison)
@Nejcc Nejcc merged commit 3092f37 into master May 28, 2026
0 of 25 checks passed
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e446557-80d6-4326-b1e1-e9288438b9bf

📥 Commits

Reviewing files that changed from the base of the PR and between 8fedd90 and 35ac354.

📒 Files selected for processing (16)
  • benchmarks/ArrayBenchmark.php
  • benchmarks/IntegerBenchmark.php
  • composer.json
  • src/Abstract/AbstractBigInteger.php
  • src/Abstract/AbstractFloat.php
  • src/Abstract/AbstractNativeInteger.php
  • src/Abstract/ArrayAbstraction.php
  • src/Composite/Arrays/ByteSlice.php
  • src/Composite/Arrays/FloatArray.php
  • src/Composite/Arrays/IntArray.php
  • src/Composite/Arrays/StringArray.php
  • src/Scalar/Byte.php
  • src/Scalar/Integers/Signed/Int8.php
  • src/Scalar/Integers/Unsigned/UInt8.php
  • src/Traits/BigArithmeticOperationsTrait.php
  • src/Traits/NativeArithmeticOperationsTrait.php

📝 Walkthrough

Walkthrough

This PR refactors the type system to support trusted constructors that skip validation, implements flyweight caching for scalar types via of() factories, refactors arithmetic operations from delegation patterns into inline trait implementations, and adds array accessor methods while modernizing validation logic.

Changes

Type System Optimization with Trusted Construction and Arithmetic Refactoring

Layer / File(s) Summary
Trusted constructor pattern foundation
src/Abstract/AbstractBigInteger.php, src/Abstract/AbstractFloat.php, src/Abstract/AbstractNativeInteger.php
Abstract base classes gain optional $trusted constructor flags to skip bounds/validation checks; protected arithmetic helper methods are removed to enable consolidation into traits.
Collection trusted factories and constructors
src/Composite/Arrays/ByteSlice.php, src/Composite/Arrays/FloatArray.php, src/Composite/Arrays/IntArray.php, src/Composite/Arrays/StringArray.php
Collection types add $trusted constructor parameters and fromTrusted() static factories to bypass element validation, enabling fast-path construction when callers guarantee data integrity.
Scalar caching factories with flyweight pattern
src/Scalar/Byte.php, src/Scalar/Integers/Signed/Int8.php, src/Scalar/Integers/Unsigned/UInt8.php
Byte, Int8, and UInt8 introduce private static instance caches and of(int $value) factory methods that lazily populate and return cached instances for repeated values.
Array type accessors and validation refactoring
src/Abstract/ArrayAbstraction.php
Adds public first(), last(), and isEmpty() collection accessor methods; refactors validateFloats, validateStrings, and validateBytes from functional array_all/array_find patterns to explicit foreach loops with inline type/range checks.
Native arithmetic operations refactoring
src/Traits/NativeArithmeticOperationsTrait.php
Replaces generic performOperation() delegation with inline implementations of add, subtract, multiply, divide, and mod; each validates bounds and returns new static($result, true) via trusted constructor; all methods gain #[NoDiscard] attributes.
Big integer arithmetic operations refactoring
src/Abstract/AbstractFloat.php, src/Traits/BigArithmeticOperationsTrait.php
Replaces performOperation() delegation with inline bc* operation implementations in arithmetic methods; validates results against bounds, throws overflow/underflow/division-by-zero exceptions, and returns new immutable instances via trusted constructor path.
Benchmark additions and build configuration
composer.json, benchmarks/ArrayBenchmark.php, benchmarks/IntegerBenchmark.php
Updates Composer autoload-dev to map Nejcc\PhpDatatypes\Benchmarks namespace; adds benchmarkIntArrayFromTrusted() and benchmarkInt8Of() methods to measure performance of trusted factory and cached scalar factory paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Nejcc/php-datatypes#15: Introduced the initial benchmark classes (ArrayBenchmark, IntegerBenchmark) that this PR extends with new trusted construction and caching benchmarks.

Poem

🐰 Factories cached, validation skipped,
Arithmetic inlined, tight and whipped,
Trusted paths race, fast and keen,
Immutable types, clean and serene!
Mutable helpers fade to night,
Delegates gone—pure speed in flight!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.1.55)

Composer install failed: the lock file is not up to date with the latest changes in composer.json. Run composer update and commit the updated composer.lock.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant