I have a input like
string input = \"14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34\";
// up to 10MB string size
int result = Calc(input); // 11
My original answer was just a bit of fun late at night trying to put this in unsafe
and i failed miserably (actually didn't work at all and was slower). However i decided to give this another shot
The premise was to make everything inline, to remove as much IL
as i could, keep everything in int
or char*
, and make my code pretty. I further optimized this by removing the switch, Ifs
will be ore efficient in this situation, also we can order them in the most logical way. And lastly, if we remove the amount of checks for things we do and assume the input is correct we can remove even more overhead by just assuming things like; if the char
is > '0'
it must be a number. if its a space we can do some calculations, else it must be an operator.
This is my last attempt with 10,000,000
calculations run 100 times to get an average, each test does a GC.Collect();
and GC.WaitForPendingFinalizers();
so we are'nt fragmenting the memory
Results
Test : ms : Cycles (rough) : Increase
-------------------------------------------------------------------
OriginalCalc : 1,295 : 4,407,795,584 :
MarcEvalNoSubStrings : 241 : 820,660,220 : 437.34%, * 5.32
MarcEvalNoSubStringsUnsafe : 206 : 701,980,373 : 528.64%, * 6.28
MiraiMannCalc1 : 225 : 765,678,062 : 475.55%, * 5.75
MiraiMannCalc2 : 183 : 623,384,924 : 607.65%, * 7.07
MyCalc4 : 156 : 534,190,325 : 730.12%, * 8.30
MyCalc5 : 146 : 496,185,459 : 786.98%, * 8.86
MyCalc6 : 134 : 455,610,410 : 866.41%, * 9.66
Fastest Code so far
unsafe int Calc6(ref string expression)
{
int res = 0, val = 0, op = 0;
var isOp = false;
// pin the array
fixed (char* p = expression)
{
// Lets not evaluate this 100 million times
var max = p + expression.Length;
// lets go straight to the source and just increment the pointer
for (var i = p; i < max; i++)
{
// numbers are the most common thing so lets do a loose
// basic check for them and push them in to our val
if (*i >= '0') { val = val * 10 + *i - 48; continue; }
// The second most common thing are spaces
if (*i == ' ')
{
// not every space we need to calculate
if (!(isOp = !isOp)) continue;
// In this case 4 ifs are more efficient then a switch
// do the calculation, reset out val and jump out
if (op == '+') { res += val; val = 0; continue; }
if (op == '-') { res -= val; val = 0; continue; }
if (op == '*') { res *= val; val = 0; continue; }
if (op == '/') { res /= val; val = 0; continue; }
// this is just for the first op
res = val; val = 0; continue;
}
// anything else is considered an operator
op = *i;
}
if (op == '+') return res + val;
if (op == '-') return res - val;
if (op == '*') return res * val;
if (op == '/') return res / val;
throw new IndexOutOfRangeException();
}
}
Previous
unsafe int Calc4(ref string expression)
{
int res = 0, val = 0, op = 0;
var isOp = false;
fixed (char* p = expression)
{
var max = p + expression.Length;
for (var i = p; i < max; i++)
switch (*i)
{
case ' ':
isOp = !isOp;
if (!isOp) continue;
switch (op)
{
case '+': res += val; val = 0; continue;
case '-': res -= val; val = 0; continue;
case '*': res *= val; val = 0; continue;
case '/': res /= val; val = 0; continue;
default: res = val; val = 0; continue;
}
case '+': case '-': case '*': case '/': op = *i; continue;
default: val = val * 10 + *i - 48; continue;
}
switch (op)
{
case '+': return res + val;
case '-': return res - val;
case '*': return res * val;
case '/': return res / val;
default : return -1;
}
}
}
How i measured the Thread cycles
static class NativeMethods {
public static ulong GetThreadCycles() {
ulong cycles;
if (!QueryThreadCycleTime(PseudoHandle, out cycles))
throw new System.ComponentModel.Win32Exception();
return cycles;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool QueryThreadCycleTime(IntPtr hThread, out ulong cycles);
private static readonly IntPtr PseudoHandle = (IntPtr)(-2);
}
I thought id try to be smart and use fixed :/ and max this out with millions of calculations
public static unsafe int Calc2(string sInput)
{
var buf = "";
var start = sInput.IndexOf(' ');
var value1 = int.Parse(sInput.Substring(0, start));
string op = null;
var iResult = 0;
var isOp = false;
fixed (char* p = sInput)
{
for (var i = start + 1; i < sInput.Length; i++)
{
var cur = *(p + i);
if (cur == ' ')
{
if (!isOp)
{
op = buf;
isOp = true;
}
else
{
var value2 = int.Parse(buf);
switch (op[0])
{
case '+': iResult += value1 + value2; break;
case '-': iResult += value1 - value2; break;
case '*': iResult += value1 * value2; break;
case '/': iResult += value1 / value2; break;
}
value1 = value2;
isOp = false;
}
buf = "";
}
else
{
buf += cur;
}
}
}
return iResult;
}
private static void Main(string[] args)
{
var input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
var sb = new StringBuilder();
sb.Append(input);
for (var i = 0; i < 10000000; i++)
sb.Append(" + " + input);
var sw = new Stopwatch();
sw.Start();
Calc2(sb.ToString());
sw.Stop();
Console.WriteLine($"sw : {sw.Elapsed:c}");
}
Results were 2 seconds slower than the original!