Skip to content

Commit 4e5d306

Browse files
committed
Refactor Base32 encoding and decoding for improved performance
1 parent da9cef2 commit 4e5d306

1 file changed

Lines changed: 32 additions & 19 deletions

File tree

src/Sidio.Text.Base32/Base32.cs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
public static partial class Base32
77
{
88
private const char UnitSeparator = (char)0x1F;
9-
private const char MaxByte = (char)0xFF;
109

1110
private static readonly char[] Base32Table =
1211
[
@@ -40,23 +39,37 @@ public static string Encode(byte[] inArray)
4039

4140
private static byte[] Decode(ReadOnlySpan<char> inputSpan, int[] decodeMap)
4241
{
43-
// remove padding '=' characters from the end of the input
44-
var trimmedInput = inputSpan.TrimEnd('=');
42+
// manually find the end position without padding '=' characters
43+
var inputLength = inputSpan.Length;
44+
while (inputLength > 0 && inputSpan[inputLength - 1] == '=')
45+
{
46+
inputLength--;
47+
}
4548

4649
// calculate the expected output byte array length
4750
// each 8 characters of base32 results in 5 bytes
48-
var outputLength = (trimmedInput.Length * 5) / 8;
51+
var outputLength = (inputLength * 5) / 8;
4952

50-
// stack allocation for small arrays
51-
Span<byte> outputSpan = stackalloc byte[outputLength];
53+
// use stack allocation for small arrays (<=256 bytes), otherwise heap
54+
Span<byte> outputSpan = outputLength <= 256
55+
? stackalloc byte[outputLength]
56+
: new byte[outputLength];
5257

5358
var bitBuffer = 0;
5459
var bitCount = 0;
5560
var outputIndex = 0;
5661

5762
// process each character in the Base32 input
58-
foreach (var c in trimmedInput)
63+
for (var i = 0; i < inputLength; i++)
5964
{
65+
var c = inputSpan[i];
66+
67+
// bounds check before array access
68+
if (c > 255)
69+
{
70+
throw new ArgumentException("Invalid character in Base32 string.");
71+
}
72+
6073
// get the index value of the character from the Base32 alphabet
6174
var index = decodeMap[c];
6275
if (index == -1)
@@ -69,13 +82,11 @@ private static byte[] Decode(ReadOnlySpan<char> inputSpan, int[] decodeMap)
6982
bitCount += 5;
7083

7184
// extract 8 bits (1 byte) whenever we have enough bits (>= 8)
72-
if (bitCount < 8)
85+
if (bitCount >= 8)
7386
{
74-
continue;
87+
outputSpan[outputIndex++] = (byte)(bitBuffer >> (bitCount - 8));
88+
bitCount -= 8;
7589
}
76-
77-
outputSpan[outputIndex++] = (byte)((bitBuffer >> (bitCount - 8)) & MaxByte);
78-
bitCount -= 8;
7990
}
8091

8192
// convert the span to a byte array and return it
@@ -88,21 +99,23 @@ private static string Encode(ReadOnlySpan<byte> inArray, char[] base32Table)
8899
var outputLength = ((inArray.Length * 8) + 4) / 5;
89100

90101
// output length should be padded to a multiple of 8
91-
var paddingLength = (outputLength + 7) / 8 * 8;
102+
var paddingLength = (outputLength + 7) & ~7; // faster than / 8 * 8
92103

93-
// use stackalloc for small memory allocation
94-
Span<char> outputSpan = stackalloc char[paddingLength];
104+
// use stack allocation for small arrays (<=256 bytes), otherwise heap
105+
Span<char> outputSpan = paddingLength <= 256
106+
? stackalloc char[paddingLength]
107+
: new char[paddingLength];
95108

96109
// declare bit buffer and bit count
97110
var bitBuffer = 0;
98111
var bitCount = 0;
99112
var outputIndex = 0;
100113

101114
// process each byte in inArray
102-
foreach (var b in inArray)
115+
for (var i = 0; i < inArray.Length; i++)
103116
{
104117
// shift the buffer left by 8 bits and add the byte value to the buffer
105-
bitBuffer = (bitBuffer << 8) | b;
118+
bitBuffer = (bitBuffer << 8) | inArray[i];
106119
bitCount += 8;
107120

108121
// while we have 5 or more bits in the buffer, process a base32 character
@@ -123,13 +136,13 @@ private static string Encode(ReadOnlySpan<byte> inArray, char[] base32Table)
123136
}
124137

125138
// add padding '=' to make the result length a multiple of 8
126-
while (outputIndex % 8 != 0)
139+
while (outputIndex < paddingLength)
127140
{
128141
outputSpan[outputIndex++] = '=';
129142
}
130143

131144
// return the result as a string
132-
return new string(outputSpan[..outputIndex]);
145+
return new string(outputSpan[..paddingLength]);
133146
}
134147

135148
private static int[] CreateDecodeMap(char[] table)

0 commit comments

Comments
 (0)