解决链计算的最快方法

时间:2018-03-22 08:46:53

标签: c# performance

我有像

这样的输入
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
  • 计算从左到右,按数字计算
  • 数字为0到99
  • 加法前的乘法被忽略,因此14 + 2 * 32512
  • 可能的计算是+-*/
  • 0之后的分区是不可能的,因此/不能0

我的方法

public static int Calc(string sInput)
{
    int iCurrent = sInput.IndexOf(' ');
    int iResult = int.Parse(sInput.Substring(0, iCurrent));
    int iNext = 0;
    while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1)
    {
        iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2))));
        iCurrent = iNext;
    }
    return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3))));
}

public static int Operate(int iReturn, char cOperator, int iOperant)
{
    switch (cOperator)
    {
        case '+':
            return (iReturn + iOperant);
        case '-':
            return (iReturn - iOperant);
        case '*':
            return (iReturn * iOperant);
        case '/':
            return (iReturn / iOperant);
        default:
            throw new Exception("Error");
    }
}

我需要最快方式来获得结果。

问题:有没有办法让这个计算更快?我有多个线程,但我只使用一个。

更新

测试用例:(我删除了除以0错误并从StringBuilder.ToString()测量中移除了StopWatch

Random rand = new Random();
System.Text.StringBuilder input = new System.Text.StringBuilder();
string operators = "+-*/";
input.Append(rand.Next(0, 100));
for (int i = 0; i < 1000000; i++)
{
    int number = rand.Next(0, 100);
    char coperator = operators[rand.Next(0, number > 0 ? 4 : 3)];
    input.Append(" " + coperator + " " + number);
}
string calc = input.ToString();
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
int result = Calc(calc);
watch.Stop();

5 个答案:

答案 0 :(得分:20)

编辑编辑:使用The General和Mirai Mann的最新版本进行更新:

如果你想知道哪匹马最快:比赛马匹。以下是BenchmarkDotNet结果,比较了这个问题的各种答案(我没有将他们的代码合并到我的完整示例中,因为这感觉不对 - 只有数字被呈现)具有可重复但大的随机输入,通过:

static MyTests()
{
    Random rand = new Random(12345);
    StringBuilder input = new StringBuilder();
    string operators = "+-*/";
    var lastOperator = '+';
    for (int i = 0; i < 1000000; i++)
    {
        var @operator = operators[rand.Next(0, 4)];
        input.Append(rand.Next(lastOperator == '/' ? 1 : 0, 100) + " " + @operator + " ");
        lastOperator = @operator;
    }
    input.Append(rand.Next(0, 100));
    expression = input.ToString();
}
private static readonly string expression;

进行健全性检查(检查他们都做对了):

Original: -1426
NoSubStrings: -1426
NoSubStringsUnsafe: -1426
TheGeneral4: -1426
MiraiMann1: -1426

我们得到时间安排(注意:Original是问题中的OP版本; NoSubStrings[Unsafe]是我的版本,以及其他两个版本来自用户名的其他答案):

(较低的“平均值”更好)

(注意;有一个更新版本的Mirai Mann的实现,但我不再设置运行新测试的东西;但是:公平地假设它应该更好!)

运行时:.NET Framework 4.7(CLR 4.0.30319.42000),32位LegacyJIT-v4.7.2633.0

             Method |      Mean |     Error |    StdDev |
------------------- |----------:|----------:|----------:|
           Original | 104.11 ms | 1.4920 ms | 1.3226 ms |
       NoSubStrings |  21.99 ms | 0.4335 ms | 0.7122 ms |
 NoSubStringsUnsafe |  20.53 ms | 0.4103 ms | 0.6967 ms |
        TheGeneral4 |  15.50 ms | 0.3020 ms | 0.5369 ms |
         MiraiMann1 |  15.54 ms | 0.3096 ms | 0.4133 ms |

运行时:.NET Framework 4.7(CLR 4.0.30319.42000),64位RyuJIT-v4.7.2633.0

             Method |      Mean |     Error |    StdDev |    Median |
------------------- |----------:|----------:|----------:|----------:|
           Original | 114.15 ms | 1.3142 ms | 1.0974 ms | 114.13 ms |
       NoSubStrings |  21.33 ms | 0.4161 ms | 0.6354 ms |  20.93 ms |
 NoSubStringsUnsafe |  19.24 ms | 0.3832 ms | 0.5245 ms |  19.43 ms |
        TheGeneral4 |  13.97 ms | 0.2795 ms | 0.2745 ms |  13.86 ms |
         MiraiMann1 |  15.60 ms | 0.3090 ms | 0.4125 ms |  15.53 ms |

运行时:.NET Core 2.1.0-preview1-26116-04(CoreCLR 4.6.26116.03,CoreFX 4.6.26116.01),64位RyuJIT

             Method |      Mean |     Error |    StdDev |    Median |
------------------- |----------:|----------:|----------:|----------:|
           Original | 101.51 ms | 1.7807 ms | 1.5786 ms | 101.44 ms |
       NoSubStrings |  21.36 ms | 0.4281 ms | 0.5414 ms |  21.07 ms |
 NoSubStringsUnsafe |  19.85 ms | 0.4172 ms | 0.6737 ms |  19.80 ms |
        TheGeneral4 |  14.06 ms | 0.2788 ms | 0.3723 ms |  13.82 ms |
         MiraiMann1 |  15.88 ms | 0.3153 ms | 0.5922 ms |  15.45 ms |

我添加BenchmarkDotNet之前的原始答案:

如果我尝试这个,我会诱惑来查看2.1预览中的Span<T>工作 - 关键是Span<T>可以切片没有分配(并且string可以转换为Span<char>而不分配);这将允许在没有任何分配的情况下执行字符串雕刻和解析。但是,减少分配并不总是与原始性能完全相同(尽管它们是相关的),所以要知道它是否更快:你需要比赛你的马(即比较它们)。

如果Span<T>不是一个选项:您仍然可以通过手动跟踪int offset并且只是*从不使用SubString来执行相同的操作。

在任何一种情况下(stringSpan<char>):如果您的操作只需要处理整数表示的某个子集,我可能会想要将角色设为自定义int.Parse相当于不适用尽可能多的规则(文化,替代布局等),并且无需Substring即可运行 - 例如它可能需要stringref int offset,其中offset更新为解析停止的位置(因为它击中了运算符或空格),并且有效。

类似的东西:

static class P
{
    static void Main()
    {
        string input = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";

        var val = Evaluate(input);
        System.Console.WriteLine(val);
    }
    static int Evaluate(string expression)
    {
        int offset = 0;
        SkipSpaces(expression, ref offset);
        int value = ReadInt32(expression, ref offset);
        while(ReadNext(expression, ref offset, out char @operator, out int operand))
        {
            switch(@operator)
            {
                case '+': value = value + operand; break;
                case '-': value = value - operand; break;
                case '*': value = value * operand; break;
                case '/': value = value / operand; break;
            }
        }
        return value;
    }
    static bool ReadNext(string value, ref int offset,
        out char @operator, out int operand)
    {
        SkipSpaces(value, ref offset);

        if(offset >= value.Length)
        {
            @operator = (char)0;
            operand = 0;
            return false;
        }

        @operator = value[offset++];
        SkipSpaces(value, ref offset);

        if (offset >= value.Length)
        {
            operand = 0;
            return false;
        }
        operand = ReadInt32(value, ref offset);
        return true;
    }

    static void SkipSpaces(string value, ref int offset)
    {
        while (offset < value.Length && value[offset] == ' ') offset++;
    }
    static int ReadInt32(string value, ref int offset)
    {
        bool isNeg = false;
        char c = value[offset++];
        int i = (c - '0');
        if(c == '-')
        {
            isNeg = true;
            i = 0;
            // todo: what to do here if `-` is not followed by [0-9]?
        }

        while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9')
            i = (i * 10) + (c - '0');
        return isNeg ? -i : i;
    }
}

接下来,我可能会考虑是否值得切换到unsafe来删除双倍长度检查。所以我实现了两种方式,并用BenchmarkDotNet之类的东西来测试它是否值得。

编辑:这里添加了unsafe和BenchmarkDotNet用法:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;

static class P
{
    static void Main()
    {
        var summary = BenchmarkRunner.Run<MyTests>();
        System.Console.WriteLine(summary);
    }

}
public class MyTests
{
    const string expression = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
    [Benchmark]
    public int Original() => EvalOriginal.Calc(expression);
    [Benchmark]
    public int NoSubStrings() => EvalNoSubStrings.Evaluate(expression);
    [Benchmark]
    public int NoSubStringsUnsafe() => EvalNoSubStringsUnsafe.Evaluate(expression);
}
static class EvalOriginal
{
    public static int Calc(string sInput)
    {
        int iCurrent = sInput.IndexOf(' ');
        int iResult = int.Parse(sInput.Substring(0, iCurrent));
        int iNext = 0;
        while ((iNext = sInput.IndexOf(' ', iCurrent + 4)) != -1)
        {
            iResult = Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3), iNext - (iCurrent + 2))));
            iCurrent = iNext;
        }
        return Operate(iResult, sInput[iCurrent + 1], int.Parse(sInput.Substring((iCurrent + 3))));
    }

    public static int Operate(int iReturn, char cOperator, int iOperant)
    {
        switch (cOperator)
        {
            case '+':
                return (iReturn + iOperant);
            case '-':
                return (iReturn - iOperant);
            case '*':
                return (iReturn * iOperant);
            case '/':
                return (iReturn / iOperant);
            default:
                throw new Exception("Error");
        }
    }
}
static class EvalNoSubStrings {
    public static int Evaluate(string expression)
    {
        int offset = 0;
        SkipSpaces(expression, ref offset);
        int value = ReadInt32(expression, ref offset);
        while (ReadNext(expression, ref offset, out char @operator, out int operand))
        {
            switch (@operator)
            {
                case '+': value = value + operand; break;
                case '-': value = value - operand; break;
                case '*': value = value * operand; break;
                case '/': value = value / operand; break;
                default: throw new Exception("Error");
            }
        }
        return value;
    }
    static bool ReadNext(string value, ref int offset,
        out char @operator, out int operand)
    {
        SkipSpaces(value, ref offset);

        if (offset >= value.Length)
        {
            @operator = (char)0;
            operand = 0;
            return false;
        }

        @operator = value[offset++];
        SkipSpaces(value, ref offset);

        if (offset >= value.Length)
        {
            operand = 0;
            return false;
        }
        operand = ReadInt32(value, ref offset);
        return true;
    }

    static void SkipSpaces(string value, ref int offset)
    {
        while (offset < value.Length && value[offset] == ' ') offset++;
    }
    static int ReadInt32(string value, ref int offset)
    {
        bool isNeg = false;
        char c = value[offset++];
        int i = (c - '0');
        if (c == '-')
        {
            isNeg = true;
            i = 0;
        }

        while (offset < value.Length && (c = value[offset++]) >= '0' && c <= '9')
            i = (i * 10) + (c - '0');
        return isNeg ? -i : i;
    }
}

static unsafe class EvalNoSubStringsUnsafe
{
    public static int Evaluate(string expression)
    {

        fixed (char* ptr = expression)
        {
            int len = expression.Length;
            var c = ptr;
            SkipSpaces(ref c, ref len);
            int value = ReadInt32(ref c, ref len);
            while (len > 0 && ReadNext(ref c, ref len, out char @operator, out int operand))
            {
                switch (@operator)
                {
                    case '+': value = value + operand; break;
                    case '-': value = value - operand; break;
                    case '*': value = value * operand; break;
                    case '/': value = value / operand; break;
                    default: throw new Exception("Error");
                }
            }
            return value;
        }
    }
    static bool ReadNext(ref char* c, ref int len,
        out char @operator, out int operand)
    {
        SkipSpaces(ref c, ref len);

        if (len-- == 0)
        {
            @operator = (char)0;
            operand = 0;
            return false;
        }
        @operator = *c++;
        SkipSpaces(ref c, ref len);

        if (len == 0)
        {
            operand = 0;
            return false;
        }
        operand = ReadInt32(ref c, ref len);
        return true;
    }

    static void SkipSpaces(ref char* c, ref int len)
    {
        while (len != 0 && *c == ' ') { c++;len--; }
    }
    static int ReadInt32(ref char* c, ref int len)
    {
        bool isNeg = false;
        char ch = *c++;
        len--;
        int i = (ch - '0');
        if (ch == '-')
        {
            isNeg = true;
            i = 0;
        }

        while (len-- != 0 && (ch = *c++) >= '0' && ch <= '9')
            i = (i * 10) + (ch - '0');
        return isNeg ? -i : i;
    }
}

答案 1 :(得分:17)

以下解决方案是有限自动机。 Calc(输入)= O(n)。为了获得更好的性能,此解决方案不使用<?php echo "<script>function getDataFunction(clicked_id){ $id=clicked_id; }</script>"; session_start(); $phone=$_SESSION['phone']; $query= "(SELECT * FROM timetable WHERE ID == '$id')"; $result = mysqli_query($conn, "SELECT * FROM timetable WHERE ID == '$id'"); $row = $result->fetch_assoc(); $timefrom=$row['timefrom']; $timeto=$row['timeto']; $status=$row['stat']; $comment=$row['comm']; ?> IndexOfSubstring,字符串连接或重复读取值(多次提取Parse)...字符处理器。

input[i]

另一个实现。它从输入中减少了25%。我希望它的性能提高25%。

    static int Calculate1(string input)
    {
        int acc = 0;
        char last = ' ', operation = '+';

        for (int i = 0; i < input.Length; i++)
        {
            var current = input[i];
            switch (current)
            {
                case ' ':
                    if (last != ' ')
                    {
                        switch (operation)
                        {
                            case '+': acc += last - '0'; break;
                            case '-': acc -= last - '0'; break;
                            case '*': acc *= last - '0'; break;
                            case '/': acc /= last - '0'; break;
                        }

                        last = ' ';
                    }

                    break;

                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    if (last == ' ') last = current;
                    else
                    {
                        var num = (last - '0') * 10 + (current - '0');
                        switch (operation)
                        {
                            case '+': acc += num; break;
                            case '-': acc -= num; break;
                            case '*': acc *= num; break;
                            case '/': acc /= num; break;
                        }
                        last = ' ';
                    }
                    break;

                case '+': case '-': case '*': case '/':
                    operation = current;
                    break;
            }
        }

        if (last != ' ')
            switch (operation)
            {
                case '+': acc += last - '0'; break;
                case '-': acc -= last - '0'; break;
                case '*': acc *= last - '0'; break;
                case '/': acc /= last - '0'; break;
            }

        return acc;
    }

我实施了另外一个变体:

    static int Calculate2(string input)
    {
        int acc = 0, i = 0;
        char last = ' ', operation = '+';

        while (i < input.Length)
        {
            var current = input[i];
            switch (current)
            {
                case ' ':
                    if (last != ' ')
                    {
                        switch (operation)
                        {
                            case '+': acc += last - '0'; break;
                            case '-': acc -= last - '0'; break;
                            case '*': acc *= last - '0'; break;
                            case '/': acc /= last - '0'; break;
                        }

                        last = ' ';
                        i++;
                    }

                    break;

                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    if (last == ' ')
                    {
                        last = current;
                        i++;
                    }
                    else
                    {
                        var num = (last - '0') * 10 + (current - '0');
                        switch (operation)
                        {
                            case '+': acc += num; break;
                            case '-': acc -= num; break;
                            case '*': acc *= num; break;
                            case '/': acc /= num; break;
                        }

                        last = ' ';
                        i += 2;
                    }
                    break;

                case '+': case '-': case '*': case '/':
                    operation = current;
                    i += 2;
                    break;
            }
        }

        if (last != ' ')
            switch (operation)
            {
                case '+': acc += last - '0'; break;
                case '-': acc -= last - '0'; break;
                case '*': acc *= last - '0'; break;
                case '/': acc /= last - '0'; break;
            }

        return acc;
    }

抽象点结果:

  • Calculate1 230
  • Calculate2 192
  • Calculate3 111

答案 2 :(得分:6)

根据评论,这个答案并不能提供高效的解决方案。 我将它留在这里,因为有些要考虑/将来可能会对其他人发现这个问题感兴趣;但正如人们在下面所说,这远非最有效的解决方案。

原始答案

.net框架已经提供了一种处理以字符串形式给出的公式的方法:

var formula = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34";
var result = new DataTable().Compute(formula, null);
Console.WriteLine(result); //returns 139.125490196078

基于评论的初步反馈

根据评论主题,我需要指出一些事情:

这是否按照我所描述的方式工作?

没有;这符合正常的数学规则。

我认为你修改后的规则是简化编写代码来处理它们,而不是因为你想支持一个新的数学分支?如果是这样的话,我反对这一点。人们会期望事物以某种方式表现;因此,您必须确保向您的代码发送方程式的任何人都知道了这些新数学的规则,而不是能够使用他们现有的期望。

此处没有更改规则的选项;所以如果你的要求是改变数学规则,这对你来说不会有用。

这是最快的解决方案

没有。然而,它应该表现良好,因为MS花费了大量时间来优化他们的代码,因此可能比任何手动代码执行相同的执行速度更快(尽管这个代码确实比支持四个主要运算符要多得多;所以它没有做同样的事情。)

根据MatthewWatson的特定注释(即调用DataTable构造函数会产生很大的开销),您需要创建然后重新使用此对象的一个​​实例。根据您的解决方案的外观,有多种方法可以做到这一点;这是一个:

interface ICalculator //if we use an interface we can easily switch from datatable to some other calulator; useful for testing, or if we wanted to compare different calculators without much recoding
{
    T Calculate<T>(string expression) where T: struct;
}
class DataTableCalculator: ICalculator 
{
    readonly DataTable dataTable = new DataTable();
    public DataTableCalculator(){}
    public T Calculate<T>(string expression) where T: struct =>
        (T)dataTable.Compute(expression, null);
}

class Calculator: ICalculator
{
    static ICalculator internalInstance;
    public Calculator(){}
    public void InitialiseCalculator (ICalculator calculator)
    {
        if (internalInstance != null)
        {
            throw new InvalidOperationException("Calculator has already been initialised");
        }
        internalInstance = calculator;
    }
    public T Calculate<T>(string expression) where T: struct =>
        internalInstance.Calculate<T>(expression);
}

//then we use it on our code
void Main()
{
    var calculator1 = new Calculator();
    calculator1.InitialiseCalculator(new DataTableCalculator());
    var equation = "14 + 2 * 32 / 60 + 43 - 7 + 3 - 1 + 0 * 7 + 87 - 32 / 34"; 
    Console.WriteLine(calculator1.Calculate<double>(equation)); //139.125490196078
    equation = "1 + 2 - 3 + 4";
    Console.WriteLine(calculator1.Calculate<int>(equation)); //4
    calculator1 = null;
    System.GC.Collect(); //in reality we'd pretty much never do this, but just to illustrate that our static variable continues fro the life of the app domain rather than the life of the instance
    var calculator2 = new Calculator();
    //calculator2.InitialiseCalculator(new DataTableCalculator()); //uncomment this and you'll get an error; i.e. the calulator should only be initialised once.
    equation = "1 + 2 - 3 + 4 / 5 * 6 - 7 / 8 + 9";
    Console.WriteLine(calculator2.Calculate<double>(equation)); //12.925
}

注意:上述解决方案使用静态变量;有些人反对使用静力学。对于这种情况(即在应用程序的生命周期中,您只需要一种方法进行计算),这是一个合法的用例。如果您想支持在运行时切换计算器,则需要采用不同的方法。

测试后更新&amp;比较

进行了一些性能测试:

  • DataTable.Compute方法的最大问题是对于方程式而言,您处理它的大小会抛出StackOverflowException; (即基于你的方程生成器的循环for (int i = 0; i < 1000000; i++)
  • 对于使用较小等式(i < 1000)的单个操作,计算方法(包括Convert.ToInt32结果上的构造函数和double)需要的时间要长近100倍。
  • 对于单个操作我也经常遇到溢出异常;即因为操作的结果将值推到支持的数据类型范围之外......
  • 即使我们将构造函数/初始化调用移到定时区域之外并将转换移到int(并运行数千次迭代以获得平均值),您的解决方案的速度比我的快3.5倍。

链接到文档:https://msdn.microsoft.com/en-us/library/system.data.datatable.compute%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

答案 3 :(得分:3)

更新

我最初的答案是深夜尝试把它放在unsafe并且我失败了(实际上根本没有工作而且速度慢)。但是我决定再给这个镜头

前提是将内容全部内联,尽可能多地删除IL,将所有内容保存在intchar*中,并使我的代码更漂亮。我通过删除开关进一步优化了这一点,Ifs在这种情况下效率很高,我们也可以以最合理的方式对它们进行排序。最后,如果我们删除对我们所做事情的检查量并假设输入是正确的,我们可以通过假设这样的事情来消除更多的开销;如果char是&gt; '0'它必须是一个数字。如果它是一个空格我们可以做一些计算,否则它必须是一个算子。

这是我最后一次尝试10,000,000计算运行100次以获得平均值,每次测试都执行GC.Collect();GC.WaitForPendingFinalizers();所以我们不会破坏内存

结果

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

目前最快的代码

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

上一页

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

我如何衡量线程周期

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

}

原帖

我认为id试图变得聪明并且使用fixed:/并将其与数百万次计算结合起来

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

结果比原来慢2秒!

答案 4 :(得分:1)

这是一个Java有趣的事实。我在Java中实现了相同的功能,它的运行速度比C#中的Mirai Mann快20倍。在我的机器上,100M字符输入字符串大约需要353毫秒。

以下是创建和测试结果的代码。

另外,请注意,虽然它是一个优秀的Java / C#性能测试程序,但这不是最佳解决方案。通过多线程可以实现更好的性能。可以计算字符串的一部分然后合并结果。

public class Test {

    public static void main(String...args){
        new Test().run();
    }

    private void run() {
        long startTime = System.currentTimeMillis();
        Random random = new Random(123);
        int result = 0;
        StringBuilder input = new StringBuilder();
        input.append(random.nextInt(99) + 1);
        while (input.length() < 100_000_000){
            int value = random.nextInt(100);
            switch (random.nextInt(4)){
                case 0:
                    input.append("-");
                    result -= value;
                    break;
                case 1: // +
                    input.append("+");
                    result += value;
                    break;
                case 2:
                    input.append("*");
                    result *= value;
                    break;
                case 3:
                    input.append("/");
                    while (value == 0){
                        value = random.nextInt(100);
                    }
                    result /= value;
                    break;
            }
            input.append(value);
        }
        String inputData = input.toString();
        System.out.println("Test created in " + (System.currentTimeMillis() - startTime));

        startTime = System.currentTimeMillis();
        int testResult = test(inputData);
        System.out.println("Completed in " + (System.currentTimeMillis() - startTime));

        if(result != testResult){
            throw new Error("Oops");
        }
    }

    private int test(String inputData) {
        char[] input;
        try {
            Field val = String.class.getDeclaredField("value");
            val.setAccessible(true);
            input = (char[]) val.get(inputData);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
        int result = 0;
        int startingI = 1;
        {
            char c = input[0];
            if (c >= '0' && c <= '9') {
                result += c - '0';
                c = input[1];
                if (c >= '0' && c <= '9') {
                    result += (c - '0') * 10;
                    startingI++;
                }
            }
        }

        for (int i = startingI, length = input.length, value=0; i < length; i++) {
            char operation = input[i];
            i++;
            char c = input[i];
            if(c >= '0' && c <= '9'){
                value += c - '0';
                c = input[i + 1];
                if(c >= '0' && c <= '9'){
                    value = value * 10 + (c - '0');
                    i++;
                }
            }
            switch (operation){
                case '-':
                    result -= value;
                    break;
                case '+':
                    result += value;
                    break;
                case '*':
                    result *= value;
                    break;
                case '/':
                    result /= value;
                    break;
            }
            value = 0;
        }

        return result;
    }
}

当您阅读代码时,您可以看到我在将字符串转换为char数组时使用了一个小的hack。我改变了字符串,以避免为char数组分配额外的内存。