Chưa chắc bạn đã trả lời đúng đâu: Độ dài tối đa của một mảng trong Node.js là bao nhiêu?

Nếu người phỏng vấn hỏi bạn câu hỏi trong tiêu đề, bạn sẽ trả lời như thế nào? Đây từng là câu tôi thường hỏi các ứng viên của mình bất kể level nào (cả senior, junior và fresher) ngày còn được ngồi phỏng vấn khi còn làm Techlead tại FPT.

Flex 1 chút tấm ảnh đi phỏng vấn 100 người trong 1 ngày khi còn trẻ

Câu hỏi này có vẻ đơn giản, chỉ cần kiểm tra tài liệu hoặc thử nghiệm để biết câu trả lời. Tuy nhiên, sự khác biệt giữa một senior và một người bình thường thường nằm ở những chi tiết này. Một senior có thể đi sâu vào một câu hỏi đơn giản và sau đó cung cấp một câu trả lời phi thường, và chúng ta có thể biết ứng viên hiểu sâu tới đâu trong thế giới Javascript rộng lớn này!

Level 1: Hãy tra đáp án nào

Oke, với những câu hỏi kiểu này thì lên thẳng Google hoặc ChatGPT là xong, chúng ta có thể tìm thấy ngay tài liệu MDN. Nó cho chúng ta biết độ dài tối đa của một mảng trong Javascript là 2³²-1

ChatGPT cũng ra kết quả tương tự:

Giờ chúng ta cùng test thử xem nhé (Cả Node.js và Chrome đều sử dụng V8 engine, vậy nên tôi sẽ test luôn bằng Chrome Dev Tool)

Kết quả kiểm tra hoàn toàn giống với tài liệu trên MDN, vậy có vẻ như câu hỏi dừng lại ở đây? Blog tới đây là hết được rồi.

Không, không, không.

Mảng mà chúng ta tạo ra trước đó thực ra là một mảng rỗng không có phần tử nào, ở trạng thái trống! Vậy nếu chúng ta thử điền dữ liệu vào mảng thì sao?

new Array(2**32 - 1).fill(0)

Nếu bạn thử điền dữ liệu vào, ứng dụng sẽ bị crash!

Điều này cho thấy một điều: trong Node.js, hay chính xác hơn là trong V8 engine, độ dài tối đa của một mảng thực tế ít hơn nhiều so với 2³²-1.

Thế con số cụ thể là bao nhiêu :)) Nếu bạn trả lời là 2³²-1 thì bạn tiêu rồi nhé 😉

Level 2: Lấy độ dài tối đa của mảng trong thí nghiệm thôi

Chúng ta sẽ tạo một array rỗng trước, sau đó add lần lượt từng phần từ vào như sau

Qua đó, chúng ta có thể thấy số phần tử của mảng đạt tới con số 112,813,858, sau đó chúng ta không thể add thêm vào nữa.

Vậy tại sao lại là 112,813,858?

Để tìm câu trả lời, chúng ta cần phải tìm hiểu source code của V8 engine – được viết bằng C++

Level 3: Kiểm tra V8 Source Code

Các mảng JavaScript trong V8 được triển khai bằng cách sử dụng các class C++ cụ thể. Tùy thuộc vào việc mảng chứa số nguyên, số thực, các class khác nhau sẽ được sử dụng (chủ yếu vì lý do hiệu suất). Class thường được sử dụng nhất là FixedArray.

FixedArray có một giới hạn độ dài tối đa:

// Maximally allowed length of a FixedArray.
static const int kMaxLength = (kMaxSize - kHeaderSize) / kTaggedSize;

Đó là một hằng số, nghĩa là nó có thể được tính toán tại thời điểm biên dịch:

kMaxSize là 1024 ** 3

kHeaderSize là 16

kTaggedSize là 8.

Giá trị cuối cùng được tính là 134,217,726.

Các mảng trong JavaScript có độ dài động, nhưng các cấu trúc dữ liệu C++ nền tảng được sử dụng thì không. Khi độ dài của một mảng JavaScript thay đổi, đối tượng C++ nền tảng có thể cần tăng hoặc giảm dung lượng lưu trữ của nó một cách động. Trên thực tế, nó không phải là thay đổi kích thước động mà là thay thế bằng một đối tượng mới.

Ở đây, chúng ta chỉ thảo luận về việc mở rộng. Tôi đã tìm thấy thuật toán tương ứng:

// Computes the new capacity when expanding the elements of a JSObject.
static uint32_t NewElementsCapacity(uint32_t old_capacity) {
  // (old_capacity + 50%) + kMinAddedElementsCapacity
  return old_capacity + (old_capacity >> 1) + kMinAddedElementsCapacity;
}

Thêm vào một vài Log show vào v8 code, và tôi có được output sau

Original capacity: 15045, expanded to: 22583
Original capacity: 22583, expanded to: 33892
Original capacity: 33892, expanded to: 50855
Original capacity: 50855, expanded to: 76300
Original capacity: 76300, expanded to: 114467
Original capacity: 114467, expanded to: 171718
Original capacity: 171718, expanded to: 257594
Original capacity: 257594, expanded to: 386408
Original capacity: 386408, expanded to: 579629
Original capacity: 579629, expanded to: 869461
Original capacity: 869461, expanded to: 1304209
Original capacity: 1304209, expanded to: 1956331
Original capacity: 1956331, expanded to: 2934514
Original capacity: 2934514, expanded to: 4401788
Original capacity: 4401788, expanded to: 6602699
Original capacity: 6602699, expanded to: 9904066
Original capacity: 9904066, expanded to: 14856116
Original capacity: 14856116, expanded to: 22284191
Original capacity: 22284191, expanded to: 33426304
Original capacity: 33426304, expanded to: 50139473
Original capacity: 50139473, expanded to: 75209227
Original capacity: 75209227, expanded to: 112813858
Original capacity: 112813858, expanded to: 169220804

Với 169220804 lớn hơn 134217726, phần mở rộng gặp lỗi:

Uncaught RangeError: Invalid array length

Trong mỗi lần thực hiện thao tác push, khi dung lượng không đủ, nó sẽ mở rộng lên 1,5 lần. Ở lần thử cuối cùng để mở rộng, dung lượng mới là 169,220,804 vượt quá kMaxLength, do đó việc mở rộng thất bại và đẩy ra lỗi. Vì vậy, độ dài của mảng sẽ giữ ở dung lượng thành công cuối cùng, là 112,813,858.

Hi vọng phần giải thích này của tôi đã làm bạn thỏa mãn.

Tóm lại

  1. Nếu chúng ta chỉ xét phiên bản hiện tại của V8, độ dài tối đa của mảng không cố định. Độ dài tối đa có thể đạt được thay đổi với các triển khai mã khác nhau, nhưng nó không bao giờ vượt quá kMaxLength.
  2. Trong các phiên bản V8 cũ hơn và mới hơn, ngay cả kMaxLength cũng có thể thay đổi, và hành vi khi đạt đến giới hạn có thể khác nhau — có thể dẫn đến lỗi thiếu bộ nhớ (OOM) hoặc đẩy ra exception.

Add a Comment

Scroll Up