Skip to content

Improve NRBF ArrayRecord.GetArray(allowNulls: false) perf - #132293

Draft
adamsitnik with Copilot wants to merge 2 commits into
mainfrom
copilot/improve-nrbf-arrayrecord-getarray-perf
Draft

Improve NRBF ArrayRecord.GetArray(allowNulls: false) perf#132293
adamsitnik with Copilot wants to merge 2 commits into
mainfrom
copilot/improve-nrbf-arrayrecord-getarray-perf

Conversation

Copilot AI commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Performance optimization: when GetArray(allowNulls: false) is called, short-circuit by comparing Length against Records.Count before allocating the result array. If they differ, a Multiple Null Record is present and we can throw immediately — avoiding a potentially massive allocation.

Changes

  • ArraySingleObjectRecord.ToArray — early length vs record count check before array allocation
  • ArraySingleStringRecord.ToArray — same
  • SZArrayOfRecords.ToArray — same
  • ArrayRecord.Populate — early destination.LongLength vs source.Count check before iterating (covers RectangularArrayRecord and JaggedArrayRecord)
  • ArrayOfSerializationRecordsTests — added test verifying no large allocation occurs when GetArray(allowNulls: false) is called on a payload containing ObjectNullMultiple

Example

private SerializationRecord?[] ToArray(bool allowNulls)
{
    // When Length is different than record count, we know the record list
    // contains at least one Multiple Null Record.
    if (!allowNulls && Length != Records.Count)
    {
        ThrowHelper.ThrowArrayContainedNulls();
    }

    SerializationRecord?[] values = new SerializationRecord?[Length];
    // ...
}

… early when null records are detected

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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.

Pull request overview

This PR optimizes NRBF array materialization in System.Formats.Nrbf for the allowNulls: false path by detecting the presence of compressed “multiple null” records up-front and throwing before allocating and/or iterating large arrays.

Changes:

  • Add an early Length vs Records.Count guard in SZ-array record implementations to throw immediately when a NullsRecord with NullCount > 1 must be present and allowNulls is false.
  • Add an analogous early destination.LongLength vs source.Count guard in ArrayRecord.Populate to avoid iterating large multidimensional/jagged arrays when allowNulls is false and compressed nulls are present.
  • Add a regression test ensuring GetArray(allowNulls: false) does not trigger a massive allocation for a payload using ObjectNullMultiple.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs Early throw on allowNulls:false when record count cannot match element count due to multi-null compression.
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs Same early-throw optimization for string SZ arrays.
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SZArrayOfRecords.cs Same early-throw optimization for SZArrayRecord<SerializationRecord> materialization.
src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArrayRecord.cs Early throw in Populate to avoid expensive iteration when allowNulls:false and compressed nulls are present.
src/libraries/System.Formats.Nrbf/tests/ArrayOfSerializationRecordsTests.cs Add allocation-guard regression test for GetArray(allowNulls:false) with ObjectNullMultiple.

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot address my feedback

internal static void Populate(List<SerializationRecord> source, Array destination, int[] lengths, AllowedRecordTypes allowedRecordTypes, bool allowNulls)
{
// When destination length is different than record count, we know the record list contains at least one Multiple Null Record.
if (!allowNulls && destination.LongLength != source.Count)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot The destination array is already provided here which means it is already allocated. This check must be performed BEFORE the array gets allocated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved the check out of Populate into RectangularArrayRecord.Deserialize and JaggedArrayRecord.Deserialize, where it now runs before Array.CreateInstance/new ArrayRecord[...]. The shared logic lives in ArrayRecord.ThrowIfNullsAreNotAllowedButPresent, which compares ArrayInfo.FlattenedLength against the record count. bbc5c73


WriteSerializedStreamHeader(writer);

writer.Write((byte)SerializationRecordType.ArraySingleObject);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot Make this test a [Theory] and pass the array type byte as an argument. Make sure you cover not just string array, but also object array and other reference type array (I don't remember the exact record type name, but you modified three SZArray record types and they all need to be covered with tests without code duplication).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Converted to a [Theory] over SerializationRecordType, covering ArraySingleObject (ArraySingleObjectRecord), ArraySingleString (ArraySingleStringRecord) and BinaryArray with a SystemClass element type (SZArrayOfRecords). Verified all three cases fail without the product change and pass with it. bbc5c73

… types with a Theory

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 13, 2026 22:48
Copilot AI requested a review from adamsitnik August 13, 2026 22:49

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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines +528 to +530
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Text.Encoding.UTF8);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants