类型化数组与字符串的内存开销

saf*_*f32 11 javascript memory v8 typed-arrays

我试图减少javascript Web应用程序的内存使用量,该应用程序以大量小字符串的形式在内存中存储大量信息.当我更改代码Uint8Array而不是使用时String,我注意到内存使用率上升了.

例如,请考虑以下创建许多小字符串的代码:

// (1000000 strings) x (10 characters)
var a=[];
for (let i=0; i<1000000; i++)
    a.push("a".repeat(10).toUpperCase());
Run Code Online (Sandbox Code Playgroud)

如果你将它放在一个空白的页面中并让内存使用量稳定几秒钟,它就会在谷歌C​​hrome上稳定在70 MiB.另一方面,以下代码:

// (1000000 arrays) x (10 bytes)
var a=[];
for (let i=0; i<1000000; i++)
    a.push(new Uint8Array(10));
Run Code Online (Sandbox Code Playgroud)

使用233 MiB的内存.没有任何代码的空页使用大约20 MiB.另一方面,如果我创建少量的大字符串/数组,差异会变小,而对于单个字符串/数组,具有10000000个字符/条目,内存使用情况几乎相同.

那么为什么类型化数组会有如此大的内存开销呢?

jmr*_*mrk 12

V8开发人员在这里.您的结论是有道理的:如果将字符串中的字符与Uint8Array中的元素进行比较,则字符串将减少开销.TypedArrays非常适合快速访问类型化元素; 但是,拥有大量小型TypedArrays并不具有内存效率.

区别在于字符串和类型化数组的对象标头大小.

对于字符串,对象标题是:

  1. 隐藏类指针
  2. 哈希
  3. 长度
  4. 有效载荷

其中有效负载向上舍入到指针大小对齐,在这种情况下为16个字节.

对于Uint8Array,您需要以下内容:

  1. 隐藏类指针
  2. 属性指针(未使用)
  3. 元素指针(见下文)
  4. 数组缓冲区指针(见下文)
  5. 偏移到数组缓冲区
  6. 字节长度
  7. 视图到数组缓冲区的长度
  8. 长度(用户可见)
  9. 嵌入场#1
  10. 嵌入场#2

  11. 数组缓冲区:隐藏类指针

  12. 数组缓冲区:属性指针(未使用)
  13. 数组缓冲区:元素指针(见下文)
  14. 数组缓冲区:字节长度
  15. 数组缓冲区:后备存储
  16. 数组缓冲区:分配基数
  17. 数组缓冲区:分配长度
  18. 数组缓冲区:位字段(内部标志)
  19. 数组缓冲区:嵌入字段#1
  20. 数组缓冲区:嵌入字段#2

  21. 元素对象:隐藏类指针

  22. 元素对象:长度(后备存储的)
  23. 元素对象:基础指针(后备存储的)
  24. elements对象:offset to data start
  25. 元素对象:有效负载

其中,有效负载再次向上舍入到指针大小对齐,因此这里消耗16个字节.

总之,每个字符串消耗5*8 = 40个字节,每个类型化数组消耗26*8 = 208个字节.这似乎是很多开销; 原因是由于TypedArrays提供了各种灵活的选项(它们可以是重叠的视图到ArrayBuffers,可以直接从JavaScript分配,或与WebGL和诸如此类共享等).

(这不是"优化内存分配",也不是"更好地处理垃圾收集字符串" - 因为你坚持使用所有对象,GC不起作用.)