运行速度最快的算法竞赛

时间:2009-11-14 19:29:53

标签: java algorithm jvm code-golf

我想参加像代码高尔夫比赛这样的比赛,但获胜者将拥有最快的算法,而不是最小的代码。

  • 衡量算法速度的一种公平方法是使用中立虚拟机,如Java的JVM。有没有一种简单的方法可以知道执行的JVM指令总数? (如果条目使用多个线程,那么JVM指令的总数将在所有线程中求和。)

例如,代码

public class simple {
    public static int main(String argc[]) {
        int i;

        i = 3;
        while (i > 0) {
            i--;
        }

    return 0;
    }
}

生成JVM代码

0:  iconst_3
1:  istore_1
2:  iload_1
3:  ifle    12
6:  iinc    1, -1
9:  goto    2
12: iconst_0
13: ireturn

并且需要运行18个JVM指令(如果我已经正确计算)。

  • 我希望人们能够在家里办理他们的参赛作品,看看评委会看到什么。显然,如果我将输入提供给程序,最快的解决方案是吐出记忆的预先计算的答案。有没有办法客观地让人们在家里运行程序而不是看到回忆的答案?

  • 还有哪些其他问题阻碍了非正式的“最快的代码竞争”的发生?

谢谢!

10 个答案:

答案 0 :(得分:5)

唯一公平的比较是在一个共同的硬件上最短的完成时间。完成一个程序的时间完全取决于硬件,否则在更多动力机器上花钱的重点是什么?

可以获得可重现结果的最接近的是报告相对速度,例如根据50%的时间运行的用户程序提供示例程序和报告。在一台PC上速度提高一倍的程序可能是另一台PC的两倍。

在uni,我们会提交针对“秘密”输入运行的分配,但是我们可以提交多次以纠正错误。我的第一次提交根本不起作用,但会记录所有输入。 ;)

编辑:更长的答案。

考虑以下计划

public class FibMain {
    public static void main(String... args) {
        {
            long start = System.nanoTime();
            System.out.println(iteration_fib(Integer.parseInt(args[0])));
            long time = System.nanoTime() - start;
            System.out.printf("Iteration took %,d us%n", time /  1000);
        }
        {
            long start = System.nanoTime();
            System.out.println(recursive_fib(Integer.parseInt(args[0])));
            long time = System.nanoTime() - start;
            System.out.printf("Recursion took %,d us%n", time /  1000);
        }
    }

    public static long iteration_fib(int n) {
        long t1 = 1;
        long t2 = 1;
        while (n-- > 2) {
            long t = t2;
            t2 += t1;
            t1 = t;
        }
        return t2;
    }

    public static long recursive_fib(int n) {
        if (n <= 2) return 1;
        return recursive_fib(n - 1) + recursive_fib(n - 2);
    }
}

如果你用javap -c查看生成的字节代码,你会看到

public static long iteration_fib(int);
  Code:
   0:   lconst_1
   1:   lstore_1
   2:   lconst_1
   3:   lstore_3
   4:   iload_0
   5:   iinc    0, -1
   8:   iconst_2
   9:   if_icmple       25
   12:  lload_3
   13:  lstore  5
   15:  lload_3
   16:  lload_1
   17:  ladd
   18:  lstore_3
   19:  lload   5
   21:  lstore_1
   22:  goto    4
   25:  lload_3
   26:  lreturn

public static long recursive_fib(int);
  Code:
   0:   iload_0
   1:   iconst_2
   2:   if_icmpgt       7
   5:   lconst_1
   6:   lreturn
   7:   iload_0
   8:   iconst_1
   9:   isub
   10:  invokestatic    #13; //Method recursive_fib:(I)J
   13:  iload_0
   14:  iconst_2
   15:  isub
   16:  invokestatic    #13; //Method recursive_fib:(I)J
   19:  ladd
   20:  lreturn

所以第一个例子比第二个例子长,所以你可能会怀疑第一个例子需要更长的时间。但是,如果“n”是一个有趣的大小,那么你就不正确了。

我在我的机器上运行了FibMain 44并获得了以下结果。

701408733
Iteration took 495 us
701408733
Recursion took 19,174,036 us

这是因为执行迭代所花费的时间与n(在这种情况下为44)成正比,因此它会线性增长,但递归所用的时间与结果成正比(在本例中为701408733)并且呈指数增长。

如果您尝试输入50作为输入,则第一次完成,第二次需要很长时间,我感到无聊等待。

答案 1 :(得分:1)

对于(1)为什么不只是执行流程的时间?设计这个难题,以便实际处理是目前最主要的时间方面,而不是流程启动,并花费多次迭代来获得平均值。

对于(2)提供样本输入,但使用替代输入进行现场比赛。

答案 2 :(得分:1)

对于(2),通常在编程竞赛中使用的解决方案(只有正确性计数)是提供少量有限数量的示例输入,但在判断系统上使用更全面的测试集。

对于(3),使用的JVM指令数量不一定是衡量速度的好方法。一些实现可能需要更长或更短的每个指令;我甚至还没有开始进行jitting和其他优化。

答案 3 :(得分:1)

您可能必须使用realtime JVM,以便您可以公平地控制垃圾收集器。 如果一个竞争者显示更长的运行时间仅仅因为垃圾收集器在运行期间被踢了,那将是不公平的。

答案 4 :(得分:1)

您可以使用多个在线工具进行竞争,例如SPOJ(这一个是免费的,支持Java)。 这样你就有了一台测量程序执行时间的参考机器。

答案 5 :(得分:0)

您可以实现一个自动编辑器类型的测试站点,人们可以在其中提交他们的代码并获得包含其性能结果的电子邮件,并可能指示最高速度结果。他们不会得到输入,但会得到官方JVM将产生的结果。为了防止滥用,修复类加载器以防止加载任何类型的传出连接类型的东西,并将性能测试器限制为每天每个地址提交一次,或者其他策略。

答案 6 :(得分:0)

唯一明智的衡量标准是某些真实硬件上的时间。编译器会优化时间,而不是执行指令的计数,因此计数指令会使许多优化失败,并使其中一些优化失败。不仅指令花费不同的时间量,而且由于例如指令而导致执行的延迟。内存访问可能会有很大差异。

答案 7 :(得分:0)

通过查看FastCode,您可以了解有关此类竞争所需的内容,尤其是在管理不同的硬件配置,以及基准测试等方面。验证程序。

答案 8 :(得分:0)

为什么不让VJM更进一步,实现一个完整的基于Linux的VM?时钟周期应该相同(我猜这取决于VM的实现方式)。

例如,你可以创建一个基于8088的VM,256K RAM和5兆的运行MINUX的磁盘空间。无论代码执行的“速度有多快”,无论8088实际上是在奔腾双核还是某些旧的Power PC上实现,CPU周期的数量都不会相同(相对于8088)。

一旦建立了虚拟硬件,语言选择就可以成为“最快算法”竞赛解决方案的一部分。

答案 9 :(得分:0)

我还认为计算指令数量是一个很好的衡量标准。

我看到的唯一缺点是,如果JVM指令过于强大。我不知道JVC,但有可能是对字符串的原生支持。附加字符串可以只产生一条指令。 (不要这么认为。)

我只使用普通的旧time命令。这可以测量执行时间,而不是实时消除几乎后台进程的所有影响。