Skip to content

Optimize AlignmentPatterns - #699

Open
KrisVandermotten wants to merge 2 commits into
Shane32:masterfrom
KrisVandermotten:AlignmentPatterns
Open

Optimize AlignmentPatterns#699
KrisVandermotten wants to merge 2 commits into
Shane32:masterfrom
KrisVandermotten:AlignmentPatterns

Conversation

@KrisVandermotten

@KrisVandermotten KrisVandermotten commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR applies a round of optimizations to the calculation and placement of alignment patterns.

First, several improvements reduce the amount of memory allocated by CreateAlignmentPatternTable:

  • The AlignmentPattern structure has been removed. It was wrapper around a list of Points and a Version property. As the Version property wasn't used, there is no need for the structure.
  • The Point structure has been reduced from two int properties to two byte properties. Given that the result of the method is essentially a table of Points, this reduces the amount of memory needed to store the actual data by a factor 4.
  • The resulting table has been changed from a Dictionary to an array.
  • The entries in the table have been changed from List<Point> to Point[].

All of the above contribute to less memory being needed to store the result and keep it referenced for later usage. In addition, some memory allocations have been reduced in the process to produce that result:

  • On modern .NET, the allocation (and initialization) of alignmentPatternBaseValues is avoided. It's values are now bytes instead of ints.
  • A single List<Point> is used as a buffer to store intermediate results, instead of a list per version.

Some changes have been made to reduce execution time:

  • The calculation for version 0 is avoided, as it's empty anyway.
  • The inner loops are terminated as soon as they can, instead of always looping 7 times.
  • The unnecessary check whether a calculated point was not already present in the points intermediate list has been removed. As a result, the IEquatable<Point> implementation is no longer needed and removed.
  • The outer loop is now a simple loop over the versions. Not only does this improve the clarity of the code, it also avoids an integer division per version.
  • The numbers in alignmentPatternBaseValues are now the coordinates of the upper left corners of the alignment patters, not the center points. This saves two substractions per point.

I ran a simple benchmark to measure the results.

BenchmarkDotNet v0.13.12, Windows 11 (10.0.26200.7462)
11th Gen Intel Core i7-1165G7 2.80GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 8.0.29 (8.0.2926.32403), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  DefaultJob : .NET 8.0.29 (8.0.2926.32403), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI

On master:

Method Mean Error StdDev Gen0 Gen1 Allocated
CreateAlignmentPatternTable 12.63 us 0.225 us 0.211 us 3.3722 0.2441 20.74 KB

This PR:

Method Mean Error StdDev Gen0 Gen1 Allocated
CreateAlignmentPatternTable 1.811 us 0.0242 us 0.0226 us 0.5455 0.0057 3.35 KB

Memory allocations have been reduced by a factor 6. Even though the method is executed only once, it's nice to see that it is now 7 times faster. More optimizations are possible but deemed unnecessary, as they would also reduce the readability of the code.

Secondly, some improvements have been made to the PlaceAlignmentPatterns method.

Obviously, placement already benefits from the fact that AlignmentPatterns.FromVersion now returns a Point[] instead of (a struct around) a List<Point>, and does so by indexing into an array instead of a dictionary.

On top of that:

  • The y and x loops have been swapped to improve data locality, and avoid redundant indexing into the ModuleMatrix array.
  • The inner x loop has been unrolled, primarily to reduce branches and branch mispredictions.
  • A redundant construction of alignmentPatternRect is avoided.

The results are visible in the existing QRCodeGeneratorBenchmark.

On master:

Method Mean Error StdDev Gen0 Allocated
CreateQRCode 126.8 us 2.52 us 3.28 us 0.6104 4.02 KB
CreateQRCodeMultiMode 984.3 us 19.62 us 31.69 us - 7.22 KB
CreateQRCodeLong 2,330.6 us 43.59 us 70.39 us - 10.89 KB
CreateQRCodeLongest 13,594.9 us 268.84 us 298.82 us - 43.84 KB

This PR:

Method Mean Error StdDev Gen0 Allocated
CreateQRCode 121.9 us 2.43 us 4.96 us 0.6104 4.02 KB
CreateQRCodeMultiMode 924.9 us 18.14 us 20.90 us 0.9766 7.22 KB
CreateQRCodeLong 2,141.0 us 41.43 us 46.04 us - 10.89 KB
CreateQRCodeLongest 12,644.3 us 193.28 us 171.34 us - 43.84 KB

Test plan

All modified code is exercised by existing unit tests. Benchmarks show the performance benefit.

Summary by CodeRabbit

  • Improvements
    • Optimized QR code alignment pattern processing and placement.
    • Reduced memory usage when handling alignment coordinates.
    • Improved alignment pattern generation across supported QR code versions.
    • Preserved the existing 5×5 alignment pattern rendering behavior.
    • Added validation to ensure alignment data remains within expected limits.
    • Maintained accurate placement of alignment patterns while improving overall generation efficiency.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ea308837-2c47-474c-96e0-9dbdfd2205aa

📥 Commits

Reviewing files that changed from the base of the PR and between a61ad31 and de2309f.

📒 Files selected for processing (1)
  • QRCoder/QRCodeGenerator/AlignmentPatterns.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • QRCoder/QRCodeGenerator/AlignmentPatterns.cs

📝 Walkthrough

Walkthrough

The PR replaces dictionary-backed alignment data with version-indexed point arrays, changes point coordinates to bytes, and updates alignment pattern placement to consume arrays and render reserved 5×5 regions by row.

Changes

Alignment pattern pipeline

Layer / File(s) Summary
Byte-based point representation
QRCoder/QRCodeGenerator/Point.cs
Point now stores byte-based coordinates and no longer implements custom equality members.
Alignment coordinate table
QRCoder/QRCodeGenerator/AlignmentPatterns.cs
AlignmentPatterns now returns version-indexed Point[] values and builds versions 2–40 from byte coordinate data.
Alignment pattern placement
QRCoder/QRCodeGenerator.cs, QRCoder/QRCodeGenerator/ModulePlacer.cs
QRCodeGenerator passes alignment coordinates directly to ModulePlacer. ModulePlacer reserves non-overlapping areas and renders each pattern by row.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Shane32/QRCoder#618: Both changes include conditional ReadOnlySpan<byte> support in QRCodeGenerator, but they modify different code paths.

Suggested labels: performance

Suggested reviewers: gfoidl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main optimization changes to alignment-pattern calculation and placement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@gfoidl gfoidl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Quoting coderabbitai:

No actionable comments were generated in the recent review. 🎉

😃 one AI hasn't found something reviewing a other AI1...(same model in the background?).

Footnotes

  1. note the "Summary by CodeRabbit" in the TOP got added later on by CodeRabbit.

Comment on lines +38 to +41
localAlignmentPatternTable[-4 + 4] = empty;
localAlignmentPatternTable[-3 + 4] = empty;
localAlignmentPatternTable[-2 + 4] = empty;
localAlignmentPatternTable[-1 + 4] = empty;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this indexing be simplified?

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.

What simplification do you have in mind?

Note that the additions are compiled away by the C# compiler (constant folding).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(constant folding)

Sure, but indices like -4 + 4 are strange to read and non-obvious, where it just could be [0], [1], ...

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.

The + 4 matches the + 4 in lines 23, 44 and 69. Would it be more clear if I declared a const int indexOffset = 4; and used it throughout?

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.

Pushed a commit doing exactly that.

@KrisVandermotten

Copy link
Copy Markdown
Contributor Author

😃 one AI hasn't found something reviewing a other AI...(same model in the background?).

Not sure what "other AI" you're referring to? No AI was used in making this PR, nor its description.

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.

2 participants