【问题标题】:Trading RAM by CPU (performance issue)按 CPU 交易 RAM(性能问题)
【发布时间】:2017-05-16 12:13:14
【问题描述】:

我正在使用一个处理文件的程序,我可以做一些事情,比如重命名它们,读取它们的内容等等。

今天我将它初始化如下:

return new Promise((resolve, reject) => {
  glob("path/for/files/**/*", {
    nodir: true
  }, (error, files) => {
    files = files.map((file) => {
      // properties like full name, basename, extension, etc.
    });
    resolve(files);
  });
});

因此,我读取特定目录的内容,返回数组中的所有文件,然后使用 Array.map 遍历数组并更改具有属性的对象的路径。

有时我会处理 200.000 个文本文件,所以这会成为一个问题,因为它消耗了太多的 RAM。

所以,我想用延迟加载的构造函数替换..但我以前从未这样做过...所以我正在寻找帮助。

这是我的代码:

class File {
  constructor(path) {
    this.path = path;
  }

  extension() {
    return path.extname(this.path);
  }
  // etc
}

所以,我的主要问题是:我应该只返回属性的评估,还是应该替换它?像这样:

extension() {
  this.extension = path.extname(this.path);
}

我知道这是一种权衡。我将通过 cpu 使用来交换内存。

谢谢。

【问题讨论】:

  • 使用类怎么样?你不能将文件数据存储在简单的对象中吗?
  • 我将文件存储在对象数组中,其中一个是对象。我想更改类以提高内存使用率。
  • @FXAMN 为什么你认为类会减少内存使用?
  • @FXAMN 课程对您没有多大帮助。我建议使用没有原型的普通对象(使用Object.create(null, properties) 创建)。但是话又说回来,您永远不会在视图中看到 200k 元素,最好使用延迟加载,甚至将视图中的 only 元素保留在内存中,丢弃其他元素(意思是,仅保留路径字符串和丢弃其他统计属性)。 (顺便说一句,我不知道谁投了反对票,这确实是一个合法的问题,所以我投了反对票。)
  • 如果您使用this glob library,您可以考虑使用match 事件和pause 方法来创建读取n 匹配项并返回它们的迭代器/生成器(一个一个)

标签: javascript node.js cpu ram


【解决方案1】:

如果您仅存储路径属性 NodeJS 类实例,则以 200k * (path.length * 2 + 6) 字节内存为例。

如果您想对基本名称、扩展等使用延迟加载,请使用延迟 getter

class File {
  constructor(path) {
     this.path = path;
     this._basename = null;
     this._extname = null;
  }
  get extname() {
     return this._extname || (this._extname = path.extname(this.path));
  }
  get basename() {
     return this._basename || (this._basename = path.basename(this.path));
  }
}

【讨论】:

    【解决方案2】:

    没有理由用 CPU 换取空间。只需遍历树并在找到文件时对其进行处理。如果首先完成深度,则遍历树所需的空间与树深度成正比。这几乎肯定与在现有代码中创建路径列表具有相同的开销。

    对于目录遍历,node.js FAQ 推荐使用node-findit。那里的文档很清楚。您的代码将类似于:

    var finder = require('findit')(root_directory);
    var path = require('path');
    var basenames = [];
    
    finder.on('file', function (file, stat) {
      basenames.push(path.basename(file));
      // etc
    }
    

    如果你愿意,也可以将捕获的值包装在一个对象中。

    【讨论】:

      【解决方案3】:

      如果你想减少RAM使用,我建议你为每个路径存储一个额外的元数据文件,如下:

      1. 将路径保留在内存中,或根据需要保留其中的一些。

      2. 将文件属性保存到硬盘

      files.forEach( (file) => { 
        // collect the properties you want for the file
        // ...
        var json = { path: file, extension: extension, .. }
      
        // mark the metadata file so you can access it later, for example: put it in the same path with a suffix
        var metaFile = path + '_meta.json';
        fs.writeFile(metaFile, JSON.stringify(json), (err) => {
          if (err) throw err;
        }); 
      });

      现在所有的元数据都在硬盘上。我相信,这样一来,您就可以用内存换取磁盘空间和 CPU 调用。

      1. 如果您想获取文件的属性,只需读取并JSON.parse其对应的元数据文件即可。

      【讨论】: