1.   Math Expression Parser 비교

 

1.1 DataTable

.Net에서 제공하는 DataTable Class Compute 메서드를 이용해 식을 계산한 코드이다. 먼저 a, b 문자를 값으로 치환한 후 계산을 진행한다. Compute 메서드의 반환값은 object로써 type에 맞게 캐스팅 해주어야하는 단점이 있다. 테스트는 아래와 같이 진행하였다.

object res = null;
DataTable dt = new DataTable();
 
 
string exp = expression;
foreach (KeyValuePair<stringstring> item in values)
{
    exp = exp.Replace(item.Key, item.Value);
}
 
Stopwatch st = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
    res = dt.Compute(exp, null);
    Type type = res.GetType();
 
    if (type == typeof(int))
    {
        int a = (int)res;
    }
    else
    {
        double b = (double)res;
    }
}
st.Stop();
Console.WriteLine(string.Format("DataTable = {0} : {1}", st.Elapsed.ToString(), res.ToString()));

 

1.2 Ncalc

CodePlex에 등록되어 있는 오픈소스로써 System.Math 클래스의 메서들과 사용자 정의 함수를 지원한다. 사용자 정의 함수는 이벤트 방식으로 함수명으로 비교해서 개발자가 직접 처리를 해주어야 한다.

Debug.Assert(0 == new Expression("Sin(0)").Evaluate());

  Debug.Assert(2 == new Expression("Sqrt(4)").Evaluate());

Debug.Assert(0 == new Expression("Tan(0)").Evaluate());

 

//함수 처리

Expression e = new Expression("SecretOperation(3, 6)");

  e.EvaluateFunction += delegate(string name, FunctionArgs args)

      {

          if (name == "SecretOperation")

              args.Result= (int)args.Parameters[0].Evaluate() + (int)args.Parameters[1].Evaluate();

      };

  Debug.Assert(9 == e.Evaluate());

 

테스트는 아래와 같이 진행하였다.

object res = null;
NCalc.Expression ex = new NCalc.Expression(expression);
 
ex.Parameters.Clear();
 
Dictionary<stringobject> v = new Dictionary<stringobject>();
 
foreach (KeyValuePair<stringstring> item in values)
{
    ex.Parameters.Add(item.Key, Convert.ToSingle(item.Value));
}
 
Stopwatch st = Stopwatch.StartNew();
 
 
for (int i = 0; i < 100000; i++)
{
    res = ex.Evaluate();
}
 
st.Stop();
Console.WriteLine(string.Format("NCalc = {0} : {1}", st.Elapsed.ToString(), res.ToString())); 

 

1.3 Flee

CodePlex에 등록되어 있는 오픈 소스로써 Fast Lightweight Expression Evaluator라는 이름과 같이 테스트한 오픈소스 중에서는 가장 빠르다. System.Math Class 및 타 Class를 등록해서 사용 할 수있고, 사용자 정의 함수 기능을 제공한다. 사용자 정의 함수는 문자열로 정의 할 수 있지만, 매개변수가 바뀔시 Recalculate라는 메서드를 호출하여 엔진 내부를 수정 해야 하는 문제가 있다.

 

// 함수 테스트
CalculationEngine engine = new CalculationEngine();
ExpressionContext context = new ExpressionContext();
 
 
context.Variables.Add("a", 2);
context.Variables.Add("b", 2);
 
engine.Add("Func1""(a*b) + 2.0", context);
 
 
Stopwatch st = Stopwatch.StartNew();
double res = 0;
for (int i = 0; i < 800; i++)
{
    res = engine.GetResult<double>("Func1");
    context.Variables["a"] = 3;
    engine.Recalculate("Func1");
}
st.Stop();
Console.WriteLine(string.Format("FleeFunc = {0} : {1}", st.Elapsed.ToString(), res.ToString()));

 

테스트는 아래와 같이 진행하였다.

ExpressionContext context = new ExpressionContext();
 
double res = double.NaN;
 
context.Variables.Clear();
            
foreach (KeyValuePair<stringstring> item in values)
{
    context.Variables[item.Key] = Convert.ToDouble(item.Value);
}
 
IGenericExpression<double> exp = context.CompileGeneric<double>(expression);
 
Stopwatch st = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
    res = exp.Evaluate();
}
st.Stop();
Console.WriteLine(string.Format("Flee = {0} : {1}", st.Elapsed.ToString(), res.ToString()));

 

1.4 MathParseNet

CodeProject에 등록되어 있는 오픈 소스로써 sin, cos, tg, ctg 등 기본적인 수학 함수를 제공한다. 식의 해석과 동시에 값을 반환하는 형식이여서 오픈 소스 중에 가장 느렸고, 반환 값의 타입은 int, double 두개의 타입만 존재한다.

MathParserNet.Parser parser = new MathParserNet.Parser();
 
double res = double.NaN;
parser.RemoveAllVariables();
 
foreach (KeyValuePair<stringstring> item in values)
{
    parser.AddVariable(item.Key, item.Value);
}
 
Stopwatch st = Stopwatch.StartNew();
 
for (int i = 0; i < 100000; i++)
{
    MathParserNet.SimplificationReturnValue ret = parser.Simplify(expression);
    if (ret.ReturnType == MathParserNet.SimplificationReturnValue.ReturnTypes.Integer)
    {
        res = ret.IntValue;
    }
    else
    {
        res = ret.DoubleValue;
    }
}
st.Stop();
Console.WriteLine(string.Format("MathParseNet = {0} : {1}", st.Elapsed.ToString(),res.ToString()));

 

 

 

1.5  테스트

  컴파일 모드 : Release

테스트한 식 : (a * b) / 2 + 3 - 2 * 321

테스트 값 : float a = 2, float b = 3

호출 테스트 수 : 100,000

 

테스트 결과:

CSharp

Lambda

DataTable

Ncalc

Flee

MathParseNet

Test1

0.000352

0.000498

0.433453

0.205334

0.017759

14.45983

Test2

0.000259

0.0005

0.350152

0.204596

0.017735

14.61664

Test3

0.00028

0.000492

0.34059

0.154331

0.017455

14.54161

Test4

0.000243

0.000489

0.339338

0.154261

0.017464

14.52951

Test5

0.000251

0.000552

0.3525

0.206181

0.018056

14.5986

평균

0.000277

0.000506

0.363207

0.184941

0.017694

14.54924

CSharp 기준(배수)

1

1.827653

1311.215

667.656

63.87588

52524.33

3 Parser 테스트 결과

 

1.6 결론

Flee 오픈소스가 가장 빠르지만, CSharp으로 하드코딩한 연산에 비해 63배나 느렸다. Parser들은 속도에 민간한 프로젝트에 적용하기는 힘들 것 같다.

 

 

프로젝트 :AboutCalculParser.zip

 

 

+ Recent posts