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 的看法。