Fastest way to solve chain-calculations

后端 未结 5 818
长发绾君心
长发绾君心 2021-02-04 07:14

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
         


        
5条回答
  •  慢半拍i
    慢半拍i (楼主)
    2021-02-04 07:31

    Update

    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);
    
    }
    

    Original Post

    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!

提交回复
热议问题