奇怪的JVM与.NET CLR性能差异(Short Lived对象+虚拟调用)

时间:2014-09-15 18:34:53

标签: java .net garbage-collection jvm clr

我正在测试crating +调用+使用虚方法放弃大量短生命对象的性能。

这是我的C#代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BoxingTest
{
    public abstract class BoxedVal
    {
        public abstract BoxedVal Add(BoxedVal other);
        public abstract bool LessThan(BoxedVal other);
        public abstract bool GreaterThan(BoxedVal other);
    }

    public class BoxedInt : BoxedVal
    {
        public int Value;

        public BoxedInt(int value)
        {
            Value = value;
        }

        public override BoxedVal Add(BoxedVal other)
        {
            BoxedInt i = other as BoxedInt;
            return new BoxedInt(Value + i.Value);
        }

        public override bool LessThan(BoxedVal other)
        {
            BoxedInt i = other as BoxedInt;
            return Value < i.Value;
        }

        public override bool GreaterThan(BoxedVal other)
        {
            BoxedInt i = other as BoxedInt;
            return Value > i.Value;
        }
    }

    class Program
    {
        private static int Fib(int nn)
        {
            BoxedVal one = new BoxedInt(1);

            BoxedVal a = one;
            BoxedVal b = one;

            BoxedVal n = new BoxedInt(nn);

            BoxedVal thousand = new BoxedInt(1000);

            for (BoxedVal i = new BoxedInt(2); i.LessThan(n); i = i.Add(one))
            {
                BoxedVal c = a.Add(b);
                a = b;
                b = c;

                if (b.GreaterThan(thousand))
                {
                    a = one;
                    b = one;
                }
            }
            return (b as BoxedInt).Value;
        }

        static void Main(string[] args)
        {
            int times = 5;
            int n = 20000000;

            int total = 0;

            for (int i = 0; i < times; i++)
            {
                var start = DateTime.Now;
                int val = Fib(n);

                int dt = (DateTime.Now - start).Milliseconds;
                total += dt;
                Console.WriteLine(val);
                Console.WriteLine("Elapsed: {0} ms", dt);
            }

            Console.WriteLine("Average: {0} ms", total / times);

            Console.ReadLine();

        }
    }
}

和Java中的相同代码:

package boxingtest;

abstract class BoxedVal
{
    public abstract BoxedVal Add(BoxedVal other);
    public abstract boolean LessThan(BoxedVal other);
    public abstract boolean GreaterThan(BoxedVal other);
}


class BoxedInt extends BoxedVal
{
    public int Value;


    public BoxedInt(int value)
    {
        Value = value;
    }

    @Override
    public BoxedVal Add(BoxedVal other)
    {
        BoxedInt i = (BoxedInt)other;
        return new BoxedInt(Value + i.Value);
    }

    @Override
    public boolean LessThan(BoxedVal other)
    {
        BoxedInt i = (BoxedInt)other;
        return Value < i.Value;
    }

    @Override
    public boolean GreaterThan(BoxedVal other)
    {
        BoxedInt i = (BoxedInt)other;
        return Value > i.Value;
    }
}

public class BoxingTest {

    private static int Fib(int nn)
    {
        BoxedVal one = new BoxedInt(1);

        BoxedVal a = one;
        BoxedVal b = one;

        BoxedVal n = new BoxedInt(nn);

        BoxedVal thousand = new BoxedInt(1000);

        for (BoxedVal i = new BoxedInt(2); i.LessThan(n); i = i.Add(one))
        {
            BoxedVal c = a.Add(b);
            a = b;
            b = c;

            if (b.GreaterThan(thousand))
            {
                a = one;
                b = one;
            }
        }
        return ((BoxedInt)b).Value;
    }

    public static void main(String[] args) {

        int times = 5;
        int n = 20000000;

        long total = 0;


        for (int i = 0; i<times;i++){
            long start = System.currentTimeMillis();

            int val = Fib(n);
            long dt = System.currentTimeMillis() - start;
            total+= dt;
            System.out.println(val);
            System.out.println("Elapsed: "+dt+" ms");
        }

        System.out.println("Average: "+(total/times)+" ms");

    }

}

结果

我遇到的情况是,我的Windows机器上的Java(1.7)版本比.NET(4.5)快<2> 。考虑到GC既是世代的又是强烈优化的,我认为这是一个很大的差异,我在网上找不到任何解释它的东西。

我希望我的.NET代码在这种情况下像Java那样快速执行。我可以这样做吗? 有人有想法吗?是否有任何文章/博客文章可以帮助我?

3 个答案:

答案 0 :(得分:3)

在我的环境中,Java 1.8.0.25比.NET 4.5.1(x64都快)快三倍。 你得到了更近的改变&#34; as&#34;表达式到标准转换。像

BoxedInt i = other as BoxedInt;

BoxedInt i = (BoxedInt)other;

添加一些像

这样的结构
int Value;
int[] Dummy;

public BoxedInt(int value)
{
    Value = value;
    Dummy = new int[10];
}

两者都使Java比.NET慢8倍 这种微观基准测试显示了有趣的结果,但在许多方面都不可行。

答案 1 :(得分:1)

在这种情况下,使用Server GC无需更改,因为只有一个线程正在运行。值类型也是不好的选择。 由于BoxedInt.Add()中的BoxedInt结构太多,因此值类型比原始类型慢约40%。

在这种情况下,只有Java和.NET之间的差异才是对象构造的代价。 如果您使用“只有一个整数”作为一个类并花费大量时间在其上,请使用Java。

但这只是微观基准。如前所述,只添加一个整数数组引用会使Java变慢。

答案 2 :(得分:0)

有很多方法可以解决这种情况。在不更改代码的情况下,启用“服务器”垃圾收集器应该稍微缩小差距:

<gcServer> Element

另一种方法是在C#代码中小心使用值类型而不是引用类型,以避免完全分配,即使您正在使用值的许多“实例”。这并不总是正确的方法,但对于某些类型的问题(特别是上面显示的情况),值类型可以导致.NET代码比等效的Java代码执行许多倍的速度