对 Java、Groovy、Jython 和 Python 进行基准测试

use*_*996 5 python java groovy jython jruby

我正在尝试对 PI (3.14159) 投掷飞镖的蒙特卡罗计算进行基准测试。我已经用 Java、Groovy、BeanShell、Julia、Jython 和 Python(用 C 实现的 Python2)实现了我的代码。

这是我的原始 Java 代码“MonteCarloPI.java”:

import java.util.Random; 

public class MonteCarloPI {
     public static void main(String[] args)
       {
         int nThrows = 0;
         int nSuccess = 0;
         double x, y;
         long then = System.nanoTime();
         int events=(int)1e8;
         Random r = new Random(); 
         for (int i = 0; i < events; i++) {
            x = r.nextFloat();      // Throw a dart
            y = r.nextFloat();
            nThrows++;
            if ( x*x + y*y <= 1 )  nSuccess++;
       }
 double itime = ((System.nanoTime() - then)/1e9);
 System.out.println("Time for calculations (sec): " + itime+"\n");
 System.out.println("Pi = " + 4*(double)nSuccess/(double)nThrows +"\n");
      }
}
Run Code Online (Sandbox Code Playgroud)

这是我在“MonteCarloPI.groovy”文件中的 Groovy 代码:

import java.util.Random

int nThrows = 0
int nSuccess = 0
double x, y
long then = System.nanoTime()
int events=1e8
r = new Random()
for (int i = 0; i < events; i++) {
            x = r.nextFloat()      // Throw a dart
            y = r.nextFloat()
            nThrows++
            if ( x*x + y*y <= 1 )  nSuccess++
}
itime = ((System.nanoTime() - then)/1e9)
System.out.println("Time for calculations (sec): " + itime+"\n")
System.out.println("Pi = " + 4*(double)nSuccess/(double)nThrows +"\n")
       
Run Code Online (Sandbox Code Playgroud)

或者,我已经删除了诸如“float”和“int”之类的定义(即松散类型)。这会使用“松散”类型检查性能。

我已将“MonteCarloPI.groovy”重命名为 BeanShell 脚本文件“MonteCarloPI.bsh”(BeanShell 的语法与 Groovy 非常相似)

在标准 Python 语言的情况下,代码“MonteCarloPI_CPython.py”如下所示:

import random,time

nThrows,nSuccess = 0,0
then = time.time()
events=int(1e8)
for i in xrange(events):
   x,y = random.random(),random.random();   # Throw a dart                   
   nThrows +=1
   if ( x*x + y*y <= 1 ):  nSuccess+=1
itime = time.time() - then
print ("Time: ",itime,"sec Pi = ",4*nSuccess/float(nThrows))
Run Code Online (Sandbox Code Playgroud)

此代码在 CPython 2.7.18(用 C 实现的 Python)或 Jython 2.7.2(Java 实现)中执行。对于 Python 3.8.3(“Python3”),将“xrange”替换为“range”。

我也在 JRuby (MonteCarloPI.rb) 中实现了相同的算法:

require "java"
java_import java.lang.System;
java_import java.util.Random;

nThrows = 0; nSuccess = 0;
xthen = System.nanoTime();
events=1e8;
r = Random.new();
for i  in 0 .. events do
  x = r.nextFloat();      #  Throw a dart
  y = r.nextFloat();
  nThrows +=1
   if ( x*x + y*y <= 1 )
                nSuccess += 1
  end
end
itime = (System.nanoTime() - xthen)/1e9;
xpi=(4.0*nSuccess)/nThrows
puts "Time for calculations (sec):  #{itime}"
puts "Pi = #{xpi}"
Run Code Online (Sandbox Code Playgroud)

这里使用 Julia 的代码:

using Random
nThrows = 0
nSuccess = 0
events=1e8
then = time()
for j in 0:events
        x = rand();      #  Throw a dart
        y = rand();
        global  nThrows += 1;
        if  x*x + y*y <= 1
                 global nSuccess += 1;
        end
end
itime = time() - then
println( "Time for calculations (sec):", itime, " sec")
println( "Pi = ", 4.0*nSuccess/float(nThrows) )
Run Code Online (Sandbox Code Playgroud)

我在DataMelt 中运行了“MonteCarloPI.java”、“MonteCarloPI.groovy”、“MonteCarloPI.py”、“MonteCarloPI.bsh”和 MonteCarloPI.rb编辑器中。julia 代码是使用本地安装的 julia-1.5.0/bin 处理的。

这是在 Intel(R) Core(TM) i5-4690K CPU @ 3.50GHz(ubuntu 20.04,8 GB 内存)上的基准测试结果,在运行 Groovy、Jython、BeanShell 代码时为 JDK9 分配了 2048 MB:

Java   code:   1.7 sec Pi = 3.14176584  -> executed in DataMelt/JDK9
Groovy code:   2.1 sec Pi = 3.14144832  -> executed in DataMelt/JDK9
Groovy code:   18 sec Pi = 3.14141132  -> same but with "loose" types 
Julia code:    15 sec Pi = 3.14156104  -> executed in julia-1.5.0
Python code:   24 sec Pi = 3.14188036  -> executed in CPython 2.7.18
Python code:   30 sec Pi = 3.14188230  -> executed in CPython 3.2.8
Python code:    3 sec Pi = 3.14188036  -> executed using PyPy
Jython code:   24 sec Pi = 3.14187860  -> executed in DataMelt/JDK9
JRuby  code:   25 sec Pi = 3.14187860  -> executed in DataMelt/JDK9
BeanShell code: takes forever?!       -> executed in DataMelt/JDK9
Run Code Online (Sandbox Code Playgroud)

如您所见,Java 和 Groovy 计算花费的时间大致相同(大约 2 秒)。使用 Groovy 中的松散类型,执行速度要慢 9 倍。Python 比 Java 和 Groovy 慢 12 倍。Python3 甚至更慢。JRuby 和 Python 一样慢。PyPy 相当快(但比 Java/Groovy 慢)。但是 BeanShell 根本不能做这个计算(需要永远,而且我的电脑永远不会停止处理这个文件)。

这有什么智慧吗?

小智 2

干得好。你的比较很有趣。作为一名 Python 开发人员,我想添加一些关于 Python 的额外观点。

我认为它速度较慢主要是因为动态类型。另一个原因是您正在计算标量值(即使用 for 循环并一次计算一个数字)。Python 的优点之一是使用 NumPy 库进行向量计算(这允许同时计算多个数字)。所以,这是我的算法的实现。注意:我使用的是python 3.6。

import numpy as np
import time

start = time.time()

events = int(1e8)
nThrows, nSuccess = 0, 0

x, y = np.random.uniform(size=(2, events))
nSuccess = (x*x + y*y <= 1).sum()
nThrows = events
pi = 4*nSuccess/float(nThrows)

stop = time.time()
print('Time: {}, Pi = {}'.format(stop-start, pi))
Run Code Online (Sandbox Code Playgroud)

以下是我的 i7 x64 计算机 (Windows 10) 上的基准测试结果:

Python (original code):      42.6s  Pi = 3.1414672
Python (my optimized code):  4.7s   Pi = 3.1417642
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,在我的计算机上运行的原始 python 代码比在你的计算机上运行的 python 代码慢。因此,优化版本可能比 Java 或 Groovy 更快。

希望这可以帮助。