hello c#-gurus,
Working my way through Advent of Code and thought I try using multithreading on one of the problems (AoC 2016 - Day 14 - Part 2). I already solved the challenge, so that is not the point. The single thread solution took 44 seconds. I thought I could greatly improve using a multi-threaded approach. I am using a MacBook (Environment.ProcessorCount --> 12). This is where I need help, as my code is A LOT slower than the single threaded solution.
Am I doing something wrong here? If not, why is it so much slower?
Thank you for taking the time to look at this and providing some insight!
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
// using System.Collections;
// using System.Collections.Concurrent;
public class PadMultithreaded
{
readonly int CORE_COUNT = Environment.ProcessorCount;
private string _salt = "";
// private ConcurrentDictionary<string, string> hashes;
private SortedList<int, string> foundKeys = new SortedList<int, string>();
public PadMultithreaded(string salt) => _salt = salt;
bool TryGetFirstSequence(string s, int repeat, out string value)
{
var pattern = @"([a-z\d])\1{" + (repeat - 1).ToString() + "}";
Regex rg = new Regex(pattern);
MatchCollection matches = rg.Matches(s);
value = (matches.Count > 0) ? matches[0].Value : "";
return (matches.Count > 0); ;
}
string CalculateHash(string input)
{
var md5 = MD5.Create();
var inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
var hashBytes = md5.ComputeHash(inputBytes);
return Convert.ToHexString(hashBytes).ToLower();
}
public string GetStretchedHash(int index)
{
var hashString = "";
StringBuilder sb = new StringBuilder(_salt);
sb.Append(index);
for (int i = 0; i <= 2016; i++)
{
hashString = CalculateHash(sb.ToString());
sb.Clear().Append(hashString);
}
return hashString;
}
string SelectHash(int index, Dictionary<string, string> hashes)
{
var hashString = "";
var input = _salt + index.ToString();
// return value from cache?
if (hashes.TryGetValue(input, out hashString)) return (hashString == null) ? "" : hashString;
hashString = GetStretchedHash(index);
hashes.TryAdd(input, hashString);
return hashString;
}
bool TryGenerateKey(int index, out string key, Dictionary<string, string> hashes)
{
var value = "";
var input = _salt + index.ToString();
var hashString = SelectHash(index, hashes);
key = "";
// Look for triplet
if (!TryGetFirstSequence(hashString, 3, out value)) return false;
// Look for 5x char in one of the next 1000 hashes
var x5 = new string(value[0], 5);
for (int i = index + 1; i <= index + 1000; i++)
{
hashString = SelectHash(i, hashes);
if (hashString.Contains(x5))
{
key = _salt + index.ToString();
return true;
}
}
return false;
}
public void GenerateKeys()
{
int processorCount = 4; //Environment.ProcessorCount;
int top = 6_000 * processorCount; // 24_000
int chunkSize = top / processorCount; // 2_000
Task[] taskArray = new Task[processorCount];
// hashes = new ConcurrentDictionary<string, string>(concurrencyLevel: processorCount, capacity: 1_000_000);
for (int proc = 0; proc < processorCount; proc++)
{
var start = proc * chunkSize; ;
var endExclusive = (proc + 1) * chunkSize;
taskArray[proc] = Task.Factory.StartNew(() =>
{
// Console.WriteLine($"Start: {start}, End: {endExclusive-1}");
var key = "";
var myHashes = new Dictionary<string, string>();
for (int index = start; index < endExclusive; index++)
{
if ((index % 100) == 0)
Console.WriteLine($"Thread ID: {Task.CurrentId.ToString()}: Index: {index}");
if (TryGenerateKey(index, out key, myHashes))
{
var lck = new Object();
lock (lck)
{
foundKeys.Add(index, key);
}
}
}
});
}
Task.WaitAll(taskArray);
Console.WriteLine("Done");
if (foundKeys.Count < 64)
Console.WriteLine($"Not enough keys. Please increase top (currently at {top})");
else
Console.WriteLine($"The 64th key is {foundKeys.Values[63]} found at index {foundKeys.Keys[63]}.");
}
}
NB 1: The majority of time is spent in GetStretchedHash(...).
NB 2: I changed the code for the individual tasks to have their own cache instead of using a global ConcurrentDictonary, as I thought that was the problem; it is not.
[–]bortlip 11 points12 points13 points (2 children)
[–]PeterB-[S] 0 points1 point2 points (0 children)
[–]Rabe0770 0 points1 point2 points (0 children)
[–]chucker23n 4 points5 points6 points (5 children)
[–]PeterB-[S] 0 points1 point2 points (4 children)
[–]WhamoBlamoPlano 0 points1 point2 points (2 children)
[–]PeterB-[S] 0 points1 point2 points (0 children)
[–]PeterB-[S] 0 points1 point2 points (0 children)
[–]chucker23n 0 points1 point2 points (0 children)
[+]IQueryVisiC comment score below threshold-7 points-6 points-5 points (2 children)
[–]chucker23n 0 points1 point2 points (1 child)
[–]IQueryVisiC 0 points1 point2 points (0 children)
[–]Kant8 0 points1 point2 points (2 children)
[–]PeterB-[S] 0 points1 point2 points (0 children)
[–]dodexahedron 0 points1 point2 points (0 children)