diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 8dcf7928..4ae2b9d2 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -4,6 +4,7 @@ #endif using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; @@ -120,8 +121,14 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid. if (minVersion > version) { - var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; - throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); + // Use a throw-helper to avoid allocating a closure + Throw(eccLevel, encoding, version); + + static void Throw(ECCLevel eccLevel, EncodingMode encoding, int version) + { + var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; + throw new Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); + } } } @@ -217,8 +224,9 @@ private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, i // Place interleaved data on module matrix var qrData = PlaceModules(); - return qrData; + CodewordBlock.ReturnList(codeWordWithECC); + return qrData; // fills the bit array with a repeating pattern to reach the required length void PadData() @@ -254,7 +262,7 @@ List CalculateECCBlocks() using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock)) { //Calculate error correction words - codewordBlocks = new List(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2); + codewordBlocks = CodewordBlock.GetList(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2); AddCodeWordBlocks(1, eccInfo.BlocksInGroup1, eccInfo.CodewordsInGroup1, 0, bitArray.Length, generatorPolynom); int offset = eccInfo.BlocksInGroup1 * eccInfo.CodewordsInGroup1 * 8; AddCodeWordBlocks(2, eccInfo.BlocksInGroup2, eccInfo.CodewordsInGroup2, offset, bitArray.Length - offset, generatorPolynom); @@ -286,7 +294,7 @@ int CalculateInterleavedLength() for (var i = 0; i < eccInfo.ECCPerBlock; i++) { foreach (var codeBlock in codeWordWithECC) - if (codeBlock.ECCWords.Length > i) + if (codeBlock.ECCWords.Count > i) length += 8; } length += CapacityTables.GetRemainderBits(version); @@ -309,8 +317,8 @@ BitArray InterleaveData() for (var i = 0; i < eccInfo.ECCPerBlock; i++) { foreach (var codeBlock in codeWordWithECC) - if (codeBlock.ECCWords.Length > i) - pos = DecToBin(codeBlock.ECCWords[i], 8, data, pos); + if (codeBlock.ECCWords.Count > i) + pos = DecToBin(codeBlock.ECCWords.Array![i], 8, data, pos); } return data; @@ -484,7 +492,7 @@ private static void GetVersionString(BitArray vStr, int version) /// This method applies polynomial division, using the message polynomial and a generator polynomial, /// to compute the remainder which forms the ECC codewords. /// - private static byte[] CalculateECCWords(BitArray bitArray, int offset, int count, ECCInfo eccInfo, Polynom generatorPolynomBase) + private static ArraySegment CalculateECCWords(BitArray bitArray, int offset, int count, ECCInfo eccInfo, Polynom generatorPolynomBase) { var eccWords = eccInfo.ECCPerBlock; // Calculate the message polynomial from the bit array data. @@ -532,9 +540,16 @@ private static byte[] CalculateECCWords(BitArray bitArray, int offset, int count generatorPolynom.Dispose(); // Convert the resulting polynomial into a byte array representing the ECC codewords. - var ret = new byte[leadTermSource.Count]; +#if NETCOREAPP + var array = ArrayPool.Shared.Rent(leadTermSource.Count); + var ret = new ArraySegment(array, 0, leadTermSource.Count); +#else + var ret = new ArraySegment(new byte[leadTermSource.Count]); + var array = ret.Array!; +#endif + for (var i = 0; i < leadTermSource.Count; i++) - ret[i] = (byte)leadTermSource[i].Coefficient; + array[i] = (byte)leadTermSource[i].Coefficient; // Free memory used by the message polynomial. leadTermSource.Dispose(); @@ -1017,8 +1032,15 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno } // Identify and merge terms with the same exponent. +#if NETCOREAPP + var toGlue = GetNotUniqueExponents(resultPolynom, resultPolynom.Count <= 128 ? stackalloc int[128].Slice(0, resultPolynom.Count) : new int[resultPolynom.Count]); + var gluedPolynoms = toGlue.Length <= 128 + ? stackalloc PolynomItem[128].Slice(0, toGlue.Length) + : new PolynomItem[toGlue.Length]; +#else var toGlue = GetNotUniqueExponents(resultPolynom); var gluedPolynoms = new PolynomItem[toGlue.Length]; +#endif var gluedPolynomsIndex = 0; foreach (var exponent in toGlue) { @@ -1036,7 +1058,11 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno // Remove duplicated exponents and add the corrected ones back. for (int i = resultPolynom.Count - 1; i >= 0; i--) +#if NETCOREAPP if (toGlue.Contains(resultPolynom[i].Exponent)) +#else + if (Array.IndexOf(toGlue, resultPolynom[i].Exponent) >= 0) +#endif resultPolynom.RemoveAt(i); foreach (var polynom in gluedPolynoms) resultPolynom.Add(polynom); @@ -1046,20 +1072,66 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno return resultPolynom; // Auxiliary function to identify exponents that appear more than once in the polynomial. - int[] GetNotUniqueExponents(Polynom list) +#if NETCOREAPP + static ReadOnlySpan GetNotUniqueExponents(Polynom list, Span buffer) { - var dic = new Dictionary(list.Count); + // It works as follows: + // 1. a scratch buffer of the same size as the list is passed in + // 2. exponents are written / copied to that scratch buffer + // 3. scratch buffer is sorted, thus the exponents are in order + // 4. for each item in the scratch buffer (= ordered exponents) it's compared w/ the previous one + // * if equal, then increment a counter + // * else check if the counter is $>0$ and if so write the exponent to the result + // + // For writing the result the same scratch buffer is used, as by definition the index to write the result + // is `<=` the iteration index, so no overlap, etc. can occur. + + Debug.Assert(list.Count == buffer.Length); + + int idx = 0; foreach (var row in list) { -#if NETCOREAPP - if (dic.TryAdd(row.Exponent, false)) - dic[row.Exponent] = true; + buffer[idx++] = row.Exponent; + } + + buffer.Sort(); + + idx = 0; + int expCount = 0; + int last = buffer[0]; + + for (int i = 1; i < buffer.Length; ++i) + { + if (buffer[i] == last) + { + expCount++; + } + else + { + if (expCount > 0) + { + Debug.Assert(idx <= i - 1); + + buffer[idx++] = last; + expCount = 0; + } + } + + last = buffer[i]; + } + + return buffer.Slice(0, idx); + } #else + static int[] GetNotUniqueExponents(Polynom list) + { + var dic = new Dictionary(list.Count); + foreach (var row in list) + { if (!dic.ContainsKey(row.Exponent)) dic.Add(row.Exponent, false); else dic[row.Exponent] = true; -#endif } // Collect all exponents that appeared more than once. @@ -1080,6 +1152,7 @@ int[] GetNotUniqueExponents(Polynom list) return result; } +#endif } /// diff --git a/QRCoder/QRCodeGenerator/CapacityTables.cs b/QRCoder/QRCodeGenerator/CapacityTables.cs index 59f0d2c9..32d3acc4 100644 --- a/QRCoder/QRCodeGenerator/CapacityTables.cs +++ b/QRCoder/QRCodeGenerator/CapacityTables.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -36,7 +37,17 @@ private static class CapacityTables /// block group details, and other parameters required for encoding error correction data. /// public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel) - => _capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel); + { + foreach (var item in _capacityECCTable) + { + if (item.Version == version && item.ErrorCorrectionLevel == eccLevel) + { + return item; + } + } + + throw new InvalidOperationException("No item found"); // same exception type as Linq would throw + } /// /// Retrieves the capacity information for a specific QR code version. @@ -92,11 +103,19 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL } // if no version was found, throw an exception - var maxSizeByte = _capacityTable.Where( - x => x.Details.Any( - y => (y.ErrorCorrectionLevel == eccLevel)) - ).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]); - throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); + // In order to get the maxSizeByte we use a throw-helper method to avoid the allocation of a closure + Throw(encMode, eccLevel); + throw null!; // this is needed to make the compiler happy + + static void Throw(EncodingMode encMode, ECCLevel eccLevel) + { + var maxSizeByte = _capacityTable.Where( + x => x.Details.Any( + y => (y.ErrorCorrectionLevel == eccLevel)) + ).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]); + + throw new Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); + } } /// diff --git a/QRCoder/QRCodeGenerator/CodewordBlock.cs b/QRCoder/QRCodeGenerator/CodewordBlock.cs index 6960219e..63c52115 100644 --- a/QRCoder/QRCodeGenerator/CodewordBlock.cs +++ b/QRCoder/QRCodeGenerator/CodewordBlock.cs @@ -1,3 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +#if NETCOREAPP +using System.Buffers; +#endif + namespace QRCoder; public partial class QRCodeGenerator @@ -6,7 +14,7 @@ public partial class QRCodeGenerator /// Represents a block of codewords in a QR code. QR codes are divided into several blocks for error correction purposes. /// Each block contains a series of data codewords followed by error correction codewords. /// - private struct CodewordBlock + private readonly struct CodewordBlock { /// /// Initializes a new instance of the CodewordBlock struct with specified arrays of code words and error correction (ECC) words. @@ -14,7 +22,7 @@ private struct CodewordBlock /// The offset of the data codewords within the main BitArray. Data codewords carry the actual information. /// The length in bits of the data codewords within the main BitArray. /// The array of error correction codewords for this block. These codewords help recover the data if the QR code is damaged. - public CodewordBlock(int codeWordsOffset, int codeWordsLength, byte[] eccWords) + public CodewordBlock(int codeWordsOffset, int codeWordsLength, ArraySegment eccWords) { CodeWordsOffset = codeWordsOffset; CodeWordsLength = codeWordsLength; @@ -34,6 +42,23 @@ public CodewordBlock(int codeWordsOffset, int codeWordsLength, byte[] eccWords) /// /// Gets the error correction codewords associated with this block. /// - public byte[] ECCWords { get; } + public ArraySegment ECCWords { get; } + + private static List? _codewordBlocks; + + public static List GetList(int capacity) + => Interlocked.Exchange(ref _codewordBlocks, null) ?? new List(capacity); + + public static void ReturnList(List list) + { +#if NETCOREAPP + foreach (var item in list) + { + ArrayPool.Shared.Return(item.ECCWords.Array!); + } +#endif + list.Clear(); + Interlocked.CompareExchange(ref _codewordBlocks, list, null); + } } } diff --git a/QRCoder/QRCodeGenerator/GaloisField.cs b/QRCoder/QRCodeGenerator/GaloisField.cs index d1e95f96..23e79457 100644 --- a/QRCoder/QRCodeGenerator/GaloisField.cs +++ b/QRCoder/QRCodeGenerator/GaloisField.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; namespace QRCoder; @@ -43,6 +44,9 @@ public static int GetAlphaExpFromIntVal(int intVal) /// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum. /// public static int ShrinkAlphaExp(int alphaExp) - => (alphaExp % 256) + (alphaExp / 256); + { + Debug.Assert(alphaExp >= 0); + return (int)((uint)alphaExp % 256 + (uint)alphaExp / 256); + } } }