Không Được Dùng những cấu trúc có sẵn cho dữ liệu Số

Đây là một nhu cầu cơ bản: bạn đọc từ file hoặc một api danh sách các số nguyên.

Bạn cần lưu nó vào bộ nhớ rồi làm abc xyz tiếp.

Quá dễ nhỉ, trước đây, với Java, mình hay làm thế này:

import java.util.*;
...
final int size = 1_000_000; // giả sử list size là 1 triệu
final List<Integer> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
    int value = randomInt();
    list.add(value);
}

Nhưng rồi mình nhận ra, một Data Engineer không bao giờ làm thế.

Vì sao vậy?

Thử ước lượng, một số nguyên là 4 bytes, chúng ta lưu 1 triệu số => Mất 4MB Ram để lưu trữ.

Đơn giản vậy thì đã tốt, hãy thử đo memory thực tế dòng code này chiếm dụng:

final long bytes = MemoryMeasurer.measureBytes(list); // đếm số bytes
System.out.println(bytes);

Kết quả: 20_000_000. Tức là 20M Ram.

Thử với một mapping giữa 2 số nguyên 4 bytes và 8 bytes, cứ tưởng sẽ mất cỡ 12M Ram, nhưng có vẻ không phải.

final Map<Long, Integer> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
    long key = (long) randomInt();
    int value = randomInt();
    map.put(key, value);
}
final long bytes = MemoryMeasurer.measureBytes(map);
System.out.println(bytes);

Kết quả: 80_000_000. Tức là 80M Ram.

Chúng ta mất hơn 5 lần bộ nhớ so với tính toán. Có nghĩa là nếu làm big data, chúng ta phải mất 5 máy tính thay vì 1 so với dự tính?

Vì sao ra nông nỗi?

Ban đầu mình đổ tội cho ngôn ngữ Java, nhưng mình nhầm, ngoài các ngôn ngữ bậc thấp, C/C++ hoặc ngôn ngữ chuyên biệt như Scala, thì Python, Php, JS, ... tất cả đều như vậy.

Phải có gì đó không ổn trong cách thiết kế của các ngôn ngữ này.

Giải pháp

Rất may là mỗi ngôn ngữ có vẻ như có giải pháp cho việc này.

Mình code Java, nên hiện nay mình dùng thư viện fast util chứ không bao giờ xài các thư viện mặc định của Java.

Hãy xem sự khác biệt nhé, đoạn code sau mình lưu cùng một lượng dữ liệu vào thư viện mặc định của java và fast util, và đo lượng memory mỗi thư viện sẽ chiếm dụng:

Java ArrayList vs fast-util IntArrayList

    public static void testList() {
        final int size = 1_000_000;
        System.out.println("Test memory java DEFAULT LIST and FAST LIST, test size = " + size);
        final List<Integer> list = new ArrayList<>(size);
        final List<Integer> fastList = new IntArrayList(size);
        for (int i = 0; i < size; i++) {
            int value = randomInt();
            list.add(value);
            fastList.add(value);
        }
        final long listBytes = MemoryMeasurer.measureBytes(list) / 1000;
        final long fastListBytes = MemoryMeasurer.measureBytes(fastList) / 1000;
        System.out.println("Default list = " + listBytes + " KB");
        System.out.println("Fast list = " + fastListBytes + " KB (" + ((100.0 * fastListBytes) / listBytes) + "%)");
    }

Kết quả: IntArrayList chiếm dụng memory chỉ bằng 1/5 thư viện mặc định của Java.

Test memory java DEFAULT LIST and FAST LIST, test size = 1000000
Default list = 20000 KB
Fast list = 4000 KB (20.0%)

Java HashMap vs fast-util OpenHashMap

    public static void testNativeMap() {
        final int size = 1_000_000;
        System.out.println("Test memory java DEFAULT MAP and FAST MAP with NATIVE TYPE, test size = " + size);
        final Map<Long, Integer> map = new HashMap<>(size);
        final Map<Long, Integer> fastMap = new Long2IntOpenHashMap(size);
        for (int i = 0; i < size; i++) {
            long key = (long) randomInt();
            int value = randomInt();
            map.put(key, value);
            fastMap.put(key, value);
        }
        final long defaultBytes = MemoryMeasurer.measureBytes(map) / 1000;
        final long fastBytes = MemoryMeasurer.measureBytes(fastMap) / 1000;
        System.out.println("Default map = " + defaultBytes + " KB");
        System.out.println("Fast map = " + fastBytes + " KB (" + (100.0 * fastBytes / defaultBytes) + "%)");
    }

Kết quả: FastUtil chiếm dụng memory bằng 1/3 so với thư viện mặc định Java

Test memory java DEFAULT MAP and FAST MAP with NATIVE TYPE, test size = 1000000
Default map = 80380 KB
Fast map = 27263 KB (33.91764120427967%)

Kết luận

Vậy là, nếu làm Data Engineer, bạn đừng bao giờ dùng những thư viện mặc định để lưu trữ dữ liệu trong RAM nhé. Hãy tìm những thư viện chuyên dụng như kiểu fast-util cho Java.

Nếu không, chi phí server/máy tính của bạn sẽ đội lên theo cấp số nhân.

Tại BeeCost.Com, nhờ sử dụng FastUtil mà với 1 máy chủ dữ liệu giá $25/tháng, chúng mình theo dõi 100 triệu sản phẩm online, xử lý 2 tỷ giá tiền sản phẩm mỗi ngày, giúp người dùng mua sắm tiết kiệm hơn.

Ít tài nguyên vậy liệu bạn có nghi ngờ về tốc độ xử lý của BeeCost??

Phải thử đi bạn mới thấy sự khác biệt nhé !!

Thực hành ngay.

Các bạn hãy sử dụng nguyên tắc này để thực hành lại các ví dụ của mình xem sao nhé.

Đối với các bạn sử dụng Java, thì đây là hướng dẫn để các bạn có code chạy lại ví dụ mình đã chạy. Trong bài này mình đưa ra 2 ví dụ về List và Map, project code mở của chúng mình còn một ví dụ thứ 3 để bạn biết cách sử dụng thành thạo fast-util.

Step 1: Download code

Link hướng dẫn

Step 2: Build code

Link hướng dẫn

Step 3: Trong thư mục code, hãy build lại và chạy class FastUtilExample:

Ví dụ đây là lệnh tại máy tính của mình:

cd ~/workspace/data_engineering/
bin/java.sh -javaagent:lib/object-explorer.jar de.FastUtilExample

Output:

Test memory java DEFAULT LIST and FAST LIST, test size = 1000000
Default list = 20000 KB
Fast list = 4000 KB (20.0%)

- - - - - - - - 
Test memory java DEFAULT MAP and FAST MAP with NATIVE TYPE, test size = 1000000
Default map = 80380 KB
Fast map = 27263 KB (33.91764120427967%)

Chúc các bạn sử dụng blog này hiệu quả. Đừng coi thường những dòng code cơ sở này nhé. Nên nhớ là BeeCost.Com, nhờ sử dụng FastUtil mà với 1 máy chủ dữ liệu giá $25/tháng, chúng mình theo dõi 100 triệu sản phẩm online, xử lý 2 tỷ giá tiền sản phẩm mỗi ngày.

Mình còn nợ các bạn

Mình vẫn chưa trả lời vấn đề vì sao mà các thư viện mặc định lại chiếm dụng memory nhiều tới vậy. Bằng kinh nghiệm của mình, đây là một một trong những rào cản của một Data Engineering khi phải tiếp xúc với lượng dữ liệu lớn.

Bài tiếp theo của BeeCost Engineering Blog sẽ đào sâu vấn đề này.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Tung Ha

1 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
24 2
Dẫn nhập Cây nhị phân là một cấu trúc dữ liệu hết sức quen thuộc với chúng ta. Có rất nhiều nghiên cứu và các thuật toán xoay quanh cấu trúc dữ liệ...
Lê Phong Vũ viết hơn 1 năm trước
24 2
White
55 23
Luận về comment code (Phong cách kiếm hiệp) Comment code luôn là vấn đề gây tranh cãi sứt đầu mẻ trán trong giới võ lâm. Xưa kia, thuở còn mài đít...
Huy Hoàng Phạm viết gần 4 năm trước
55 23
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
1 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!