为什么这个Java代码比相同的C#代码快6倍?

2022-09-01 00:29:44

对于欧拉项目问题5,我有一些不同的解决方案,但是在这个特定的实现中,两种语言/平台之间的执行时间差引起了我的兴趣。我没有对编译器标志进行任何优化,只是普通的(通过命令行)和(通过Visual Studio)。javaccsc

这是Java代码。它在55ms内完成。

public class Problem005b
{
    public static void main(String[] args)
    {
        long begin = System.currentTimeMillis();
        int i = 20;
        while (true)
        {
            if (
                    (i % 19 == 0) &&
                    (i % 18 == 0) &&
                    (i % 17 == 0) &&
                    (i % 16 == 0) &&
                    (i % 15 == 0) &&
                    (i % 14 == 0) &&
                    (i % 13 == 0) &&
                    (i % 12 == 0) &&
                    (i % 11 == 0)
                )
            {
                break;
            }
            i += 20;
        }
        long end = System.currentTimeMillis();
        System.out.println(i);
        System.out.println(end-begin + "ms");
    }   
}

下面是相同的 C# 代码。它在320ms内完成

using System;

namespace ProjectEuler05
{
    class Problem005
    {
        static void Main(String[] args)
        {
            DateTime begin = DateTime.Now;
            int i = 20;
            while (true)
            {
                if (
                        (i % 19 == 0) &&
                        (i % 18 == 0) &&
                        (i % 17 == 0) &&
                        (i % 16 == 0) &&
                        (i % 15 == 0) &&
                        (i % 14 == 0) &&
                        (i % 13 == 0) &&
                        (i % 12 == 0) &&
                        (i % 11 == 0)
                    )
                    {
                        break;
                    }
                i += 20;
            }
            DateTime end = DateTime.Now;
            TimeSpan elapsed = end - begin;
            Console.WriteLine(i);
            Console.WriteLine(elapsed.TotalMilliseconds + "ms");
        }
    }
}

答案 1
  1. 若要计算代码执行的时间,应使用 StopWatch 类。
  2. 此外,您必须考虑JIT,运行时等,因此让测试运行足够的次数(如10,000,100,000次)并获得某种平均值。重要的是多次运行代码而不是程序。因此,编写一个方法,并在main方法中循环以获得测量值。
  3. 从程序集中删除所有调试内容,并允许代码在发布版本中独立运行

答案 2

有一些可能的优化。也许Java JIT正在执行它们,而CLR没有。

优化 #1:

(x % a == 0) && (x % b == 0) && ... && (x % z == 0)

等效于

(x % lcm(a, b, ... z) == 0)

因此,在您的示例中,比较链可以替换为

if (i % 232792560 == 0) break;

(但是当然,如果您已经计算了LCM,那么首先运行该程序就没有多大意义了!

优化#2

这也是等效的:

if (i % (14549535 * 16)) == 0 break;

if ((i % 16 == 0) && (i % 14549535 == 0)) break;

第一个除法可以用掩码替换,并与零进行比较:

if (((i & 15) == 0) && (i % 14549535 == 0)) break;

第二除法可以用模逆乘法代替:

final long LCM = 14549535;
final long INV_LCM = 8384559098224769503L; // == 14549535**-1 mod 2**64
final long MAX_QUOTIENT = Long.MAX_VALUE / LCM;
// ...
if (((i & 15) == 0) &&
    (0 <= (i>>4) * INV_LCM) &&
    ((i>>4) * INV_LCM < MAX_QUOTIENT)) {
    break;
}

JIT不太可能采用这种方式,但它并不像你想象的那么牵强 - 一些C编译器以这种方式实现指针减法。


推荐