Another 2x speedup by hardcoding flattening for fixed arrays

feature-optimized-md5
Inga 🏳‍🌈 7 years ago
parent b570a06f2b
commit 325ae0b314
  1. 9
      README.md
  2. 181
      WhiteRabbit/Flattener.cs
  3. 37
      WhiteRabbit/StringsProcessor.cs
  4. 1
      WhiteRabbit/WhiteRabbit.csproj

@ -22,13 +22,14 @@ Anagrams generation is not parallelized, as even single-threaded performance for
Multi-threaded performance with RyuJIT (.NET 4.6, 64-bit system) on quad-core Sandy Bridge @2.8GHz is as follows:
* If only phrases of at most 4 words are allowed, then it takes around 4 seconds to find and check all 7433016 anagrams; all hashes are solved in first 0.5 seconds.
* If only phrases of at most 4 words are allowed, then it takes **less than 3 seconds** to find and check all 7433016 anagrams; all hashes are solved in first 0.4 seconds.
* If phrases of 5 words are allowed as well, then it takes around 12 minutes to find and check all 1348876896 anagrams; all hashes are solved in first 18 seconds. Most of time is spent on MD5 computations for correct anagrams, so there is not a lot to optimize further.
* If phrases of 5 words are allowed as well, then it takes around 7.5 minutes to find and check all 1348876896 anagrams; all hashes are solved in first 10 seconds.
Most of time is spent on MD5 computations for correct anagrams, so there is not a lot to optimize further.
* If phrases of 6 words are allowed as well, then "more difficult" hash is solved in 19 seconds, "easiest" in 2 minutes, and "hard" in less than 5 minutes.
* If phrases of 6 words are allowed as well, then "more difficult" hash is solved in 10 seconds, "easiest" in 1 minute, and "hard" in 2.5 minutes.
* If phrases of 7 words are allowed as well, then "more difficult" hash is solved in ~2 minutes.
* If phrases of 7 words are allowed as well, then "more difficult" hash is solved in 58 seconds.
Note that all measurements were done on a Release build; Debug build is significantly slower.

@ -0,0 +1,181 @@
namespace WhiteRabbit
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
/// <summary>
/// Converts e.g. pair of variants [[a, b, c], [d, e]] into all possible pairs: [[a, d], [a, e], [b, d], [b, e], [c, d], [c, e]]
/// </summary>
internal static class Flattener
{
// Slow universal implementation
private static IEnumerable<ImmutableStack<T>> FlattenAny<T>(ImmutableStack<T[]> phrase)
{
if (phrase.IsEmpty)
{
return new[] { ImmutableStack.Create<T>() };
}
T[] wordVariants;
var newStack = phrase.Pop(out wordVariants);
return FlattenAny(newStack).SelectMany(remainder => wordVariants.Select(word => remainder.Push(word)));
}
// Fast hard-coded implementation for 3 words
private static IEnumerable<T[]> Flatten3<T>(T[][] phrase)
{
for (var i0 = 0; i0 < phrase[0].Length; i0++)
{
for (var i1 = 0; i1 < phrase[1].Length; i1++)
{
for (var i2 = 0; i2 < phrase[2].Length; i2++)
{
yield return new T[]
{
phrase[0][i0],
phrase[1][i1],
phrase[2][i2],
};
}
}
}
}
// Fast hard-coded implementation for 4 words
private static IEnumerable<T[]> Flatten4<T>(T[][] phrase)
{
for (var i0 = 0; i0 < phrase[0].Length; i0++)
{
for (var i1 = 0; i1 < phrase[1].Length; i1++)
{
for (var i2 = 0; i2 < phrase[2].Length; i2++)
{
for (var i3 = 0; i3 < phrase[3].Length; i3++)
{
yield return new T[]
{
phrase[0][i0],
phrase[1][i1],
phrase[2][i2],
phrase[3][i3],
};
}
}
}
}
}
// Fast hard-coded implementation for 5 words
private static IEnumerable<T[]> Flatten5<T>(T[][] phrase)
{
for (var i0 = 0; i0 < phrase[0].Length; i0++)
{
for (var i1 = 0; i1 < phrase[1].Length; i1++)
{
for (var i2 = 0; i2 < phrase[2].Length; i2++)
{
for (var i3 = 0; i3 < phrase[3].Length; i3++)
{
for (var i4 = 0; i4 < phrase[4].Length; i4++)
{
yield return new T[]
{
phrase[0][i0],
phrase[1][i1],
phrase[2][i2],
phrase[3][i3],
phrase[4][i4],
};
}
}
}
}
}
}
// Fast hard-coded implementation for 6 words
private static IEnumerable<T[]> Flatten6<T>(T[][] phrase)
{
for (var i0 = 0; i0 < phrase[0].Length; i0++)
{
for (var i1 = 0; i1 < phrase[1].Length; i1++)
{
for (var i2 = 0; i2 < phrase[2].Length; i2++)
{
for (var i3 = 0; i3 < phrase[3].Length; i3++)
{
for (var i4 = 0; i4 < phrase[4].Length; i4++)
{
for (var i5 = 0; i5 < phrase[5].Length; i5++)
{
yield return new T[]
{
phrase[0][i0],
phrase[1][i1],
phrase[2][i2],
phrase[3][i3],
phrase[4][i4],
};
}
}
}
}
}
}
}
// Fast hard-coded implementation for 7 words
private static IEnumerable<T[]> Flatten7<T>(T[][] phrase)
{
for (var i0 = 0; i0 < phrase[0].Length; i0++)
{
for (var i1 = 0; i1 < phrase[1].Length; i1++)
{
for (var i2 = 0; i2 < phrase[2].Length; i2++)
{
for (var i3 = 0; i3 < phrase[3].Length; i3++)
{
for (var i4 = 0; i4 < phrase[4].Length; i4++)
{
for (var i5 = 0; i5 < phrase[5].Length; i5++)
{
for (var i6 = 0; i6 < phrase[6].Length; i6++)
{
yield return new T[]
{
phrase[0][i0],
phrase[1][i1],
phrase[2][i2],
phrase[3][i3],
phrase[4][i4],
};
}
}
}
}
}
}
}
}
public static IEnumerable<T[]> Flatten<T>(T[][] wordVariants)
{
switch (wordVariants.Length)
{
case 3:
return Flatten3(wordVariants);
case 4:
return Flatten4(wordVariants);
case 5:
return Flatten5(wordVariants);
case 6:
return Flatten6(wordVariants);
case 7:
return Flatten7(wordVariants);
default:
return FlattenAny(ImmutableStack.Create(wordVariants)).Select(words => words.ToArray());
}
}
}
}

@ -54,30 +54,11 @@
// converting sequences of vectors to the sequences of words...
return sums
.Select(this.ConvertVectorsToWords)
.SelectMany(FlattenWords)
.SelectMany(Flattener.Flatten)
.Select(this.ConvertWordsToPhrase);
}
// Converts e.g. pair of variants [[a, b, c], [d, e]] into all possible pairs: [[a, d], [a, e], [b, d], [b, e], [c, d], [c, e]]
private static IEnumerable<ImmutableStack<T>> Flatten<T>(ImmutableStack<T[]> phrase)
{
if (phrase.IsEmpty)
{
return new[] { ImmutableStack.Create<T>() };
}
T[] wordVariants;
var newStack = phrase.Pop(out wordVariants);
return Flatten(newStack).SelectMany(remainder => wordVariants.Select(word => remainder.Push(word)));
}
private static IEnumerable<Tuple<int, ImmutableStack<byte[]>>> FlattenWords(Tuple<int, ImmutableStack<byte[][]>> wordVariants)
{
var item1 = wordVariants.Item1;
return Flatten(wordVariants.Item2).Select(words => Tuple.Create(item1, words));
}
private Tuple<int, ImmutableStack<byte[][]>> ConvertVectorsToWords(int[] vectors)
private byte[][][] ConvertVectorsToWords(int[] vectors)
{
var length = vectors.Length;
var words = new byte[length][][];
@ -86,24 +67,22 @@
words[i] = this.WordsDictionary[vectors[i]];
}
return Tuple.Create(length, ImmutableStack.Create(words));
return words;
}
private byte[] ConvertWordsToPhrase(Tuple<int, ImmutableStack<byte[]>> words)
private byte[] ConvertWordsToPhrase(byte[][] words)
{
var wordCount = words.Item1;
var result = new byte[this.NumberOfCharacters + wordCount - 1];
var result = new byte[this.NumberOfCharacters + words.Length - 1];
byte[] currentWord;
var currentStack = words.Item2.Pop(out currentWord);
byte[] currentWord = words[0];
Buffer.BlockCopy(currentWord, 0, result, 0, currentWord.Length);
var position = currentWord.Length;
while (!currentStack.IsEmpty)
for (var i = 1; i < words.Length; i++)
{
result[position] = 32;
position++;
currentStack = currentStack.Pop(out currentWord);
currentWord = words[i];
Buffer.BlockCopy(currentWord, 0, result, position, currentWord.Length);
position += currentWord.Length;
}

@ -60,6 +60,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ByteArrayEqualityComparer.cs" />
<Compile Include="Flattener.cs" />
<Compile Include="PrecomputedPermutationsGenerator.cs" />
<Compile Include="PermutationsGenerator.cs" />
<Compile Include="StringsProcessor.cs" />

Loading…
Cancel
Save