在.NET与Java中锁定的成本

时间:2011-08-27 17:00:36

标签: c# java performance locking synchronized

我正在使用Disruptor框架及其.NET平台端口,并发现了一个有趣的案例。可能是我完全错过了一些东西所以我正在寻求全能社区的帮助。

        long iterations = 500*1000*1000;
        long testValue = 1;

        //.NET 4.0. Release build. Mean time - 26 secs;
        object lockObject = new object();
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            lock (lockObject)
            {
                testValue++;    
            }
        }
        sw.Stop();

        //Java 6.25. Default JVM params. Mean time - 17 secs.
        Object lock = new Object();
        long start = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++)
        {
                synchronized (lock)
                {
                    testValue++;
                }
        }
        long stop = System.currentTimeMillis();

似乎在.NET中使用signle线程获取锁定的成本仅比Java高50%。起初我对计时器很怀疑,但我已经进行了几次同样的测试,结果只是提到了上面的平均值。然后我对 synchronized 代码块感到怀疑,但它只是 monitorenter / monitorexit 字节代码指令 - 与在.NET中锁定关键字。在.NET与Java中,为什么采取锁定的任何其他想法都是如此昂贵?

6 个答案:

答案 0 :(得分:22)

是的,在.NET中使用无竞争锁定看起来比在Java中更昂贵。 (我的上网本上的结果仍然略显引人注目。)

性能的各个方面在一个平台上比另一个平台更快,有时在这个程度上。 HotSpot JIT和.NET JIT在各方面都截然不同 - 尤其是因为.NET JIT只在IL上运行一次,而HotSpot能够越来越多地优化作为特定的一部分代码运行得越来越频繁。

重要的问题是这是否真的重要。如果您的真实生活应用程序花费每分钟获得5亿次无限制锁定,那么它可能 显着 - 您可能应该稍微重新设计您的应用程序。如果您的真实应用程序实际上在锁定内(或锁定的获取之间)实际工作,那么它不太可能成为真正的瓶颈。

我最近发现了两个.NET陷阱(part one; part two),当我正在编写“系统级库”时,我需要解决它们,它们会产生重大影响当一个应用程序执行 lot 的日期/时间解析时 - 但这种微优化很少值得做。

答案 1 :(得分:8)

要记住关于微基准测试的第一件事是,Java特别擅长识别和消除无法执行任何操作的代码。我一次又一次地发现,Java比其他任何语言都更快地执行无意义的代码。 ;)

如果Java与其他语言相比速度惊人,那么第一个问题应该是;代码是否对远程有用? (或者看起来它可能很有用)

Java倾向于循环展开比以前更多。它还可以组合锁。由于您的测试是无可争议的,并且确实做了任何事情,您的代码就像是看起来像。

for (int i = 0; i < iterations; i+=8) {
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
    synchronized (lock) {
        testValue++;
    }
}

成为

for (int i = 0; i < iterations; i+=8) {
    synchronized (lock) {
        testValue++;
        testValue++;
        testValue++;
        testValue++;
        testValue++;
        testValue++;
        testValue++;
        testValue++;
    }
}

因为没有使用testValue。

for (int i = 0; i < iterations; i+=8) {
    synchronized (lock) {
    }
}

最后

{ }

答案 2 :(得分:3)

变量'testValue'是方法的本地吗?如果是这样,JRE可能检测到锁定是不必要的,因为变量是一个线程的本地变量,因此根本不锁定。

这解释为here

要说明JVM决定做什么优化有多难 - 以及何时决定这样做 - 从连续三次运行代码中检查这些结果:

public static void main(String[] args) {
  System.out.println("Java version: " + System.getProperty("java.version"));
  System.out.println("First call : " + doIt(500 * 1000 * 1000, 1)); // 14 secs
  System.out.println("Second call: " + doIt(500 * 1000 * 1000, 1)); // 1 sec
  System.out.println("Third call : " + doIt(500 * 1000 * 1000, 1)); // 0.4 secs
}

private static String doIt(final long iterations, long testValue) {
    Object lock = new Object();
    long start = System.currentTimeMillis();
    for (int i = 0; i < iterations; i++) {
        synchronized (lock) {
            testValue++;
        }
    }
    long stop = System.currentTimeMillis();
    return (stop - start) + " ms, result = " + testValue;
}

这些结果很难解释,我认为只有JVM工程师可以帮助解决问题。

答案 3 :(得分:1)

记住,两者都非常快;我们在这里谈论锁定读写解锁的50个CPU周期。

在Java中,我将其与无竞争情况下的模拟impl进行了比较

volatile int waitingList=0;

    AtomicInteger x = new AtomicInteger(0);
    for (int i = 0; i < iterations; i++)
    {
        while( ! x.compareAndSet(0, 1) )
            ;

        testValue++;

        if(waitingList!=0)
            ;
        x.set(0);
    }

这个裸骨模拟比synchronized版本快一点,所用时间是15/17。

这表明在你的测试用例中,Java并没有做疯狂的优化,它诚实地为每次迭代做了lock-read-update-unlock。然而,Java的impl与裸骨的速度一样快;它不能更快​​。

虽然C#的impl也接近最小值,但它显然比Java做了一两件事。我不熟悉C#,但这可能表明一些语义差异,所以C#必须做一些额外的事情。

答案 4 :(得分:0)

几年前,当我研究Java中的锁/同步成本时,我最终提出了一个很大的问题:锁定如何影响其他线程访问任何类型内存的整体性能。可能受影响的是CPU缓存,尤其是在多处理器计算机上 - 并且取决于特定CPU架构如何处理缓存同步。我相信现代单CPU架构不会影响整体性能,但我不确定。

无论如何,如果有疑问,特别是当多进程计算机可能用于托管软件时,可能值得在几个操作中锁定更高级别。

答案 5 :(得分:0)

Java JIT将优化同步,因为锁对象是线程本地的(即它被限制在线程的堆栈中并且从不共享),因此永远不能从另一个线程同步。我不确定.NET JIT是否会这样做。

请参阅this very informative article,尤其是关于锁定省略的部分。