2016-02-12 24 views
5

Theo tài liệu node.js, một nút có giới hạn 512meg trên phiên bản 32 bit và giới hạn 1,4gig trên phiên bản 64bit. Các giới hạn tương tự cho Chrome AFAICT. (+/- 25%)Tại sao v8 hết bộ nhớ trong tình huống này?

Vì vậy, tại sao mã này hết bộ nhớ khi nó không bao giờ sử dụng nhiều hơn ~ 424meg bộ nhớ?

Đây là mã (Mã này là vô nghĩa. Câu hỏi này không phải về mã đang làm gì đó là lý do mã bị lỗi).

var lookup = 'superCaliFragilisticExpialidosiousThispartdoesnotrealllymattersd'; 
function encode (num) { 
    return lookup[num]; 
} 

function makeString(uint8) { 
    var output = ''; 

    for (var i = 0, length = uint8.length; i < length; i += 3) { 
    var temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); 
    output += encode(temp >> 18 & 0x3F) + encode(temp >> 12 & 0x3F) + encode(temp >> 6 & 0x3F) + encode(temp & 0x3F); 
    } 

    return output; 
} 

function test() { 
    var big = new Uint8Array(64 * 1024 * 1024 + 2); // multiple of 3 
    var str = makeString(big); 
    console.log("big:", big.length); 
    console.log("str:", str.length); 
} 

test(); 

Như bạn có thể thấy makeString tạo chuỗi bằng cách thêm 4 ký tự cùng một lúc. Trong trường hợp này nó sẽ xây dựng một chuỗi dài 89478988 (180meg) lớn. Kể từ khi output được thêm vào, các ký tự thời gian cuối cùng được nối thêm sẽ có 2 chuỗi trong bộ nhớ. Cái cũ với 89478984 ký tự và cái cuối cùng với 89478988. GC nên thu thập bất kỳ bộ nhớ nào khác được sử dụng.

Vì vậy, 64meg (mảng ban đầu) + 180meg * 2 = 424meg. Cũng theo giới hạn v8.

Nhưng, nếu bạn chạy mẫu nó sẽ thất bại với ra khỏi bộ nhớ

<--- Last few GCs ---> 

    3992 ms: Scavenge 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 0.2/0 ms (+ 1.5 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep]. 
    4450 ms: Mark-sweep 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 458.0/0 ms (+ 2.9 ms in 2 steps since start of marking, biggest step 1.5 ms) [last resort gc]. 
    4909 ms: Mark-sweep 1397.9 (1458.1) -> 1397.9 (1458.1) MB, 458.7/0 ms [last resort gc]. 

$ node foo.js  
<--- JS stacktrace ---> 

==== JS stack trace ========================================= 

Security context: 0x3a8521e3ac1 <JS Object> 
    2: makeString(aka makeString) [/Users/gregg/src/foo.js:~6] [pc=0x1f83baf53a3b] (this=0x3a852104189 <undefined>,uint8=0x2ce813b51709 <an Uint8Array with map 0x32f492c0a039>) 
    3: test(aka test) [/Users/gregg/src/foo.js:19] [pc=0x1f83baf4df7a] (this=0x3a852104189 <undefined>) 
    4: /* anonymous */ [/Users/gregg/src/foo.js:24] [pc=0x1f83baf4d9e5] (this=0x2ce813b... 

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory 
Abort trap: 6 

đã thử cả hai nút 4.2.4 và 5.6.0

Vì vậy, câu hỏi là tại sao nó lại chạy ra bộ nhớ?

Một số điều tôi đã thử.

  1. tôi đã cố gắng gia nhập khối

    Thay vì gắn liền với các output vô thời hạn Tôi đã cố gắng kiểm tra nếu nó lớn hơn một số kích thước (như 8k). Nếu vậy tôi đặt nó vào một mảng và đặt lại đầu ra cho chuỗi rỗng.

    Bằng cách thực hiện việc này output không bao giờ lớn hơn 8k. Mảng giữ 180meg + sổ sách kế toán. Vì vậy, 180meg + 8k nhỏ hơn 180meg + 180meg. Nó vẫn hết bộ nhớ. Bây giờ, vào cuối quá trình đó, tôi tham gia vào mảng tại thời điểm đó nó sẽ thực sự sử dụng nhiều bộ nhớ hơn (180meg + 180meg + bookeeping). Tuy nhiên, v8 bị treo trước khi nó được chuyển đến dòng đó.

  2. Tôi đã cố gắng thay đổi mã hóa để chỉ

    function encode(num) { 
        return 'X'; 
    } 
    

    Trong trường hợp này nó thực sự chạy đến khi kết thúc !! Vì vậy, tôi nghĩ, "A-ha! vấn đề này phải được một cái gì đó liên quan đến lookup[num] tạo một chuỗi mới mỗi gọi? Vì vậy, tôi đã cố gắng ...

  3. Changed lookup đến mảng các chuỗi

    var lookup = Array.prototype.map.call(
        'superCaliFragilisticExpialidosiousThispartdoesnotrealllymattersd', 
        function(c) { 
         return c; 
        }); 
    

    Vẫn hết bộ nhớ

Điều này có vẻ như lỗi trong v8?Nó không thể GC không sử dụng chuỗi một cách kỳ lạ vì mã này mặc dù # 2 vs # 3 là lạ vì chúng có vẻ tương đương về mặt sử dụng bộ nhớ.

Tại sao v8 hết bộ nhớ trong các trường hợp này? (và có cách giải quyết khác)

+0

Hầu hết những người thu gom rác sẽ * đóng băng thế giới * khi chúng hết bộ nhớ và đòi lại bất kỳ bộ nhớ nào có thể đòi lại. Xem câu trả lời được chấp nhận vì sao nó hết bộ nhớ nếu bạn tò mò – gman

Trả lời

2

TL; DR: Ví dụ của bạn là một trường hợp bệnh lý cho một trong các biểu diễn chuỗi bên trong của v8. Bạn có thể sửa lỗi bằng cách lập chỉ mục thành output một lần trong một thời gian (thông tin về lý do bên dưới).

Đầu tiên, chúng ta có thể sử dụng heapdump để xem những gì các bộ thu rác là lên đến:

enter image description here

Các ảnh chụp trên được chụp trong thời gian ngắn trước khi nút chạy ra khỏi bộ nhớ. Như bạn có thể thấy, hầu hết mọi thứ trông bình thường: chúng ta thấy hai chuỗi (rất lớn output và đoạn nhỏ được thêm vào), ba tham chiếu đến cùng một mảng big (khoảng 64MB, tương tự như những gì chúng ta mong đợi), và nhiều mặt hàng nhỏ hơn trông không khác thường.

Nhưng, một điều nổi bật: output là con số khổng lồ 1.4+ GB. Vào thời điểm chụp nhanh, nó dài khoảng 80 triệu ký tự, vì vậy ~ 160 MB giả sử 2 byte cho mỗi ký tự. Sao có thể như thế được?

Có thể điều này liên quan đến biểu diễn chuỗi bên trong của v8. Trích dẫn mraleph:

Có hai loại [các chuỗi v8] (trên thực tế nhiều hơn, nhưng đối với các vấn đề trong tầm tay chỉ hai đều quan trọng):

  • chuỗi phẳng là mảng bất biến của các nhân vật
  • chuỗi khuyết là cặp dây, kết quả của nối.

Nếu bạn concat a và b bạn nhận được chuỗi ký tự (a, b) đại diện cho kết quả của nối. Nếu bạn sau đó concat d mà bạn nhận được một cons-string ((a, b), d).

Việc lập chỉ mục thành chuỗi "giống cây" không phải là O (1) để làm cho V8 nhanh hơn làm phẳng chuỗi khi bạn lập chỉ mục: sao chép tất cả ký tự vào một chuỗi phẳng.

Vì vậy, có thể là v8 đại diện cho output là một cây khổng lồ? Một cách để kiểm tra là để buộc v8 để san bằng chuỗi (theo đề nghị của mraleph ở trên), ví dụ bằng cách lập chỉ mục vào output đều đặn bên trong vòng lặp for:

if (i % 10000000 === 0) { 
    // We don't do it at each iteration since it's relatively expensive. 
    output[0]; 
} 

Và quả thực, chương trình chạy thành công!

Vẫn còn một câu hỏi: tại sao phiên bản 2 ở trên chạy? Dường như trong trường hợp đó, v8 có thể tối ưu hóa hầu hết các nối chuỗi (tất cả các phần bên phải, được chuyển đổi thành các phép toán bitwise trên một mảng 4 phần tử).

+0

Thú vị * sửa *. Tôi tự hỏi bao lâu nó sẽ tiếp tục sửa chữa những thứ: (Điều đó không thực sự giải thích tại sao # 1 không hoạt động.Vì 'output' được đặt lại thành chuỗi rỗng mỗi 8192 ký tự, dường như v8 sẽ có thể vứt bỏ chuỗi đối số trước đó. Tôi cho rằng mỗi đoạn * là một chuỗi độ sâu 8192 ở điểm vẫn đủ để giết nó. Có lẽ tôi nên thử thêm các ký tự (hoặc ba) vào một mảng, và tham gia nó. Vì vậy, không có chuỗi khuyết điểm. – gman

+0

Phải, bạn vẫn kết thúc với nhiều chuỗi ký tự trong trường hợp đó. Sử dụng mảng (hoặc thậm chí bộ đệm) thực sự sẽ là một cách an toàn hơn để đi. – mtth

Các vấn đề liên quan