Java 11 和 Java 12 分别与 .NET Core 2.2 和 .NET Core 3.0 (preview 6) 的性能对比测试
文章目录
测试背景
网上大多数的 Java 和 .NET Core 性能测试不是版本太老就是把 .NET Core 使用 Debug 模式编译运行和别的语言的生产模式进行比较(dotnet build和dotnet run均为Debug模式,如果需要指定生产模式需要加参数 “-c Release”)。
这一次我们均采用生产模式评测一下两个语言的运算性能,来一次公正的运算性能比较。
另外,评论有人补充说 Windows 下测试毫无意义,因此我追加了 Java 12 和 .NET Core 2.2 在 Linux x64 下的测试。
我们使用FFT进行测试,FFT算法涉及到大量的复杂运算,可以体现一个平台的运算性能综合能力。
测试环境1
系统:Windows 10 x64 1809
CPU:Intel® Core™ i7-7660U @ 2.50GHz
内存:8GB
Java SE Development Kit 版本:11.0.2
.NET Core SDK 版本:2.2.103
测试环境2
系统:Ubuntu x64 18.04.2
CPU:4 核心,Intel® Xeon® CPU E5-2667 v4 @ 3.20GHz
内存:8GB
Java SE Development Kit 版本:12
.NET Core SDK 版本:2.2.105
以上的 SDK 版本均为同一时间下 .NET Core 和 Java 所发布了的最新的稳定版 SDK。由于测试环境 1 所执行测试的时间比 2 要早了几个月,因此测试环境 1 版本均比 2 的旧一些。
测试环境3
系统:Ubuntu x64 18.04.2
CPU:4 核心,Intel® Xeon® CPU E5-2667 v4 @ 3.20GHz
内存:8GB
Java SE Development Kit 版本:12.0.1
.NET Core SDK 版本:3.0.100-preview6-012228
测试环境3采用同一时间点的最新 SDK,无论稳定版还是预发布版本
测试方法
对含1048576个复数的序列进行 FFT 运算,公平起见,两边采用相同代码,并且均使用生产模式编译运行,时间包含随机复数的生成时间。
测试代码
C#
Program.cs
using System;
using System.Diagnostics;
using System.Numerics;
namespace Fft
{
class Program
{
static void Warmup()
{
var random = new Random();
int N = 1048576;
Complex[] x = new Complex[N];
// original data
for (int i = 0; i < N; i++)
{
x[i] = new Complex(i, 0);
x[i] = new Complex(-2 * random.Next() + 1, 0);
}
// FFT of original data
Complex[] y = FFTImpl.fft(x);
// take inverse FFT
Complex[] z = FFTImpl.ifft(y);
// circular convolution of x with itself
Complex[] c = FFTImpl.cconvolve(x, x);
// linear convolution of x with itself
Complex[] d = FFTImpl.convolve(x, x);
}
static void Main(string[] args)
{
for (int i = 0; i < 10; i++) Warmup(); // warming up
for (int j = 0; j < 5; j++)
{
var st = new Stopwatch();
st.Start();
var random = new Random();
int N = 1048576;
Complex[] x = new Complex[N];
// original data
for (int i = 0; i < N; i++)
{
x[i] = new Complex(i, 0);
x[i] = new Complex(-2 * random.Next() + 1, 0);
}
// FFT of original data
Complex[] y = FFTImpl.fft(x);
// take inverse FFT
Complex[] z = FFTImpl.ifft(y);
// circular convolution of x with itself
Complex[] c = FFTImpl.cconvolve(x, x);
// linear convolution of x with itself
Complex[] d = FFTImpl.convolve(x, x);
st.Stop();
Console.WriteLine(st.ElapsedMilliseconds);
}
}
}
}
FFTImpl.cs
using System;
using System.Numerics;
namespace Fft
{
public class FFTImpl
{
// compute the FFT of x[], assuming its length is a power of 2
public static Complex[] fft(Complex[] x)
{
int N = x.Length;
// base case
if (N == 1) return new Complex[] { x[0] };
// radix 2 Cooley-Tukey FFT
if (N % 2 != 0)
{
throw new Exception("N is not a power of 2");
}
// fft of even terms
Complex[] even = new Complex[N / 2];
for (int k = 0; k < N / 2; k++)
{
even[k] = x[2 * k];
}
Complex[] q = FFTImpl.fft(even);
// fft of odd terms
Complex[] odd = even; // reuse the array
for (int k = 0; k < N / 2; k++)
{
odd[k] = x[2 * k + 1];
}
Complex[] r = FFTImpl.fft(odd);
// combine
Complex[] y = new Complex[N];
for (int k = 0; k < N / 2; k++)
{
double kth = -2 * k * Math.PI / N;
Complex wk = new Complex(Math.Cos(kth), Math.Sin(kth));
y[k] = q[k] + (wk * r[k]);
y[k + N / 2] = q[k] - (wk * r[k]);
}
return y;
}
// compute the inverse FFT of x[], assuming its length is a power of 2
public static Complex[] ifft(Complex[] x)
{
int N = x.Length;
Complex[] y = new Complex[N];
// take conjugate
for (int i = 0; i < N; i++)
{
y[i] = Complex.Conjugate(x[i]);
}
// compute forward FFT
y = FFTImpl.fft(y);
// take conjugate again
for (int i = 0; i < N; i++)
{
y[i] = Complex.Conjugate(y[i]);
}
// divide by N
for (int i = 0; i < N; i++)
{
y[i] = y[i] * new Complex(1.0 / N, 0);
}
return y;
}
// compute the circular convolution of x and y
public static Complex[] cconvolve(Complex[] x, Complex[] y)
{
// should probably pad x and y with 0s so that they have same length
// and are powers of 2
if (x.Length != y.Length)
{
throw new Exception("Dimensions don't agree");
}
int N = x.Length;
// compute FFT of each sequence
Complex[] a = FFTImpl.fft(x);
Complex[] b = FFTImpl.fft(y);
// point-wise multiply
Complex[] c = new Complex[N];
for (int i = 0; i < N; i++)
{
c[i] = a[i] * b[i];
}
// compute inverse FFT
return FFTImpl.ifft(c);
}
// compute the linear convolution of x and y
public static Complex[] convolve(Complex[] x, Complex[] y)
{
Complex ZERO = new Complex(0, 0);
Complex[] a = new Complex[2 * x.Length];
for (int i = 0; i < x.Length; i++) a[i] = x[i];
for (int i = x.Length; i < 2 * x.Length; i++) a[i] = ZERO;
Complex[] b = new Complex[2 * y.Length];
for (int i = 0; i < y.Length; i++) b[i] = y[i];
for (int i = y.Length; i < 2 * y.Length; i++) b[i] = ZERO;
return FFTImpl.cconvolve(a, b);
}
}
}
Java(复数类使用 Complex.java)
Main.java:
package com.company;
public class Main {
public static void warmup() {
int N = 1048576;
Complex[] x = new Complex[N];
// original data
for (int i = 0; i < N; i++) {
x[i] = new Complex(i, 0);
x[i] = new Complex(-2*Math.random() + 1, 0);
}
// FFT of original data
Complex[] y = FFT.fft(x);
// take inverse FFT
Complex[] z = FFT.ifft(y);
// circular convolution of x with itself
Complex[] c = FFT.cconvolve(x, x);
// linear convolution of x with itself
Complex[] d = FFT.convolve(x, x);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) warmup(); // warming up
for (int j = 0; j < 5; j++) {
long begintime = System.currentTimeMillis();
int N = 1048576;
Complex[] x = new Complex[N];
// original data
for (int i = 0; i < N; i++) {
x[i] = new Complex(i, 0);
x[i] = new Complex(-2*Math.random() + 1, 0);
}
// FFT of original data
Complex[] y = FFT.fft(x);
// take inverse FFT
Complex[] z = FFT.ifft(y);
// circular convolution of x with itself
Complex[] c = FFT.cconvolve(x, x);
// linear convolution of x with itself
Complex[] d = FFT.convolve(x, x);
long endtime = System.currentTimeMillis();
System.out.println(endtime - begintime);
}
}
}
FFT.java:
package com.company;
public class FFT {
// compute the FFT of x[], assuming its length is a power of 2
public static Complex[] fft(Complex[] x) {
int N = x.length;
// base case
if (N == 1) return new Complex[]{x[0]};
// radix 2 Cooley-Tukey FFT
if (N % 2 != 0) {
throw new RuntimeException("N is not a power of 2");
}
// fft of even terms
Complex[] even = new Complex[N / 2];
for (int k = 0; k < N / 2; k++) {
even[k] = x[2 * k];
}
Complex[] q = fft(even);
// fft of odd terms
Complex[] odd = even; // reuse the array
for (int k = 0; k < N / 2; k++) {
odd[k] = x[2 * k + 1];
}
Complex[] r = fft(odd);
// combine
Complex[] y = new Complex[N];
for (int k = 0; k < N / 2; k++) {
double kth = -2 * k * Math.PI / N;
Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
y[k] = q[k].add(wk.mult(r[k]));
y[k + N / 2] = q[k].minus(wk.mult(r[k]));
}
return y;
}
// compute the inverse FFT of x[], assuming its length is a power of 2
public static Complex[] ifft(Complex[] x) {
int N = x.length;
Complex[] y = new Complex[N];
// take conjugate
for (int i = 0; i < N; i++) {
y[i] = x[i].conj();
}
// compute forward FFT
y = fft(y);
// take conjugate again
for (int i = 0; i < N; i++) {
y[i] = y[i].conj();
}
// divide by N
for (int i = 0; i < N; i++) {
y[i] = y[i].mult(new Complex(1.0 / N, 0));
}
return y;
}
// compute the circular convolution of x and y
public static Complex[] cconvolve(Complex[] x, Complex[] y) {
// should probably pad x and y with 0s so that they have same length
// and are powers of 2
if (x.length != y.length) {
throw new RuntimeException("Dimensions don't agree");
}
int N = x.length;
// compute FFT of each sequence
Complex[] a = fft(x);
Complex[] b = fft(y);
// point-wise multiply
Complex[] c = new Complex[N];
for (int i = 0; i < N; i++) {
c[i] = a[i].mult(b[i]);
}
// compute inverse FFT
return ifft(c);
}
// compute the linear convolution of x and y
public static Complex[] convolve(Complex[] x, Complex[] y) {
Complex ZERO = new Complex(0, 0);
Complex[] a = new Complex[2 * x.length];
for (int i = 0; i < x.length; i++) a[i] = x[i];
for (int i = x.length; i < 2 * x.length; i++) a[i] = ZERO;
Complex[] b = new Complex[2 * y.length];
for (int i = 0; i < y.length; i++) b[i] = y[i];
for (int i = y.length; i < 2 * y.length; i++) b[i] = ZERO;
return cconvolve(a, b);
}
}
进行评测
分别进行生产模式编译:
dotnet build -c Release
javac com\company\*.java
运行
dotnet ./bin/Release/netcoreapp2.2/Fft.dll # .NET Core 2.2
dotnet ./bin/Release/netcoreapp3.0/Fft.dll # .NET Core 3.0
java com.company.Main
测试结果 – 测试环境1
单位:ms
.NET Core 2.2.103 (无预热)
4485
4131
3930
3942
3902
取最短时间:3902ms
Java 11.0.2(无预热)
12924
10158
9801
12679
12218
取最短时间:9801ms
测试结果 – 测试环境2
单位:ms
.NET Core 2.2.105(无预热)
7124
6996
7043
7092
7038
取最短时间:6996ms
Java 12(无预热)
13779
13704
13769
13441
13626
取最短时间:13441ms
测试结果 – 测试环境3
单位:ms
.NET Core 3.0.100-preview6-012228(有预热)
6533
6606
6538
6539
6670
取最短时间:6533ms
Java 12.0.1(有预热)
9174
8541
8928
8981
9121
取最短时间:8541ms
结论
注意:只有第三个环境下的测试跑了预热过程,前两个环境之所以没有做是因为忘了。。。然后现在也找不到之前的测试环境以及下载不到之前的旧版本了所以就不纠正了。
此测试中:
- 在 Windows 平台上,双方均没有预热的情况下,.NET Core 2.2.103 的性能差不多是 Java 11.0.2 的 3 倍
- 在 Linux 平台上,双方均没有预热的情况下,.NET Core 2.2.105 的性能差不多是 Java 12 的 2 倍。
- 在 Linux 平台上,双方均预热的情况下,.NET Core 3.0 (preview 6) 的性能差不多是 Java 12.0.1 的 1.5 倍。
- Java 预热与否前后结果差异较大,.NET Core 预热提升则较小。然而 Java 预热后的性能比 .NET Core 不预热的性能还要低一些。
- 由于这些测试所用机器配置不一致,因此在这里不做纵向比较,只进行横向比较,但是测试环境 2 和 3 具有一定的纵向比较价值。
有些人用着几年前的观念看待现在的新 .NET Core 平台,现在的 .NET Core 跨平台、开源、性能经过大幅度的改进,可以说只是一个长着像 .NET Framework 但又完全不是 .NET Framework 的一个全新的优秀平台,希望抱有旧观点的人能够重新审视这个平台,相信你会彻底推翻你以前对 .NET 的看法。