Skip to content

Optimize KeyExpression.Create()#444

Open
SergeiPavlov wants to merge 1 commit intomaster-servicetitanfrom
KeyExpression
Open

Optimize KeyExpression.Create()#444
SergeiPavlov wants to merge 1 commit intomaster-servicetitanfrom
KeyExpression

Conversation

@SergeiPavlov
Copy link
Collaborator

Avoid closure & reduce LINQ operations to save allocations

Copy link

@botinko botinko left a comment

Choose a reason for hiding this comment

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

It doesn't look as it would be faster. Simple LINQ like entityType.Key.Columns.Select(CreateField).ToArray() can be optimized by .net itself better, then manually.
entityType.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Field.MappingInfo.Offset) still there, which looks like a primiry case.
The code becomes more complex, and the benefits are insignificant.

Is it known super-hot spot?

@SergeiPavlov
Copy link
Collaborator Author

SergeiPavlov commented Jan 7, 2026

entityType.Key.Columns.Select(CreateField).ToArray() can be optimized by .net itself better,

CreateField is a closure as it uses an external parameter.
The optimizer cannot optimize LINQ with closures

And I see this code in core dumps' stack traces. That means it is a hot-spot

fields = ar;
}
else {
List<FieldExpression> list = new(1);
Copy link

Choose a reason for hiding this comment

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

my previous experiment showed, that it's more efficient to count array size entityType.Columns.Count(c => c.IsPrimaryKey) and allocate array of fixed size.

@botinko
Copy link

botinko commented Jan 8, 2026

Got it. It's still not obvious without bench, that new version is significantly faster, then existing.

@botinko
Copy link

botinko commented Jan 8, 2026

I let AI work on this an got the following:

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.7462/25H2/2025Update/HudsonValley2)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 10.0.101
  [Host]    : .NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v4
  .NET 10.0 : .NET 10.0.1 (10.0.1, 10.0.125.57005), X64 RyuJIT x86-64-v4

Job=.NET 10.0  Runtime=.NET 10.0

| Method                                | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|-------------------------------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| CreateKeyExpression_Locked            |  50.53 ns | 1.033 ns | 1.783 ns |  1.00 |    0.05 | 0.0249 |     416 B |        1.00 |
| CreateKeyExpression2_Locked           |  27.04 ns | 0.579 ns | 0.619 ns |  0.54 |    0.02 | 0.0153 |     256 B |        0.62 |
| CreateKeyExpressionOptimized_Locked   |  25.97 ns | 1.063 ns | 3.100 ns |  0.51 |    0.06 | 0.0153 |     256 B |        0.62 |
| CreateKeyExpression_Unlocked          | 135.62 ns | 2.320 ns | 2.170 ns |  2.69 |    0.10 | 0.0496 |     832 B |        2.00 |
| CreateKeyExpression2_Unlocked         | 156.77 ns | 3.091 ns | 3.036 ns |  3.11 |    0.12 | 0.0520 |     872 B |        2.10 |
| CreateKeyExpressionOptimized_Unlocked |  78.71 ns | 1.599 ns | 4.376 ns |  1.56 |    0.10 | 0.0234 |     392 B |        0.94 |```

public static MockKeyExpression Create(MockTypeInfo entityType, ColNum offset)
{
  var mapping = new Segment<ColNum>(offset, (ColNum)entityType.Key.TupleDescriptor.Count);

  MockFieldExpression CreateField(MockColumnInfo c) => MockFieldExpression.CreateField(c.Field, offset);

  var fields = entityType.IsLocked
    ? entityType.Key.Columns.Select(CreateField).ToArray()
    : entityType.Columns
      .Where(c => c.IsPrimaryKey)
      .OrderBy(c => c.Field.MappingInfo.Offset)
      .Select(CreateField)
      .ToArray();
  return new MockKeyExpression(entityType, fields);
}

// Optimized Create method
public static MockKeyExpression CreateOptimized(MockTypeInfo entityType, ColNum offset)
{
  var entityTypeKey = entityType.Key;
  var tupleCount = entityTypeKey.TupleDescriptor.Count;
  var mapping = new Segment<ColNum>(offset, (ColNum)tupleCount);
  IReadOnlyList<MockFieldExpression> fields;

  if (entityType.IsLocked)
  {
    // Optimized: Direct for loop, no LINQ overhead
    var columns = entityTypeKey.Columns;
    var n = columns.Count;
    var ar = new MockFieldExpression[n];
    for (int i = 0; i < n; ++i)
    {
      var column = columns[i];
      var field = column.Field;
      var mappingInfo = field.MappingInfo;
      var fieldMapping = new Segment<ColNum>((ColNum)(mappingInfo.Offset + offset), mappingInfo.Length);
      ar[i] = new MockFieldExpression(field, fieldMapping);
    }
    fields = ar;
  }
  else
  {
    // Optimized: Single pass to collect, then sort, then create expressions
    var primaryKeyColumns = new List<MockColumnInfo>(entityType.Columns.Count);
    foreach (var c in entityType.Columns)
    {
      if (c.IsPrimaryKey)
      {
        primaryKeyColumns.Add(c);
      }
    }

    // Sort in-place
    primaryKeyColumns.Sort((a, b) => a.Field.MappingInfo.Offset.CompareTo(b.Field.MappingInfo.Offset));

    // Create field expressions
    var n = primaryKeyColumns.Count;
    var ar = new MockFieldExpression[n];
    for (int i = 0; i < n; ++i)
    {
      var column = primaryKeyColumns[i];
      var field = column.Field;
      var mappingInfo = field.MappingInfo;
      var fieldMapping = new Segment<ColNum>((ColNum)(mappingInfo.Offset + offset), mappingInfo.Length);
      ar[i] = new MockFieldExpression(field, fieldMapping);
    }
    fields = ar;
  }

  return new MockKeyExpression(entityType, fields);
}

// Create2 method (optimized version)
public static MockKeyExpression Create2(MockTypeInfo entityType, ColNum offset)
{
  var entityTypeKey = entityType.Key;
  IReadOnlyList<MockFieldExpression> fields;
  if (entityType.IsLocked)
  {
    var columns = entityTypeKey.Columns;
    var n = columns.Count;
    var ar = new MockFieldExpression[n];
    for (int i = 0; i < n; ++i)
    {
      ar[i] = MockFieldExpression.CreateField(columns[i].Field, offset);
    }
    fields = ar;
  }
  else
  {
    List<MockFieldExpression> list = new(1);
    foreach (var c in entityType.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Field.MappingInfo.Offset))
    {
      list.Add(MockFieldExpression.CreateField(c.Field, offset));
    }
    fields = list;
  }
  return new MockKeyExpression(entityType, fields);
}

@botinko
Copy link

botinko commented Jan 8, 2026

So, my gut feeling was right, Unlocked path of code doing worse.
Seriously, LLM could create Bench for fou in couple of minutes. And it could optimize too.

@SergeiPavlov
Copy link
Collaborator Author

SergeiPavlov commented Jan 9, 2026

Seriously, LLM could create Bench for fou in couple of minutes. And it could optimize too.

CreateOptimized() looks over-complicated for such simple functionality.
It is better to keep a balance between optimal and readable code

@snaumenko-st
Copy link

snaumenko-st commented Jan 12, 2026

According to the provided benchmarks - we are struggling to optimize a couple of nanoseconds. Is it really worth the efforts?

@SergeiPavlov
Copy link
Collaborator Author

SergeiPavlov commented Jan 12, 2026

struggling to optimize a couple of nanoseconds.

~20 ns and 150 bytes in most frequent case:

CreateKeyExpression_Locked            |  50.53 ns |  416 B
CreateKeyExpression2_Locked           |  27.04 ns|     256 B 

And I started this because I saw in core dumps' stacktraces that the App is busy at this method.

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.

3 participants