DotNet vs Java: Kiểu Dữ Liệu Có Cấu Trúc
C# vs Java và .NET vs JVM là những cuộc tranh luận không bao giờ kết thúc. Mỗi hệ sinh thái có những lợi thế riêng, tính năng riêng biệt. Nhưng ở trong một số hoàn cảnh nhất định, .NET có một thứ ưu việt hơn JVM, đó chính là kiểu dữ liệu cấu trúc (struct)
Java Test case
Tạo một class đơn giản với 2 thuộc tính kiểu integer:
private static class Coords{
public int x;
public int y;
public Coords(int x, int y) {
this.x = x;
this.y = y;
}
}
Sau đó tạo test case với JMH (một framework dùng để stress test trong Java)
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void testLoopAndGetData(BenchMarkState state, Blackhole bh){
Coords value;
for(int i = 0; i < state.SIZE;i++) {
value = state.data[i];
bh.consume(value.x);
bh.consume(value.y);
}
}
@State(Scope.Benchmark)
public static class BenchMarkState {
@Setup
public void init() {
Random r = new Random();
for(int i = 0; i < SIZE; i++) {
int nextInt = r.nextInt();
data[i] = new Coords(nextInt, nextInt);
}
}
public final int SIZE = 5000000;
public Coords[] data = new Coords[SIZE];
}
Với test case trên, Java cần tới 20ms để hoàn thành một lần xử lý:
C# test case
Tôi sử dụng .NET CORE trên Ubuntu 16.04 LTS và dùng MonoDevelop để biên soạn code C#. Một struct giống class bên trên của Java:
public struct Coords
{
public int x, y;
public Coords(int p1, int p2)
{
x = p1;
y = p2;
}
}
Sau đó tôi sử dụng BenchmarkDotNet để viết test case:
[CoreJob]
[RPlotExporter, RankColumn]
public class BenchmarkStruct
{
private Coords[] data;
public int N = 5000000;
[GlobalSetup]
public void Setup()
{
Random r= new Random(42);
data = new Coords[N];
for (int i = 0; i < N; i++)
{
data[i] = new Coords(r.Next(), r.Next());
}
}
[Benchmark]
public int Loop() {
int a = 0, b = 0;
for(int i = 0; i < N; i++) {
a = data[i].x;
b = data[i].y;
}
return a & b;
}
}
Kết quả cho thấy .NET có hiệu năng tốt hơn hẳn Java, chỉ mất 3.167ms cho một tác vụ:
| Method | Mean | Error | StdDev | Rank |
|------- |---------:|----------:|----------:|-----:|
| Loop | 3.167 ms | 0.0473 ms | 0.0420 ms | 1 |
Một cách tiếp cận khác của Java
Thư viện Junion của Java cố gắng "deliver struct types for Java programming language". Giống như được mô tả trên trang chủ, kiểu dữ liệu cấu trúc của Junion có thể giảm dung lượng bộ nhớ nếu so sánh với class thông thường của Java. Đây là một cải tiến rất hữu ích khi bạn có hàng triệu objects, nhưng tôi tò mò không biết Junion có thể nhanh bằng struct của C#?
Khai báo một struct với Junion khá đơn giản:
@Struct
private static class CoordsStruct{
public int x;
public int y;
}
Test case cho Junion
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void testJunion(BenchMarkState state, Blackhole bh){
CoordsStruct value;
for(int i = 0; i < state.SIZE;i++) {
value = state.dataStruct[i];
bh.consume(value.x);
bh.consume(value.y);
}
}
@State(Scope.Benchmark)
public static class BenchMarkState {
@Setup
public void init() {
Random r = new Random();
for(int i = 0; i < SIZE; i++) {
int nextInt = r.nextInt();
dataStruct[i] = new CoordsStruct();
dataStruct[i].x = nextInt;
dataStruct[i].y = nextInt;
}
}
public final int SIZE = 5000000;
public CoordsStruct[] dataStruct = new CoordsStruct[SIZE];
}
Kết quả giống như dự đoán, Junion có thể giảm dung lượng bộ nhớ nhưng không thể đạt được hiệu năng tương tự C#.
Kết luận
Trong C#, kiểu dữ liệu cấu trúc là một ValueType, và nó được cấp phát ở vùng nhớ stack của chương trình. Vì thế, khi chúng ta khai báo một mảng của kiểu struct, .NET có thể cấp phát một vùng nhớ chứa toàn bộ dữ liệu và các phần tử trong mảng được sắp xếp liên tiếp trong bộ nhớ. Hệ quả là khi chúng ta duyệt từng phần tử của mảng, CPU có thể làm việc rất hiệu quả với cache line và có hiệu năng rất cao.
Đối với Java thì hoàn toàn khác, một mảng của objects thực chất chỉ lưu trữ các con trỏ (hoặc có thể gọi là tham chiếu, địa chỉ trong bộ nhớ của object). Mọi truy xuất từ mảng đều yêu cầu JVM đọc dữ liệu ngẫu nhiên từ bộ nhớ (hoặc L3 cache của CPU nếu không bị cache miss). Việc truy xuất dữ liệu như vây không phù hợp với kiến trúc của CPU và bộ nhớ, làm giảm hiệu năng.
Nếu bạn muốn biết rõ hơn về cơ chế hoạt động của Cache line thì có thể tham khảo Cách truy cập Ram hiệu quả.