Skip to content

Commit 9f08e3d

Browse files
authored
Merge pull request #43 from Daramant/master
Speed and memory usage optimizations.
2 parents 82504ee + 1e280f1 commit 9f08e3d

1 file changed

Lines changed: 77 additions & 61 deletions

File tree

src/Hashids.net/Hashids.cs

Lines changed: 77 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using System.Text.RegularExpressions;
66
using Microsoft.Extensions.ObjectPool;
7+
using System.Buffers;
78

89
namespace HashidsNet
910
{
@@ -19,7 +20,10 @@ public partial class Hashids : IHashids
1920
private const double SEP_DIV = 3.5;
2021
private const double GUARD_DIV = 12.0;
2122

23+
private const int MaxNumberHashLength = 12; // Length of long.MaxValue;
24+
2225
private char[] _alphabet;
26+
private long _alphabetLength;
2327
private char[] _seps;
2428
private char[] _guards;
2529
private char[] _salt;
@@ -73,6 +77,8 @@ public Hashids(
7377

7478
SetupSeps();
7579
SetupGuards();
80+
81+
_alphabetLength = _alphabet.Length;
7682
}
7783

7884
private void SetupSeps()
@@ -85,9 +91,9 @@ private void SetupSeps()
8591

8692
ConsistentShuffle(_seps, _seps.Length, _salt, _salt.Length);
8793

88-
if (_seps.Length == 0 || ((float) _alphabet.Length / _seps.Length) > SEP_DIV)
94+
if (_seps.Length == 0 || ((float)_alphabet.Length / _seps.Length) > SEP_DIV)
8995
{
90-
var sepsLength = (int) Math.Ceiling((float) _alphabet.Length / SEP_DIV);
96+
var sepsLength = (int)Math.Ceiling((float)_alphabet.Length / SEP_DIV);
9197

9298
if (sepsLength == 1)
9399
{
@@ -111,7 +117,7 @@ private void SetupSeps()
111117

112118
private void SetupGuards()
113119
{
114-
var guardCount = (int) Math.Ceiling(_alphabet.Length / GUARD_DIV);
120+
var guardCount = (int)Math.Ceiling(_alphabet.Length / GUARD_DIV);
115121

116122
if (_alphabet.Length < 3)
117123
{
@@ -131,7 +137,7 @@ private void SetupGuards()
131137
/// </summary>
132138
/// <param name="numbers">List of integers.</param>
133139
/// <returns>Encoded hash string.</returns>
134-
public virtual string Encode(params int[] numbers) => GenerateHashFrom(Array.ConvertAll(numbers, n => (long) n));
140+
public virtual string Encode(params int[] numbers) => GenerateHashFrom(Array.ConvertAll(numbers, n => (long)n));
135141

136142
/// <summary>
137143
/// Encodes the provided numbers into a hash string.
@@ -160,7 +166,7 @@ private void SetupGuards()
160166
/// <param name="hash">Hash string to decode.</param>
161167
/// <returns>Array of integers.</returns>
162168
/// <exception cref="T:System.OverflowException">If the decoded number overflows integer.</exception>
163-
public virtual int[] Decode(string hash) => Array.ConvertAll(GetNumbersFrom(hash), n => (int) n);
169+
public virtual int[] Decode(string hash) => Array.ConvertAll(GetNumbersFrom(hash), n => (int)n);
164170

165171
/// <summary>
166172
/// Decodes the provided hash into numbers.
@@ -202,8 +208,14 @@ public virtual string DecodeHex(string hash)
202208
var numbers = DecodeLong(hash);
203209

204210
foreach (var number in numbers)
205-
foreach (var ch in number.ToString("X").AsSpan().Slice(1))
206-
builder.Append(ch);
211+
{
212+
var s = number.ToString("X");
213+
214+
for (var i = 1; i < s.Length; i++)
215+
{
216+
builder.Append(s[i]);
217+
}
218+
}
207219

208220
var result = builder.ToString();
209221
_sbPool.Return(builder);
@@ -223,13 +235,14 @@ private string GenerateHashFrom(long[] numbers)
223235

224236
var builder = _sbPool.Get();
225237

226-
char[] buffer = null;
238+
char[] shuffleBuffer = null;
227239
var alphabet = _alphabet.CopyPooled();
240+
var hashBuffer = ArrayPool<char>.Shared.Rent(MaxNumberHashLength);
228241
try
229242
{
230-
var lottery = alphabet[numbersHashInt % _alphabet.Length];
243+
var lottery = alphabet[numbersHashInt % _alphabetLength];
231244
builder.Append(lottery);
232-
buffer = CreatePooledBuffer(_alphabet.Length, lottery);
245+
shuffleBuffer = CreatePooledBuffer(_alphabet.Length, lottery);
233246

234247
var startIndex = 1 + _salt.Length;
235248
var length = _alphabet.Length - startIndex;
@@ -240,17 +253,20 @@ private string GenerateHashFrom(long[] numbers)
240253

241254
if (length > 0)
242255
{
243-
Array.Copy(alphabet, 0, buffer, startIndex, length);
256+
Array.Copy(alphabet, 0, shuffleBuffer, startIndex, length);
244257
}
258+
259+
ConsistentShuffle(alphabet, _alphabet.Length, shuffleBuffer, _alphabet.Length);
260+
var hashLength = BuildReversedHash(number, alphabet, hashBuffer);
245261

246-
ConsistentShuffle(alphabet, _alphabet.Length, buffer, _alphabet.Length);
247-
var last = Hash(number, alphabet, _alphabet.Length);
248-
249-
builder.Append(last);
262+
for (var j = hashLength - 1; j > -1; j--)
263+
{
264+
builder.Append(hashBuffer[j]);
265+
}
250266

251267
if (i + 1 < numbers.Length)
252268
{
253-
number %= last[0] + i;
269+
number %= hashBuffer[hashLength - 1] + i;
254270
var sepsIndex = number % _seps.Length;
255271

256272
builder.Append(_seps[sepsIndex]);
@@ -277,8 +293,8 @@ private string GenerateHashFrom(long[] numbers)
277293

278294
while (builder.Length < _minHashLength)
279295
{
280-
Array.Copy(alphabet, buffer, _alphabet.Length);
281-
ConsistentShuffle(alphabet, _alphabet.Length, buffer, _alphabet.Length);
296+
Array.Copy(alphabet, shuffleBuffer, _alphabet.Length);
297+
ConsistentShuffle(alphabet, _alphabet.Length, shuffleBuffer, _alphabet.Length);
282298
builder.Insert(0, alphabet, halfLength, _alphabet.Length - halfLength);
283299
builder.Append(alphabet, 0, halfLength);
284300

@@ -293,36 +309,35 @@ private string GenerateHashFrom(long[] numbers)
293309
finally
294310
{
295311
alphabet.ReturnToPool();
296-
buffer.ReturnToPool();
312+
shuffleBuffer.ReturnToPool();
313+
hashBuffer.ReturnToPool();
297314
}
298315

299316
var result = builder.ToString();
300317
_sbPool.Return(builder);
301318
return result;
302319
}
303320

304-
private char[] Hash(long input, char[] alphabet, int alphabetLength)
321+
private int BuildReversedHash(long input, char[] alphabet, char[] hashBuffer)
305322
{
306-
var hash = new List<char>(4);
307-
323+
var length = 0;
308324
do
309325
{
310-
hash.Add(alphabet[input % alphabetLength]);
311-
input /= alphabetLength;
326+
hashBuffer[length++] = alphabet[input % _alphabetLength];
327+
input /= _alphabetLength;
312328
} while (input > 0);
313329

314-
hash.Reverse();
315-
return hash.ToArray();
330+
return length;
316331
}
317332

318-
private long Unhash(string input, char[] alphabet, int alphabetLength)
333+
private long Unhash(string input, char[] alphabet)
319334
{
320335
long number = 0;
321336

322337
for (var i = 0; i < input.Length; i++)
323338
{
324339
var pos = Array.IndexOf(alphabet, input[i]);
325-
number = number * alphabetLength + pos;
340+
number = number * _alphabetLength + pos;
326341
}
327342

328343
return number;
@@ -343,55 +358,56 @@ private long[] GetNumbersFrom(string hash)
343358
i = 1;
344359
}
345360

346-
var result = new List<long>();
347361
var hashBreakdown = hashArray[i];
348-
if (hashBreakdown[0] != default(char))
349-
{
350-
var lottery = hashBreakdown[0];
351-
hashBreakdown = hashBreakdown.Substring(1);
362+
var lottery = hashBreakdown[0];
352363

353-
hashArray = hashBreakdown.Split(_seps, StringSplitOptions.RemoveEmptyEntries);
364+
if (lottery == default(char))
365+
return Array.Empty<long>();
366+
367+
hashBreakdown = hashBreakdown.Substring(1);
354368

355-
char[] buffer = null;
356-
var alphabet = _alphabet.CopyPooled();
357-
try
358-
{
359-
buffer = CreatePooledBuffer(_alphabet.Length, lottery);
369+
hashArray = hashBreakdown.Split(_seps, StringSplitOptions.RemoveEmptyEntries);
360370

361-
var startIndex = 1 + _salt.Length;
362-
var length = _alphabet.Length - startIndex;
371+
var result = new long[hashArray.Length];
372+
char[] buffer = null;
373+
var alphabet = _alphabet.CopyPooled();
374+
try
375+
{
376+
buffer = CreatePooledBuffer(_alphabet.Length, lottery);
363377

364-
for (var j = 0; j < hashArray.Length; j++)
365-
{
366-
var subHash = hashArray[j];
378+
var startIndex = 1 + _salt.Length;
379+
var length = _alphabet.Length - startIndex;
367380

368-
if (length > 0)
369-
{
370-
Array.Copy(alphabet, 0, buffer, startIndex, length);
371-
}
381+
for (var j = 0; j < hashArray.Length; j++)
382+
{
383+
var subHash = hashArray[j];
372384

373-
ConsistentShuffle(alphabet, _alphabet.Length, buffer, _alphabet.Length);
374-
result.Add(Unhash(subHash, alphabet, _alphabet.Length));
385+
if (length > 0)
386+
{
387+
Array.Copy(alphabet, 0, buffer, startIndex, length);
375388
}
376-
}
377-
finally
378-
{
379-
alphabet.ReturnToPool();
380-
buffer.ReturnToPool();
381-
}
382389

383-
if (EncodeLong(result.ToArray()) != hash)
384-
{
385-
result.Clear();
390+
ConsistentShuffle(alphabet, _alphabet.Length, buffer, _alphabet.Length);
391+
result[j] = Unhash(subHash, alphabet);
386392
}
387393
}
394+
finally
395+
{
396+
alphabet.ReturnToPool();
397+
buffer.ReturnToPool();
398+
}
399+
400+
if (EncodeLong(result) == hash)
401+
{
402+
return result;
403+
}
388404

389-
return result.ToArray();
405+
return Array.Empty<long>();
390406
}
391407

392408
private char[] CreatePooledBuffer(int alphabetLength, char lottery)
393409
{
394-
var buffer = System.Buffers.ArrayPool<char>.Shared.Rent(alphabetLength);
410+
var buffer = ArrayPool<char>.Shared.Rent(alphabetLength);
395411
buffer[0] = lottery;
396412
Array.Copy(_salt, 0, buffer, 1, Math.Min(_salt.Length, alphabetLength - 1));
397413
return buffer;

0 commit comments

Comments
 (0)