Mozilla Javascript 性能 NEW OS.File vs OLD nsIFile 超过 3000 个文件
Mozilla Javascript Performance NEW OS.File vs OLD nsIFile over 3000 files
我有一个包含小 XML 文件的目录(每个文件是 170~200 字节),我想读取每个文件的所有内容并将它们合并到一个 XML 文件中, 显示在树中。
旧
FileUtils.File + NetUtil.asyncFetch + NetUtil.readInputStreamToString
读取 3000 个 XML 个文件的时间 1112.3642930000005 毫秒
新
OS.File.DirectoryIterator + OS.File.read
读取 3000 个 XML 文件的时间 5330.708094999999 毫秒
我注意到每个文件的阅读时间存在巨大差异:
OLD的时间为0.08~0.12毫秒
NEW 的时间为 0.5~6.0 毫秒(与 OLD 相比,6.0 不是我看到的一些时间峰值的错字)
我知道旧版本链接到 C++,但位于:https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/OSFile.jsm
OS.File is a new API designed for efficient, off-main thread,
manipulation of files by privileged JavaScript code.
我看不到新 API 的效率。我的代码有问题吗?
n.b : dbgPerf 是一个性能调试,它收集对象数组中的时间和注释,并在我最后调用结束函数时执行所有计算。它不会影响性能。
使用 nsIFile 的代码:
this._readDir2 = function (pathToTarget, callbackEndLoad) {
var _content = '';
dbgPerf.add("2 start read dir");
var fuDir = new FileUtils.File(pathToTarget);
var entries = fuDir.directoryEntries;
var files = [];
while (entries.hasMoreElements()) {
var entry = entries.getNext();
entry = entry.QueryInterface(OX.LIB.Ci.nsIFile);
if (entry.isFile()) {
var channel = NetUtil.newChannel(entry);
files.push(channel);
dbgPerf.add("ADD file" + entry.path);
} else {
dbgPerf.add("NOT a file" + entry.path);
}
}
var totalFiles = files.length;
var totalFetched = 0;
for (var a = 0; a < files.length; a++) {
var entry = files[a];
dbgPerf.add("start asynch file " + entry.name);
NetUtil.asyncFetch(entry, function (inputStream, status) {
totalFetched++;
if (!Components.isSuccessCode(status)) {
dbgPerf.add('asyncFetch failed for reason ' + status);
return;
} else {
_content += NetUtil.readInputStreamToString(inputStream, inputStream.available());
dbgPerf.add("process end file " + entry.name);
}
if (totalFetched == files.length) {
var parser = new DOMParser();
_content = _content.replace(/<root>/g, '');
_content = _content.replace(/<\/root>/g, '');
_content = _content.replace(/<catalog>/g, '');
_content = _content.replace(/<\/catalog>/g, '');
_content = _content.replace(/<\?xml[\s\S]*?\?>/g, '');
xmlDoc = parser.parseFromString('<?xml version="1.0" encoding="utf-8"?><root>' + _content + '</root>', "text/xml");
//dbgPerf.add("2 fine parsing XML file " + arrFileData);
var response = {};
response.total = totalFiles;
response.xml = xmlDoc;
callbackEndLoad(response);
}
});
}
dbgPerf.add("2 AFTER REQUEST ALL FILE");
};
代码使用 OS.File :
this._readDir = function (pathToTarget, callbackEndLoad) {
dbgPerf.add("1 inizio read dir");
var xmlDoc;
var arrFileData = '';
var iterator = new OS.File.DirectoryIterator(pathToTarget);
var files = [];
iterator.forEach(function onEntry(entry) {
if (!entry.isDir) {
files.push(entry.path);
}
});
var totalFetched = 0;
files.forEach(function (fpath) {
Task.spawn(function () {
arrFileData += OS.File.read(fpath, {
encoding: "utf-8"
});
totalFetched++;
if (totalFetched == files.length) {
var parser = new DOMParser();
arrFileData = arrFileData.replace(/<root>/g, '');
arrFileData = arrFileData.replace(/<\/root>/g, '');
arrFileData = arrFileData.replace(/<catalog>/g, '');
arrFileData = arrFileData.replace(/<\/catalog>/g, '');
arrFileData = arrFileData.replace(/<\?xml[\s\S]*?\?>/g, '');
xmlDoc = parser.parseFromString('<?xml version="1.0" encoding="utf-8"?><root>' + arrFileData + '</root>', "text/xml");
dbgPerf.add("1 fine parsing XML file " + arrFileData);
var response = {};
response.xml = xmlDoc;
callbackEndLoad(response);
}
});
});
};
OS.File
是高效的,因为它是非阻塞的。当然,这会使基准测试受到影响,但用户将享受不间断的体验,甚至 感知 速度的提高。
您所展示的是一种新的 OSFile 方法比旧方法慢得多的方法,但这并不一定与新方法更有效的说法相冲突。
I/O 运行 在不同线程上的事实意味着应用程序的其他部分仍然可以在 I/O 线程等待(通常非常慢)存储来提供数据。这直接导致明显的改进,例如增加 UI 平滑度,因此在几乎所有情况下,用户都会从这种新方法中受益。
但是,这些类型的效率提高的代价是您的代码不再立即访问它所请求的文件,因此您必须等待数据提供给您的代码的总时间将更高。
可能值得测试第三种方法,在这种方法中,您 运行 在工作程序中编写代码 - 这将使您能够访问同步文件 API,因此可能会让您重新获得一些速度您看到了旧的 nsIFile 方法,同时保留了不阻塞主线程的好处。
我是 OS.File 的作者。
过去我们有一些 nsIFile 与 OS.File 的基准测试。如果您要重写 nsIFile 以在后台线程中工作(这在 XPConnect 的设计中是不可能的)或 OS.File 以在主线程中工作(我们不可能避免阻塞用户体验),在大多数情况下我记得,你会发现 OS.File 更快。
如前所述,根据设计,OS.File 专门设计为不在主线程中执行任何工作。这是因为 I/O 任务具有不可预测的持续时间 – 在极端和不可预测的情况下,关闭文件的简单操作可能会阻塞线程几秒钟,这在主线程中是不可接受的。
这样做的结果是您要进行基准测试的实际上是以下内容:
- 序列化请求并将其发送到OS.File线程;
- 执行实际 I/O;
- 序列化响应并将其发送到主线程;
- 等到主线程的下一个tick(也就是主线程真正收到响应的时候);
- 反序列化响应;
- 触发
then
回调并等待主线程的下一个 tick(根据 Promise 的定义)。
I/O效率在步骤(2)中,因为OS.File通常比nsIFile
聪明得多,所以会执行I/O 少于 nsIFile
。这对电池更好,对成为一个好公民和与其他进程友好相处更好,并且与在同一线程中执行的其他 I/O 相比更好。 响应能力是因为我们在主线程中执行的工作尽可能少。但是,如果您的代码在主线程中执行,由于步骤 (1)、(3)、(4),总 吞吐量 通常会远低于 nsIFile
), (5), (6).
我希望这能回答你的问题。
PS 你的片段是错误的。一方面,它们是倒置的。此外,您在 OS.File.read
.
的调用中忘记了 yield
我有一个包含小 XML 文件的目录(每个文件是 170~200 字节),我想读取每个文件的所有内容并将它们合并到一个 XML 文件中, 显示在树中。
旧
FileUtils.File + NetUtil.asyncFetch + NetUtil.readInputStreamToString
读取 3000 个 XML 个文件的时间 1112.3642930000005 毫秒
新
OS.File.DirectoryIterator + OS.File.read
读取 3000 个 XML 文件的时间 5330.708094999999 毫秒
我注意到每个文件的阅读时间存在巨大差异: OLD的时间为0.08~0.12毫秒 NEW 的时间为 0.5~6.0 毫秒(与 OLD 相比,6.0 不是我看到的一些时间峰值的错字)
我知道旧版本链接到 C++,但位于:https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/OSFile.jsm
OS.File is a new API designed for efficient, off-main thread, manipulation of files by privileged JavaScript code.
我看不到新 API 的效率。我的代码有问题吗?
n.b : dbgPerf 是一个性能调试,它收集对象数组中的时间和注释,并在我最后调用结束函数时执行所有计算。它不会影响性能。
使用 nsIFile 的代码:
this._readDir2 = function (pathToTarget, callbackEndLoad) {
var _content = '';
dbgPerf.add("2 start read dir");
var fuDir = new FileUtils.File(pathToTarget);
var entries = fuDir.directoryEntries;
var files = [];
while (entries.hasMoreElements()) {
var entry = entries.getNext();
entry = entry.QueryInterface(OX.LIB.Ci.nsIFile);
if (entry.isFile()) {
var channel = NetUtil.newChannel(entry);
files.push(channel);
dbgPerf.add("ADD file" + entry.path);
} else {
dbgPerf.add("NOT a file" + entry.path);
}
}
var totalFiles = files.length;
var totalFetched = 0;
for (var a = 0; a < files.length; a++) {
var entry = files[a];
dbgPerf.add("start asynch file " + entry.name);
NetUtil.asyncFetch(entry, function (inputStream, status) {
totalFetched++;
if (!Components.isSuccessCode(status)) {
dbgPerf.add('asyncFetch failed for reason ' + status);
return;
} else {
_content += NetUtil.readInputStreamToString(inputStream, inputStream.available());
dbgPerf.add("process end file " + entry.name);
}
if (totalFetched == files.length) {
var parser = new DOMParser();
_content = _content.replace(/<root>/g, '');
_content = _content.replace(/<\/root>/g, '');
_content = _content.replace(/<catalog>/g, '');
_content = _content.replace(/<\/catalog>/g, '');
_content = _content.replace(/<\?xml[\s\S]*?\?>/g, '');
xmlDoc = parser.parseFromString('<?xml version="1.0" encoding="utf-8"?><root>' + _content + '</root>', "text/xml");
//dbgPerf.add("2 fine parsing XML file " + arrFileData);
var response = {};
response.total = totalFiles;
response.xml = xmlDoc;
callbackEndLoad(response);
}
});
}
dbgPerf.add("2 AFTER REQUEST ALL FILE");
};
代码使用 OS.File :
this._readDir = function (pathToTarget, callbackEndLoad) {
dbgPerf.add("1 inizio read dir");
var xmlDoc;
var arrFileData = '';
var iterator = new OS.File.DirectoryIterator(pathToTarget);
var files = [];
iterator.forEach(function onEntry(entry) {
if (!entry.isDir) {
files.push(entry.path);
}
});
var totalFetched = 0;
files.forEach(function (fpath) {
Task.spawn(function () {
arrFileData += OS.File.read(fpath, {
encoding: "utf-8"
});
totalFetched++;
if (totalFetched == files.length) {
var parser = new DOMParser();
arrFileData = arrFileData.replace(/<root>/g, '');
arrFileData = arrFileData.replace(/<\/root>/g, '');
arrFileData = arrFileData.replace(/<catalog>/g, '');
arrFileData = arrFileData.replace(/<\/catalog>/g, '');
arrFileData = arrFileData.replace(/<\?xml[\s\S]*?\?>/g, '');
xmlDoc = parser.parseFromString('<?xml version="1.0" encoding="utf-8"?><root>' + arrFileData + '</root>', "text/xml");
dbgPerf.add("1 fine parsing XML file " + arrFileData);
var response = {};
response.xml = xmlDoc;
callbackEndLoad(response);
}
});
});
};
OS.File
是高效的,因为它是非阻塞的。当然,这会使基准测试受到影响,但用户将享受不间断的体验,甚至 感知 速度的提高。
您所展示的是一种新的 OSFile 方法比旧方法慢得多的方法,但这并不一定与新方法更有效的说法相冲突。
I/O 运行 在不同线程上的事实意味着应用程序的其他部分仍然可以在 I/O 线程等待(通常非常慢)存储来提供数据。这直接导致明显的改进,例如增加 UI 平滑度,因此在几乎所有情况下,用户都会从这种新方法中受益。
但是,这些类型的效率提高的代价是您的代码不再立即访问它所请求的文件,因此您必须等待数据提供给您的代码的总时间将更高。
可能值得测试第三种方法,在这种方法中,您 运行 在工作程序中编写代码 - 这将使您能够访问同步文件 API,因此可能会让您重新获得一些速度您看到了旧的 nsIFile 方法,同时保留了不阻塞主线程的好处。
我是 OS.File 的作者。
过去我们有一些 nsIFile 与 OS.File 的基准测试。如果您要重写 nsIFile 以在后台线程中工作(这在 XPConnect 的设计中是不可能的)或 OS.File 以在主线程中工作(我们不可能避免阻塞用户体验),在大多数情况下我记得,你会发现 OS.File 更快。
如前所述,根据设计,OS.File 专门设计为不在主线程中执行任何工作。这是因为 I/O 任务具有不可预测的持续时间 – 在极端和不可预测的情况下,关闭文件的简单操作可能会阻塞线程几秒钟,这在主线程中是不可接受的。
这样做的结果是您要进行基准测试的实际上是以下内容:
- 序列化请求并将其发送到OS.File线程;
- 执行实际 I/O;
- 序列化响应并将其发送到主线程;
- 等到主线程的下一个tick(也就是主线程真正收到响应的时候);
- 反序列化响应;
- 触发
then
回调并等待主线程的下一个 tick(根据 Promise 的定义)。
I/O效率在步骤(2)中,因为OS.File通常比nsIFile
聪明得多,所以会执行I/O 少于 nsIFile
。这对电池更好,对成为一个好公民和与其他进程友好相处更好,并且与在同一线程中执行的其他 I/O 相比更好。 响应能力是因为我们在主线程中执行的工作尽可能少。但是,如果您的代码在主线程中执行,由于步骤 (1)、(3)、(4),总 吞吐量 通常会远低于 nsIFile
), (5), (6).
我希望这能回答你的问题。
PS 你的片段是错误的。一方面,它们是倒置的。此外,您在 OS.File.read
.
yield