

1 億ポイントは、スケールを前提に作られたチャートライブラリと、ダッシュボード向けに作られたものとを分けるストレステストです。WinUI ではその差がはっきりと表れます。大半の WinUI チャートは XAML レイヤーを通じて描画するマネージドな .NET コントロールであるのに対し、ProEssentials は内部にネイティブな Direct3D および Direct2D の C++ エンジンをホストしているからです。
私たちは、同じ 1億ポイントの信号を 5 つの WinUI ライブラリ、すなわち ProEssentials、Syncfusion、Telerik、DevExpress、ComponentOne でプロットしました。以下のコードは、それぞれにおいてチャートに至る最短かつ誠実な道筋です。本当の違いはコード行数ではなく、データが画面に届くまでに何が起こるかにあります。
ProEssentials は float 配列をその場で読み取り、ジオメトリを GPU 上で再構築します。4 つのマネージドスイートは、ポイントごとに 1 つずつ、オブジェクトのコレクションにバインドするため、同じ 1億個の値は、何かが描画される前に数ギガバイトのマネージドヒープへと膨れ上がります。
Reproduce the WinUI numbers yourself: clone, build, run the WinUI 100M-point chart demo on GitHub.Prefer WPF or WinForms? See the WPF 100M-point demo and the WinForms 100M-point demo.
同じデータ、同じマシン、5 つのライブラリ。コード行数、メモリのオーバーヘッド、そもそも描画できるかどうか、そして表示が無損失かダウンサンプリングされた近似かを測定します。
| Parameter | Value |
|---|---|
| Data points | 100,000,000 (100 million) |
| Data type | float (4 bytes per value) — 400 MB raw data |
| Chart type | 2-D line chart, single series, sequential X-axis |
| Pattern | Sine wave with random noise (realistic signal data) |
| Platform | WinUI 3 (.NET 10), Windows 11, mid-range GPU |
| What we measure | Lines of code, memory overhead, render time, data fidelity |
以下のパターンは 5 つのライブラリすべてに共通します。データを読み込むコードのほうが、チャートを構成するコードよりもはるかに重要だということです。
チャートは既存の float 配列を UseDataAtLocation を通じて読み取るため、2 度目のコピーは一切発生しません。1億個の値はすべて GPU に送られ、そこで Filter2D3D compute shader が控えめな min/max の事前フィルタパスを実行したのち、最終シェーダーがシーンを構築します。すべてのポイントが処理されます。データが表示解像度より高密度な場合、フィルタはピクセル列ごとに 50 組の min/max ペア(ピクセルあたり 100 個のプロットポイント)を保持するため、スパイクが失われることはなく、密な領域は完全なソリッド塗りつぶしとして描画されます。これはデータセット全体を描いたのと同じ画像です。
// ProEssentials — Plot 100 Million Points (WinUI 3, ~15 lines)
// NuGet: Install-Package ProEssentials.Chart.Net10.WinUI
// Control: PesgoWinUI (named Pesgo1 in XAML). .NET 10, x64 and native ARM64.
// 1. Allocate your data — this is the ONLY copy that will ever exist
float[] yData = new float[100_000_000];
for (int i = 0; i < yData.Length; i++)
yData[i] = (float)Math.Sin(i * 0.0001) + (float)(rand.NextDouble() * 0.1);
// 2. Configure the chart
Pesgo1.PeData.Subsets = 1;
Pesgo1.PeData.Points = 100_000_000;
Pesgo1.PePlot.Method = SGraphPlottingMethod.Line;
Pesgo1.PeConfigure.RenderEngine = RenderEngine.Direct3D;
Pesgo1.PeData.ComputeShader = true; // build chart geometry on the GPU
Pesgo1.PeData.Filter2D3D = true; // lossless GPU min/max pre-filter
Pesgo1.PeData.StagingBufferY = true; // required with ComputeShader
// 3. Zero-copy: point the chart at your existing array — no duplication
Pesgo1.PeData.Y.UseDataAtLocation(yData, yData.Length);
// 4. Rebuild vertices on the GPU and render
Pesgo1.PeFunction.Force3dxVerticeRebuild = true;
Pesgo1.PeFunction.ReinitializeResetImage();
Pesgo1.Invalidate();表示処理もネイティブです。エンジンは独自の DXGI flip-model composition swap chain にレンダリングし、それを ISwapChainPanelNative::SetSwapChain を通じて XAML ホストに渡します。これにより DWM は、中間ビットマップもフレームごとの CPU コピーもなしに、チャートを直接ビジュアルツリーにコンポジットします。さらに SetMaximumFrameLatency(1) でプレゼントキューを制限しており、これが、1億ポイントのチャートでもフレームを先読みしてバッファリングするのではなくドラッグに追従できる理由です。
チャートが追加で消費するメモリは、小さな GPU ステージングバッファだけです。あなたの 400 MB のデータは 400 MB のままであり、同じ PesgoWinUI コントロールが、特別なシリーズタイプなしで 10,000 ポイントでも 1億ポイントでも処理します。
以下に示す 4 つの例は、各ベンダーがドキュメント化した API に忠実であり、コンパイルも通ります。ただし、チャートを描画してはくれません。いずれかに 1億個の値を渡すと、現実的な結果は、割り当てループのどこかで発生する OutOfMemoryException か、終わる前にデバッガーを強制終了したくなるほど長い待ち時間のいずれかです。このコードを掲載するのは、違いを具体的に示すためであり、この規模で実際に実行することを想定しているからではありません。各スニペットで注目すべきは、下部のチャート設定ではなく、データポイントごとにオブジェクトを 1 つ割り当てる、上部のループです。
これは、性能に関する主張を読むときに念頭に置く価値があります。4 社はいずれも自社のチャートを高性能とうたっており、実際に数値を公表しているプラットフォームにおいては、それは公正な主張です。問題は、それらが WinUI の数値ではないという点です。
これらの数値についての注記。4 社のいずれも、WinUI チャートに特化した大規模データのベンチマークを公表していません。Syncfusion の 100 万ポイントのベンチマークは WPF のものです。DevExpress の最大値は、WinForms 専用のシリーズビューである SwiftPlotSeriesView に依存しています。ComponentOne が公表している Direct2D の数値は、.NET 6 の WinForms ビルドで約 5 ms で 50,000 ポイントです。大規模なセットに関する Telerik のガイダンスは、バインドする前にデータを集約して減らすというものです。私たち自身は各社の WinUI コントロールをベンチマークしていないため、それらの上限を勝手に作り出すつもりはありません。言えるのは、WinUI での大規模データ性能について公開された証拠は、いずれの製品についてもまだ存在しないということです。
基準として、私たち自身の限界も示しておきます。ProEssentials の Direct2D パスは、300万ポイントあたりから負荷が高まり始めます。しかもこれは、ゼロコピーでの読み込みと、ポイントごとのオブジェクトのオーバーヘッドがない状態での話です。そこから 1億まで引き上げるのは Direct3D の compute shader パスです。ポイントごとにオブジェクトを割り当てるマネージドな XAML コントロールは、その手前でとうに息切れします。
Syncfusion の高速シリーズは、線を WriteableBitmap にラスタライズします。これはデフォルトのパスに対する実質的な最適化です。問題はデータモデルにあります。チャートはオブジェクトのコレクションにバインドするため、1億ポイントは、ビットマップのパスが動き出すはるか前に 1億回の割り当てを意味します。
// Syncfusion — Plot 100 Million Points (WinUI, SfCartesianChart)
// NuGet: Install-Package Syncfusion.Chart.WinUI
// 1. Syncfusion binds to objects — one per point
public class DataPoint { public double X { get; set; } public double Y { get; set; } }
// 2. Build the collection — 100M objects is ~2.4 GB+ with object headers
var data = new List<DataPoint>(100_000_000);
for (int i = 0; i < 100_000_000; i++)
data.Add(new DataPoint { X = i, Y = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1 });
// This allocation alone risks OutOfMemoryException; the practical ceiling is well below 100M.
// 3. Fast bitmap series — rasterizes the line to a WriteableBitmap
var series = new FastLineBitmapSeries {
ItemsSource = data,
XBindingPath = "X",
YBindingPath = "Y"
};
chart.Series.Add(series);Syncfusion の 100 万ポイントという数値の出所も注目に値します。同社が公表しているそのベンチマークは、WinUI ではなく WPF のものです。いずれにせよ上限を決めているのは、ラスタライザーではなく、ポイントごとにオブジェクトを生成するモデルです。
Telerik はシリーズを Composition ContainerVisualsFactory を通じて描画するため、通常のチャートは滑らかに動作します。Syncfusion と同様にオブジェクトにバインドしており、大規模データに関する Telerik 自身のガイダンスも、データをチャートに渡す前にダウンサンプリングすることを勧めています。
// Telerik — Plot 100 Million Points (WinUI, RadCartesianChart)
// NuGet: Install-Package Telerik.WinUI.Controls (license key required)
// 1. Telerik binds to objects — one per point
public class DataPoint { public double X { get; set; } public double Y { get; set; } }
// 2. Build the collection (~2.4 GB+ at 100M objects)
var data = new ObservableCollection<DataPoint>();
for (int i = 0; i < 100_000_000; i++)
data.Add(new DataPoint { X = i, Y = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1 });
// 3. Line series — drawn through the Composition ContainerVisualsFactory
var series = new LineSeries {
ItemsSource = data,
CategoryBinding = new PropertyNameDataPointBinding { PropertyName = "X" },
ValueBinding = new PropertyNameDataPointBinding { PropertyName = "Y" }
};
// For large data, Telerik expects you to downsample before binding.
radChart.Series.Add(series);先にダウンサンプリングするということは、チャートが 1億ポイントすべてを見ることは決してないという意味です。これは妥当な戦略ではありますが、無損失ではありません。
DevExpress の WinUI ChartControl は、argument メンバーと value メンバーを持つ DataSource をバインドします。大規模なデータセットについて DevExpress は、すべてのポイントではなく代表的なサブセットを描画するよう、データサンプリングを有効にすることを推奨しています。
// DevExpress — Plot 100 Million Points (WinUI, ChartControl)
// NuGet: DevExpress WinUI packages (private feed, credentials required)
// 1. DevExpress binds to objects — one per point
public class DataPoint { public double Argument { get; set; } public double Value { get; set; } }
// 2. Build the collection (~2.4 GB+ at 100M objects)
var data = new List<DataPoint>(100_000_000);
for (int i = 0; i < 100_000_000; i++)
data.Add(new DataPoint { Argument = i, Value = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1 });
// 3. Line series on the ChartControl — bind DataSource + data members
var series = new LineSeries();
series.DataSource = data;
series.ArgumentDataMember = "Argument";
series.ValueDataMember = "Value";
chartControl.Series.Add(series);
// DevExpress recommends data sampling to keep large sets responsive.サンプリングによって UI の応答性は保たれますが、その前段階で、ポイントごとにオブジェクトを割り当てるという同じ処理が発生します。
ComponentOne は Direct2D レンダリングモードを備えた唯一の競合であり、デフォルトのパスに対する実質的な最適化になっています。とはいえ、依然としてオブジェクトコレクションにバインドするマネージドコントロールであるため、メモリに関する状況は他社と変わりません。スループットについて ComponentOne が Direct2D 向けに公表している具体的な数値は約 5 ms で 50,000 ポイントですが、これは WinUI ではなく .NET 6 の WinForms ビルドで測定されたものです。
// ComponentOne (MESCIUS) — Plot 100 Million Points (WinUI, FlexChart)
// NuGet: Install-Package C1.WinUI.Chart
// 1. FlexChart binds to objects — one per point
public class DataPoint { public double X { get; set; } public double Y { get; set; } }
// 2. Build the collection (~2.4 GB+ at 100M objects)
var data = new List<DataPoint>(100_000_000);
for (int i = 0; i < 100_000_000; i++)
data.Add(new DataPoint { X = i, Y = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1 });
// 3. Bind the chart, then switch on the Direct2D render mode for large data
flexChart.ItemsSource = data;
flexChart.BindingX = "X";
flexChart.Series.Add(new Series { Binding = "Y" });
flexChart.RenderMode = RenderMode.Direct2D; // required to draw millions of pointsDirect2D モードは、マネージドな WinUI チャートがネイティブのアプローチに最も近づいた例であり、公正に比較するうえで知っておく価値があります。
メモリの行はベンチマークではなく単純な計算です。1億個の float は 400 MB ですが、1億個のバインドされたオブジェクトは、1 ピクセルも描画されないうちからギガバイト単位のヘッダーと参照を抱えます。
| Factor | ProEssentials | Syncfusion | Telerik | DevExpress | ComponentOne |
|---|---|---|---|---|---|
| Renders 100M in real time? | ✅ Yes — natively | ❌ OOM well before 100M | ⚠️ Aggregate first | ⚠️ Sample first | ⚠️ Direct2D mode (50k published) |
| Rendering | Native Direct3D compute | WriteableBitmap | Composition visuals | Managed XAML | Direct2D (optional) |
| Data model | Zero-copy float[] pointer | Object-per-point | Object-per-point | Object-per-point | Object-per-point |
| Memory: your data | 400 MB | 400 MB | 400 MB | 400 MB | 400 MB |
| Memory: library overhead | ~0 MB | ~2,400 MB+ | ~2,400 MB+ | ~2,400 MB+ | ~2,400 MB+ |
| Total memory | ~400 MB | OOM risk | ~2,800 MB (or downsampled) | ~2,800 MB (or sampled) | ~2,800 MB |
| Special series type? | No — same control | FastLineBitmapSeries | Sampling settings | Sampling settings | Direct2D render mode |
| Full-fidelity display | Lossless GPU min/max | Bitmap raster | Downsampled subset | Sampled subset | Direct2D raster |
| Native ARM64 | Yes — native binary | Managed | Managed | Managed | Managed |
この規模ですべてを決める数値は、1 秒あたりのフレーム数ではなく、データが画面に表示されるまでに何回コピーされるかです。
ProEssentials はデータを一度もコピーしません。すでに割り当て済みの float 配列を読み取り、GPU 上で画像を再構築します。コレクションにバインドするマネージドチャートは、各値をヘッダーと参照を持つオブジェクトへコピーします。ギガバイト単位のメモリ消費は、ここから生じます。
数千ポイントであれば誰も気づきません。しかし 1 億ポイントになると、そのたった 1 つの設計上の選択が、400 MB のアプリと、メモリ不足に陥るアプリとの分かれ目になります。
| Data Model | Overhead (100M pts) | Used By |
|---|---|---|
| Zero-copy pointer | ~0 MB | ProEssentials |
| Array copy (float) | ~400 MB | — |
| Object-per-point | ~2,400 MB+ | Syncfusion, Telerik, DevExpress, ComponentOne |
ポイントごとにオブジェクトを割り当てるデータモデルは、レンダリングがいくら高速でも救えません。マネージドスイートがダウンサンプリングやビットマップの高速パスに頼るのは、まさにそのオブジェクトモデルでは 1億ポイントを保持できないからです。ProEssentials はそもそもこの問題を生じさせません。
5 つのライブラリはいずれもきれいな折れ線チャートを描きます。しかし、データをダウンサンプリングさせることも、シリーズタイプを切り替えさせることも、数ギガバイトのヒープを受け入れさせることもなく、1億ポイントを無損失かつリアルタイムに描画できるのは 1 つだけです。それは、データを既に存在する場所で直接読み取るネイティブエンジンならではの利点です。
WinUI アプリでプロットするのが数千ポイントなら、すでにお持ちのスイートを選べば十分です。しかし数百万から数億ポイントをプロットするなら、データモデルがすべてを左右します。そしてそのために作られているのが ProEssentials です。
競合製品のレンダリング方式とバインディング API は、各ベンダーが公開するドキュメントに基づいています:
Syncfusion (WinUI SfCartesianChart)
Telerik (UI for WinUI RadChart)
DevExpress (WinUI ChartControl)
ComponentOne / MESCIUS (FlexChart for WinUI)
プロットが必要なポイント数と、それらがどのように更新されるかをお聞かせください。エンジンを開発した技術者が、ProEssentials が適しているかどうかを正直にお伝えします。
Contact us御社とエンドユーザーに最も簡単で最もプロフェッショナルな価値を提供することで、お客様の成功を最優先目標とします。
ProEssentials は、自らのチャートコンポーネントを必要としたプロの電気エンジニアから生まれました。ProEssentials を使う一流エンジニアリング企業の長いリストに加わってください。
ProEssentials のお客様であることに感謝するとともに、ProEssentials チャートエンジンをご検討いただきありがとうございます。