JScript.net的运行效率测试(兼多种语言效率对比)

昨天的文章《用JScript.net写.net应用程序》一文写了之后,对于其运行效率问题有了一点疑问,所以需要进行以下测试,前人当然做过很多种测试,不过bigtall的测试方法有些不同。这里我采用了斐波纳契的两个算法,一个是递归实现,一个是迭代实现。采用斐波纳契的理由如下:

  1. 它是一个复杂度为O(2n)的算法,计算量足够大
  2. 相同的计算,递归和迭代的主要区别是堆栈的处理,我们也可以同时比较一下不同语言在调用函数之间的效率差别。
  3. 代码简单,而且算法容易理解。不同测试代码之间的差别也小,不容易起争议。

测试运行时候考虑到如下的情况:

  1. 第一次系统装入是从磁盘装入,而后几次则是直接从磁盘缓存装入,所每个测试连续运行4遍,第一遍时间不计入。
  2. 因为IO库实现效率不同,所以算法代码中不存在任何IO调用,纯计算代码。

参与比较的语言包括c,c#,标准的javascript,JScript.net,后来觉得不过瘾,把java6也加上了。加上bigtall自己写的一个计算时间的小程序和批处理,一共12段代码,表示如下:

fibc.c fib2c.c

long Fib(long n)
{
    if (n <= 1) {
        return n;
    } else {
        return Fib(n - 1) + Fib(n - 2);
    }
}

void main()
{
    int i;
    for(i = 0; i < 10; i++)
        Fib(30);
}

long Fib(long n)
{
    int i;
    long a = 0, b = 1, c=0;
    if (n <= 1) {
        return n;
    } else {
        for (i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

void main()
{
    int i;
    for(i = 0; i < 26925370; i++)
        Fib(30);
}

fibcs.cs fib2cs.cs

public class A
{
static long Fib(long n)
{
    if (n <= 1) {
        return n;
    } else {
        return Fib(n - 1) + Fib(n - 2);
    }
}

public static void Main()
{
    for(int i = 0; i < 10; i++)
        Fib(30);
}

}

public class A
{
static long Fib(long n)
{
    int i;
    long a = 0, b = 1, c=0;
    if (n <= 1) {
        return n;
    } else {
        for (i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

public static void Main()
{
    for(int i = 0; i < 26925370; i++)
        Fib(30);
}

}

fibjava.java fib2java.java

public class fibjava
{
static long Fib(long n)
{
    if (n <= 1) {
        return n;
    } else {
        return Fib(n - 1) + Fib(n - 2);
    }
}

public static void main(String[] args)
{
    for(int i = 0; i < 10; i++)
        Fib(30);
}

}

public class fib2java
{
static long Fib(long n)
{
    int i;
    long a = 0, b = 1, c=0;
    if (n <= 1) {
        return n;
    } else {
        for (i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

public static void main(String[] args)
{
    for(int i = 0; i < 26925370; i++)
        Fib(30);
}

}

fibjs1.js fib2js1.js

function Fib(n)
{
    if (n <= 1) {
        return n;
    } else {
        return Fib(n - 1) + Fib(n - 2);
    }
}

for(var i:int = 0; i < 10; i++)
    Fib(30);

function Fib(n)
{
    var i;
    var a = 0, b = 1, c=0;
    if (n <= 1) {
        return n;
    } else {
        for (i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

for(var i:int = 0; i < 26925370; i++)
    Fib(30);

fibjs2.js fib2js2.js

function Fib(n:int):int
{
    if (n <= 1) {
        return n;
    } else {
        return Fib(n - 1) + Fib(n - 2);
    }
}

for(var i:int = 0; i < 10; i++)
    Fib(30);

function Fib(n:int):int
{
    var i:int;
    var a:int = 0, b:int = 1, c:int=0;
    if (n <= 1) {
        return n;
    } else {
        for (i = 2; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

for(var i:int = 0; i < 26925370; i++)
    Fib(30);

ptime.cs cp.bat

public class A
{
    public static void Main()
    {
        System.Console.Write(System.DateTime.Now.Ticks);
        System.Console.Write(',');
    }

}

@echo off
cl /O2 fibc.c
cl /O2 fib2c.c

csc /o+ /debug- fibcs.cs
csc /o+ /debug- fib2cs.cs

jsc /fast- /debug- fibjs1.js
jsc /fast- /debug- fib2js1.js

jsc /fast+ /debug- fibjs2.js
jsc /fast+ /debug- fib2js2.js

"%JAVA_HOME%\bin\javac" -g:none fibjava.java
"%JAVA_HOME%\bin\javac" -g:none fib2java.java

echo 编译完成

call:run fibc
call:run fib2c
call:run fibcs
call:run fib2cs
call:run fibjs1
call:run fib2js1
call:run fibjs2
call:run fib2js2
call:run "%JAVA_HOME%\bin\java" fibjava
call:run "%JAVA_HOME%\bin\java" fib2java
echo finish!

goto end
:run
echo =================================
ptime & %1 %2 & ptime & echo %1 %2 1
ptime & %1 %2 & ptime & echo %1 %2 2
ptime & %1 %2 & ptime & echo %1 %2 3
ptime & %1 %2 & ptime & echo %1 %2 4

:end

运行测试的结果如下表格所示,表格内部蓝色的4组数据分别为1,2,3,4测试数据,黑色数据为后三组测试结果的平均数,绿色数据为相对C语言运行耗时的比例,最后一行红色纵比数字为相同语言【单次】递归和迭代算法的耗时比例。时间单位为百分之一秒:

 

C

C#

java

js

JScript.net

递归
(10次)

119
49
48
49

486
439
461
441

2258
520
467
464

75397
75327
76424
74228

1571
1501
1502
1499

48.67

447

483.67

75326.33

1500.67

1

9.18

9.94

1547.70

30.83

迭代
(26,925,370次)

120
49
47
46

5261
5041
5040
5039

7880
7769
7762
7766

125786
127117
127273
127541

9196
9101
9086
9121

47.33

5040

7765.67

127310.33

9102.67

1

106.48

164.06

2689.65

192.31

纵比 0.97 11.28 16.06 1.69 6.07

由此,我们看出,如果横向比较,以C语言运行速度为标准,递归运算的时候,C#和java的速度都慢了将近10倍,JScript.net慢了将近31倍,js因为使用了运行时绑定,速度慢了1500倍之多;而一旦消除了函数调用,使用纯计算代码的迭代算法的运行时间上,各种语言相差更大,而且明显C#代码比java快(这里没有考虑基础类库装入的差别,因为M$对.net有预装入),最差的依然是javascript,不过看起来不带调用的后期绑定似乎更快一些。不过令人惊讶的是JScript.net的编译优化做的不错,速度算是很快了。

纵向比较之前,我们需要对算法进行一下分析,通过简单的代码,我们得知fib(30)的递归调用次数为2692537次,10次重复就是26925370次。这个就是递归和迭代算法的区别所在,但是我们把迭代的次数也设定为26925370,以消除函数调用的差别,突出代码的线性运行差别。通过对代码的分析,我们得出代码特征的统计表格:

  递归 迭代
赋值语句 3 120
变量分配 2 4
函数调用 2 0
返回 2 2
条件判断 1 30
跳转 1 30
累计 11 186

迭代算法和递归算法相比,明显代码量较大,其代码规模大约是递归的186/11=16.9倍。但是运行时间中除了java体现出了这一比例之外,其他都比这个比例要小。C语言甚至时间更短,如果不考虑测试误差,唯一合理的解释应该是代码优化问题,因为编译器和CPU都有优化代码的能力,但是显然无论是哪种优化,都无法跨越函数调用进行优化;C#比java要快,是不是说明C#的优化器比java要好一些呢?但是JS代码的两个比例值有点让我难住了,但是也并非不可解释,因为js代码中间可以优化的地方实在是太多太多了。

结论:

  1. C作为一种老牌的中高级语言,优势没得说。
  2. 递归少用,尤其是JavaScript,连函数调用都尽可能压缩一些。
  3. 本文的最主要目的,如果用JScript.net做一般应用程序,效率应该属于可以接受的范围,但是千万不要进行数值计算。
  4. 以前看过什么java或者C#运行效率可以达到C语言的70%之类的文章,现在看来是有水分的,如果单纯比较编译器的效率,我看差距还是明显的。看来枪手文章还是要警惕啊!

另外给各位看官提一个小小的请求,如果哪位对python,ruby,perl等熟悉的,用相同算法做一个测试如何?

本文章算法参考了浅议Fibonacci(斐波纳契)数列求解

========================================

2007-11-16 17:30修改迭代部分的循环次数为26925370次,重新更正相关测试的时间和部分结论。非常感谢装配脑袋的提醒。谢谢!另外对之前给大家的误导表示歉意!

posted on 2007-11-16 15:24  老翅寒暑  阅读(3389)  评论(33编辑  收藏  举报

导航