pods4k

repository·main·Indexed 43 days ago

https://github.com/daniel-rusu/pods4k

A collection of performance-oriented data structures for Kotlin designed for high efficiency. It features ImmutableArray and 8 specialized primitive implementations (e.g., ImmutableIntArray, ImmutableDoubleArray) to avoid boxing overhead, reduce memory consumption, and minimize garbage collection pauses.

Tokens
12.4K
Snippets
23
Records
57
Agent score
53%

What's inside pods4k

  1. Overview of Immutable Arrays - Core

    main

    The ImmutableArray core library provides high-performance, immutable data structures for Kotlin. It is designed to offer the convenience of standard library collections while maintaining the efficiency of primitive arrays through specialization.

    Key features include:

    • A generic ImmutableArray<T> class for storing any object type.
    • 8 specialized implementations for primitive types to avoid boxing overhead: ImmutableIntArray, ImmutableLongArray, ImmutableDoubleArray, ImmutableFloatArray, ImmutableBooleanArray, ImmutableByteArray, ImmutableShortArray, and ImmutableCharArray.
    • Standard collection operations such as map, filter, and more.
    • Kotlin standard library extensions to allow seamless integration with existing codebases.
  2. Verify API Compatibility and Annotations

    main

    Ensure that public API surfaces remain stable. Specifically, check that:

    • Public package names, class names, function names, parameters, return types, visibility, and overloads are unchanged unless intended.
    • Annotations like @JvmName, @JvmInline, @PublishedApi, inline, variance, and overload-resolution are preserved or deliberately changed.
    • Public operations continue to return immutable arrays, except for explicit standard-collection transformation APIs.
  3. Understand the Immutability Model

    main

    Immutable Arrays enforce immutability at the type and API level.

    • No mutating operations: All transformation operations return a new Immutable Array and leave the original unchanged.
    • Shallow Immutability: Immutability only applies to the array itself. If the array contains mutable objects, those objects can still be modified.
    • Enforcement: Immutability is enforced by language constraints (type system) rather than JVM security mechanisms.
  4. Module layout and dependencies

    main

    The project is organized into several modules:

    • immutable-arrays/core: The primary module containing published core APIs and implementations (targets JVM 11).
    • immutable-arrays/transformations-to-standard-collections: Provides conversions from pods4k immutable arrays to standard Kotlin/JDK collection types. This module depends on immutable-arrays/core.
    • publishing/aggregator & publishing/bom: Modules used for publication support.
    • buildSrc: Contains the local Gradle plugin and the KotlinPoet-based code generator.
  5. Ensure Generated-Code Consistency

    main

    For files marked with // Auto-generated file. DO NOT EDIT!, verify that:

    • Changes were made through buildSrc generators.
    • generateCode was executed after any generator changes.
    • Output is consistent across all variants: generic plus Boolean, Byte, Char, Short, Int, Long, Float, and Double.
    • Multiplicative specialization output is consistent across source and target base-type combinations.
  6. Validate Primitive Specialization and Boxing

    main

    To maintain performance, ensure primitive types are not accidentally boxed:

    • Primitive arrays must remain backed by primitive JVM arrays.
    • Mapping to primitive lambda return types must produce primitive immutable arrays.
    • Avoid routing primitive values through generic, nullable, Any, List, Iterable, Sequence, or Array<Int> paths unless intentional and measured.
    • Fast paths for empty or unchanged results should return EMPTY or this.
    • Known-size results should allocate exact-size arrays or use bulk-copy patterns.
    • Avoid using Builders when the final size is already known.
    • Ensure no new intermediate standard collections appear in core hot paths.
  7. Why use Immutable Arrays instead of regular arrays or lists

    main

    Immutable Arrays provide a superior alternative to standard Kotlin/Java collection types by combining true immutability, memory efficiency, and performance.

    Key Advantages:

    • Structural Equality: Unlike regular arrays, ImmutableArray implements equals and hashCode based on content, making them safe for use in data class properties and HashSet collections.
    • No Mutation Backdoors: Unlike listOf() (which can be cast to MutableList) or array.asList() (which can be cast to MutableList to mutate the original array), ImmutableArray cannot be mutated via casting. Attempting to cast an ImmutableArray to a primitive array (e.g., IntArray) will result in a ClassCastException rather than allowing mutation.
    • Compile-time Safety: Mutation attempts result in compiler errors, whereas Java unmodifiable lists or Kotlin read-only lists often rely on runtime exceptions.
    • Covariance: ImmutableArray is covariant. For example, ImmutableArray<Manager> is a subtype of ImmutableArray<Person>, making it more flexible than invariant Kotlin arrays.
    • Performance & Memory: They are significantly more efficient than standard lists (often 2-8x faster and using 4x less memory) because they avoid auto-boxing for primitives, use bitmaps for efficient resizing, and reuse instances.
    // ✅ Structural equality works as expected
    immutableArrayOf("Dan", "Bob") == immutableArrayOf("Dan", "Bob")
    
    // ❌ Regular arrays fail equality checks
    arrayOf("Dan", "Bob") == arrayOf("Dan", "Bob")
  8. Avoid boxing risks when working with primitives

    main

    To maintain the performance benefits of primitive specialization, avoid patterns that cause boxing. Boxing occurs when primitives are passed through generic functions, Any, nullable types, or standard collection interfaces.

    Common Boxing Triggers

    • Generic Interfaces: Passing primitives through Iterable<Int>, List<Int>, Array<Int>, or generic lambdas.
    • Conversions: Using asList, asIterable, or asSequence can change allocation and boxing behavior.
    • Typed Arrays: Calling toTypedArray() on primitive arrays causes boxing.
    • Value Classes: Treating value classes as Any or generic values can cause them to box.

    Best Practices for Primitive Arrays

    • Ensure mapping operations preserve primitive result binding (e.g., mapping to Int should produce an ImmutableIntArray).
    • Avoid generic helpers on primitive hot paths that force values through Any, nullable wrappers, Iterable<T>, or Array<T>.
  9. Why Immutable Arrays do not implement the List interface

    main

    The pods4k Immutable Arrays do not implement the standard Kotlin List interface for three primary reasons:

    1. Performance: Implementing List for primitive variants (e.g., ImmutableFloatArray) would require auto-boxing elements on every access, which degrades memory and performance.
    2. Immutability: Implementing List would allow standard library operations to overshadow the optimized versions provided by this library. Furthermore, it could break immutability guarantees as some List operations might produce read-only lists that are susceptible to mutation via casting.
    3. API Integrity: The List interface includes methods that return List types. Using these would cause performance issues and lead users into the standard List ecosystem where immutability guarantees are not strictly enforced. Throwing OperationNotSupportedException to maintain the List contract would result in unpredictable behavior.
  10. Understand the core abstractions of pods4k

    main

    The library is built around two primary abstractions:

    1. Immutable Arrays:

      • ImmutableArray<T> is the generic immutable wrapper.
      • Primitive-specialized wrappers (e.g., ImmutableIntArray, ImmutableLongArray, ImmutableDoubleArray, etc.) are provided to avoid boxing overhead.
      • These are implemented as @JvmInline value class types over backing arrays.
    2. Selection:

      • A manual bitmap-based helper used for filter and partition-style operations.

    Builders: To create immutable arrays, use append-only builders. Note that builders are not thread-safe.

  11. Important invariants and performance considerations

    main

    When working with pods4k, keep the following invariants in mind:

    • Immutability: Contents cannot be reassigned through the public API.
    • Memory Efficiency:
      • Empty results typically reuse a shared EMPTY instance.
      • Operations that result in no change to the range often return this (the original instance).
      • Primitive operations use primitive backing arrays and primitive iterator/value paths to avoid boxing.
    • Equality: Use referencesSameArrayAs to handle concerns regarding value-class boxing and reference equality.
    • Transformations: Most operations return immutable arrays. Only functions explicitly labeled as transformations to standard collections will return Kotlin/JDK collections.
  12. Understand the Immutable Array Core API structure

    main

    The core API consists of generic and primitive immutable arrays.

    Key Components

    • Generic Arrays: ImmutableArray<T>
    • Primitive Arrays: Specialized variants like ImmutableIntArray to avoid boxing.
    • Factories & Utilities: ImmutableArrayFactory.kt, Arrays.kt, Collections.kt, and Sequences.kt.

    These files are generated from ImmutableArrayGenerator, ImmutableArrayFactoryGenerator, and various extension generators located in buildSrc.