Performance degradation in concurrent usage with `Promise`, `Promise.all`
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 35/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 停滞
- 技術スタック
- javascript
- 領域
- performance
調査の方向性
benchmark/XmlParserConcurrent.mjs、src/fxp.js、spec/assets/midsize.xmlから始めます。concurrentおよびnon-concurrentのベンチマークを実行し、その後、XMLParserとlegacy parserが使用するパーサー経路を追跡します。大きなファイルで報告されているasync/concurrentの低速化が説明されて対処され、ベンチマーク結果で改善が示されれば完了です。
索引モデルが issue の本文から書いたものです。
説明
When implementing XML processing using Promise.all with fast-xml-parser, we encountered significant performance
degradation issues. After conducting benchmark tests, the results suggested that this could be related to how
fast-xml-parser handles concurrent operations, particularly with larger XML files.
Summary
When processing large XML files (e.g., 13MB) concurrently with Promise, Promise.all (10 at the same time), fast-xml-parser is significantly slower than xml2js.
In contrast, for small or lightweight files, fast-xml-parser performs well and even outperforms xml2js.
When running a single parse (no concurrency), fast-xml-parser shows very high throughput, meaning the slowdown only appears with concurrent or async usage.
Reproduction Code
https://github.com/sya-ri/fast-xml-parser/commit/172d53094b2a3324bd8c30e8a53ba72789bb9452
A. Concurrent execution (13MB / 10 at once)
Executed as benchmark/XmlParserConcurrent.mjs.
"use strict";
import Benchmark from "benchmark";
import {XMLParser} from "../src/fxp.js";
import xml2js from "xml2js";
import fxpv3 from "fast-xml-parser";
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
// compatibility
const __dirname = dirname(fileURLToPath(import.meta.url));
const suite = new Benchmark.Suite("XML Parser concurrent benchmark");
import fs from "fs";
import path from "path";
const fileNamePath = path.join(__dirname, "../spec/assets/midsize.xml"); // 13MB
const xmlData = fs.readFileSync(fileNamePath).toString();
const promiseSize = 10;
const fxpParser = new XMLParser();
const fxpParserForOrderedJs = new XMLParser({preserveOrder: true});
suite
.add("fxp v3", {
defer: true,
fn: function(deferred) {
Promise.all(Array.from({ length: promiseSize }).map(async () =>
fxpv3.parse(xmlData)
)).then(() => deferred.resolve());
}
})
.add("fxp", {
defer: true,
fn: function(deferred) {
Promise.all(Array.from({ length: promiseSize }).map(async () =>
fxpParser.parse(xmlData)
)).then(() => deferred.resolve());
}
})
.add("fxp - preserve order", {
defer: true,
fn: function(deferred) {
Promise.all(Array.from({ length: promiseSize }).map(async () =>
fxpParserForOrderedJs.parse(xmlData)
)).then(() => deferred.resolve());
}
})
.add('xml2js ', {
defer: true,
fn: function(deferred) {
Promise.all(Array.from({ length: promiseSize }).map(async () =>
xml2js.parseStringPromise(xmlData)
)).then(() => deferred.resolve());
}
})
.on("start", function() {
console.log("Running Suite: " + this.name);
})
.on("error", function(e) {
console.log("Error in Suite: " + this.name, e);
})
.on("abort", function(e) {
console.log("Aborting Suite: " + this.name, e);
})
.on("complete", function() {
for (let j = 0; j < this.length; j++) {
console.log(this[j].name + " : " + this[j].hz + " requests/second");
}
})
.run({"async": true});
B. Non-concurrent (wrapped in async)
Executed as benchmark/XmlParserConcurrent.mjs.
"use strict";
import Benchmark from "benchmark";
import {XMLParser} from "../src/fxp.js";
import xml2js from "xml2js";
import fxpv3 from "fast-xml-parser";
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
// compatibility
const __dirname = dirname(fileURLToPath(import.meta.url));
const suite = new Benchmark.Suite("XML Parser concurrent benchmark");
import fs from "fs";
import path from "path";
const fileNamePath = path.join(__dirname, "../spec/assets/midsize.xml"); // 13MB
const xmlData = fs.readFileSync(fileNamePath).toString();
const fxpParser = new XMLParser();
const fxpParserForOrderedJs = new XMLParser({preserveOrder: true});
suite
.add("fxp v3", {
defer: true,
fn: function(deferred) {
(async () =>
fxpv3.parse(xmlData)
)().then(() => deferred.resolve());
}
})
.add("fxp", {
defer: true,
fn: function(deferred) {
(async () =>
fxpParser.parse(xmlData)
)().then(() => deferred.resolve());
}
})
.add("fxp - preserve order", {
defer: true,
fn: function(deferred) {
(async () =>
fxpParserForOrderedJs.parse(xmlData)
)().then(() => deferred.resolve());
}
})
.add('xml2js ', {
defer: true,
fn: function(deferred) {
(async () =>
xml2js.parseStringPromise(xmlData)
)().then(() => deferred.resolve());
}
})
.on("start", function() {
console.log("Running Suite: " + this.name);
})
.on("error", function(e) {
console.log("Error in Suite: " + this.name, e);
})
.on("abort", function(e) {
console.log("Aborting Suite: " + this.name, e);
})
.on("complete", function() {
for (let j = 0; j < this.length; j++) {
console.log(this[j].name + " : " + this[j].hz + " requests/second");
}
})
.run({"async": true});
Benchmark Results
13MB XML / 10 concurrent
fxp v3 : 0.2865043014339054 requests/second
fxp : 0.16876260944496088 requests/second
fxp - preserve order : 0.1669240166670523 requests/second
xml2js : 1282.977050682912 requests/second
With large XML files under concurrency, fast-xml-parser is drastically slower than xml2js.
1.5KB XML / 10 concurrent
fxp v3 : 3238.1802137107325 requests/second
fxp : 1778.4294041322637 requests/second
fxp - preserve order : 2043.0091729760097 requests/second
xml2js : 1745.291494844131 requests/second
With small XML, fast-xml-parser performs on par or better than xml2js.
CDATA (lightweight XML) / 10 concurrent
fxp v3 : 9789.883828979968 requests/second
fxp : 6691.052733490235 requests/second
fxp - preserve order : 7227.875838621091 requests/second
xml2js : 3116.079812611551 requests/second
With lightweight files, fast-xml-parser outperforms xml2js significantly.
13MB XML / single execution (npm run parser)
fxp v3 : 100200.12823920241 requests/second
fxp : 66284.89077189047 requests/second
fxp - preserve order : 71015.28486649034 requests/second
xmlbuilder2 : 29305.972887978354 requests/second
xml2js : 31336.192966600825 requests/second
In single execution, fast-xml-parser shows excellent performance, faster than xml2js.
13MB XML / non-concurrent (async wrapper)
fxp v3 : 2.807664801903926 requests/second
fxp : 1.8445207259077958 requests/second
fxp - preserve order : 2.0789000186976128 requests/second
xml2js : 13684.611351341111 requests/second
Even with a simple async wrapper, fast-xml-parser slows down drastically, while xml2js maintains good performance.
Notes
- Compared parsers: fast-xml-parser and xml2js.
xmlbuilder2 is excluded except in the single execution test. - Using
xml2js.parseString(xmlData, callback)instead ofxml2js.parseStringPromise(xmlData)produced the same results.
- 主要言語
- JavaScript
- スター
- 3.1k
- フォーク
- 398
- 平均マージ
- 2日 14時間
- マージ済み PR(30日)
- 2
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
NaturalIntelligence/fast-xml-parser のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
NaturalIntelligence/fast-xml-parser#888 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
ignoreDeclaration does not ignore lower case doctype declaration再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
NaturalIntelligence/fast-xml-parser#870 · コメント 2 件 ·
メンテナーはふだん 2 日以内に返信
-
Enhance type definition for XMLParser `isArray` option when using TypeScript再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 52/100
NaturalIntelligence/fast-xml-parser#803 · コメント 21 件 · リアクション 2 件 ·
メンテナーはふだん 2 日以内に返信
-
Feature-Request
難易度 4/5 3〜5日 初心者へのやさしさ 52/100
NaturalIntelligence/fast-xml-parser#779 · コメント 9 件 ·
メンテナーはふだん 2 日以内に返信
-
enhancement
難易度 3/5 1〜2日 初心者へのやさしさ 48/100
NaturalIntelligence/fast-xml-parser#708 · コメント 4 件 ·
メンテナーはふだん 2 日以内に返信
NaturalIntelligence/fast-xml-parser の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
processing/p5.sound.js#123 ·
-
難易度 1/5 1〜3時間 初心者へのやさしさ 82/100
PhilflowIO/dav-mcp#146 ·
メンテナーはふだん 1 日以内に返信
-
good first issue hacktoberfest
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
Tanishq964/trail-kit.#4 ·
-
new icon permissions in review
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
simple-icons/simple-icons#15067 ·
メンテナーはふだん 1 日以内に返信
-
status: needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
mastra-ai/mastra#26562 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信