在 WinForms 中绘制 1 亿个点

比 WPF 更快——C# 代码对比

100 million points WinForms
large dataset winforms chart
GPU compute shader
zero copy data
winforms chart faster than wpf
winforms chart benchmark
C# winforms charting
plot millions winforms

在 WinForms 中绘制 1 亿个点
比 WPF 更快——C# 代码对比

如何在 WinForms 折线图中显示 100,000,000 个数据点?答案完全取决于你选择哪个图表库——以及一个几乎所有对比都搞反了的细节:在正确的引擎上,WinForms 并不比 WPF 慢。它更快。

本文提供完整、可运行的 C# 代码,用于在开发者实际评估的 WinForms 图表库——ProEssentials、LightningChart、DevExpress、Syncfusion、ComponentOne 和 ScottPlot——中渲染 1 亿个点。它展示每一步中你的数据发生了什么,以及为什么同一个 ProEssentials 引擎在 WinForms 上比在 WPF 上快约 5%。

如果你搜索过 "plot millions of points WinForms"、"fastest WinForms chart" 或 "WinForms chart 100 million points",这就是代码层面的具体答案——并附带一个 clone-and-run 的 GitHub 仓库,让你能亲自复现每一个数字。

亲自试试:WinForms 1 亿点演示是一个公开仓库——克隆它,按 F5,看着 1 亿个点渲染到 hDC 上。无需账户、无需试用注册、没有栅格化替代数据。

Building on WinUI 3? See the WinUI edition of this 100M-point comparison

Reproduce the WinForms numbers yourself: clone, build, run the WinForms 100M-point chart demo on GitHub.

Compare against the WPF build: clone, build, run the WPF 100M-point chart demo on GitHub.


100 million points in a native WinForms chart — lossless GPU compute-shader rendering, ProEssentials
ProEssentials WinForms (GigaPrime2D) — 100,000,000 points, lossless, ~15 ms render, Direct3D ComputeShader, hDC direct present
为何 WinForms 在同一引擎上胜过 WPF

在看代码之前,先看一个重新定义全局的架构要点。WPF 上的 GPU 图表无法把 Direct3D 直接画到屏幕上——WPF 合成器掌控这些像素,所以库必须渲染到离屏纹理,通过 D3DImage 交给 WPF,再让合成器混合进去。每一帧都要付出纹理复制和合成器同步的代价。

原生 WinForms 控件拥有真实的 Win32 窗口句柄和设备上下文(hDC)。ProEssentials 将 Direct3D 直接耦合到该 hDC——由 Compute Shader 构建的帧被直接呈现到窗口,没有 render-to-texture,没有二次合成。在各类代表性数据集上,这使原生 WinForms 路径的端到端速度比驱动 WPF 控件的同一引擎快约 5%。

重新定义:

相同的 Compute Shader、相同的 zero-copy 数据路径、相同的 on-demand 模型。WinForms 不是折衷接口——它是更快的那个,因为它完全跳过了 WPF 合成器。

测试:100,000,000 个 Float 值

每个库都接受相同的测试:在原生 WinForms 窗体上,从 1 亿个连续 float 值渲染一个单系列折线图。我们测量超出源数组的内存开销、渲染时间,以及该库是显示每一个点还是显示降采样后的近似。

ParameterValue
Data points100,000,000 (100 million)
Data typefloat (4 bytes per value) — 400 MB raw data
Chart type2-D line chart, single series, sequential X-axis
PatternSine wave with random noise (realistic signal data)
PlatformWinForms (.NET 8), Windows 11, mid-range GPU
What we measureLines of code, memory overhead, render time, data fidelity

下面我们展示每个库的完整 C# 代码,并标注每一步中你的数据发生了什么。请尤其关注一件事:这 1 亿个值是被复制、被转换,还是被包装成对象——以及图表是直接呈现到窗口,还是经过一个 interop 层。

库 1:ProEssentials——Zero-Copy + GPU Compute Shader,hDC 耦合

ProEssentials 使用 UseDataAtLocation() 让 WinForms 图表直接指向你现有的 float 数组。没有复制、没有转换、没有对象包装。Direct3D Compute Shader 在 GPU 上构建全部 1 亿个顶点,并把帧直接呈现到控件的 hDC。

// ProEssentials — Plot 100 Million Points (WinForms, ~15 lines)
// NuGet: Install-Package ProEssentials.Chart.Net.Winforms

// 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 WinForms chart control (PesgoWin)
pesgo1.PeData.Subsets = 1;
pesgo1.PeData.Points = 100_000_000;
pesgo1.PePlot.Method = SGraphPlottingMethod.Line;
pesgo1.PeConfigure.RenderEngine = RenderEngine.Direct3D;
pesgo1.PeData.ComputeShader = true;
pesgo1.PeData.Filter2D3D = true;  // GPU pre-filter shader — lossless min/max

// 3. Zero-copy: point the chart at your existing array — no duplication
pesgo1.PeData.Y.UseDataAtLocation(yData, yData.Length);

// 4. Tell the Direct3D engine to rebuild vertices and colors
pesgo1.PeFunction.Force3dxVerticeRebuild = true;
pesgo1.PeFunction.Force3dxNewColors = true;

// 5. Render — presented DIRECTLY to the WinForms window hDC
//    (no render-to-texture, no WPF compositor hop)
pesgo1.PeFunction.ReinitializeResetImage();

关键行是 UseDataAtLocation(yData, yData.Length)。这不是复制操作——是指针赋值。你的数据已占用的 400 MB 是唯一会存在的副本;图表增加的开销基本为零。

Force3dxVerticeRebuildForce3dxNewColors 标志告诉 Direct3D 引擎在 GPU 上重建顶点和颜色。配合 ComputeShader = trueFilter2D3D = true,GPU 预过滤器会计算每个像素列的无损 min/max,因此不会丢失任何尖峰——结果在没有 WPF 合成器环节的情况下呈现到 hDC。

结果:

约 15 行 C#。约 0 MB 内存开销。约 15 ms 渲染时间,比 WPF 上的相同代码快约 5%。通过 GPU Compute Shader 进行无损 min/max 过滤,直接呈现到 WinForms hDC。

库 2:SciChart——没有原生 WinForms 控件

SciChart 常被誉为最快的 WPF 图表,但在 WinForms 上有一个任何代码示例都无法绕过的陷阱:SciChart 没有原生 WinForms 控件。其自身文档说明 WinForms 仅通过与 WPF 的集成获得支持——即把 WPF 的 SciChartSurface 托管在 Microsoft ElementHost 内。

// SciChart — Plot 100 Million Points (WinForms)
// NOTE: SciChart has NO native WinForms control.
// WinForms is supported only by hosting the WPF control in an ElementHost.

// 1. Host the WPF SciChartSurface inside a WinForms ElementHost
var host = new System.Windows.Forms.Integration.ElementHost();
host.Dock = DockStyle.Fill;
var sciChartSurface = new SciChart.Charting.Visuals.SciChartSurface();
host.Child = sciChartSurface;       // WPF control living inside WinForms
this.Controls.Add(host);

// 2. From here the code is identical to the WPF path — and inherits
//    WPF's render-to-texture compositor cost plus ElementHost interop
//    limitations (mouse events, focus, z-order / "airspace").
var dataSeries = new XyDataSeries<double, double>();  // double[], not float[]
dataSeries.Append(xData, yData);    // full internal copy
var lineSeries = new FastLineRenderableSeries {
    DataSeries = dataSeries,
    ResamplingMode = ResamplingMode.Auto   // downsamples to ~viewport width
};
sciChartSurface.RenderableSeries.Add(lineSeries);

// There is no native WinForms SciChart control to target.

这意味着 SciChart 的 WinForms 应用继承了 WPF 的 render-to-texture 合成器路径,外加 ElementHost 在鼠标事件、焦点和 Z 顺序方面有据可查的 interop 限制。这 1 亿点代码实质上就是被包装的 WPF 代码——并承担着原生 WinForms 所避免的合成器代价。

对于真正的原生 WinForms 应用,没有一流的 SciChart 路径。这个类别中最响亮的性能名号并未推出原生 WinForms 条目,因此这里以 interop 而非代码示例的形式呈现。

库 3:LightningChart——原生、DirectX、专用系列

LightningChart 是最强的真正原生 WinForms 竞争者。它通过 DirectX 管线使用专用样本系列渲染 1 亿个点,并通过类似 AddSamples 的调用把你的 float[] 复制到其内部存储。

// LightningChart — Plot 100 Million Points (WinForms)
// NuGet: Install-Package LightningChart.NET (native WinForms control)

// 1. Allocate your data
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 native WinForms chart for large data
lightningChart.BeginUpdate();
lightningChart.ViewXY.XAxes[0].SetRange(0, 100_000_000);

// 3. SampleDataSeries (fixed-interval) — copies float[] into internal buffer
var series = new SampleDataSeries(lightningChart.ViewXY,
    lightningChart.ViewXY.XAxes[0], lightningChart.ViewXY.YAxes[0]);
series.FirstSampleTimeStamp = 0;
series.SamplingFrequency = 1;

// 4. Add all 100M samples — array copy (not zero-copy)
series.AddSamples(yData, false);

// 5. Add series; continuous DirectX render loop keeps the GPU active
lightningChart.ViewXY.SampleDataSeries.Add(series);
lightningChart.EndUpdate();

它(在使用正确的系列类型时)无损渲染全部 1 亿个点,这是一项真正的优势。其取舍与更广泛的对比一致:连续的 DirectX 渲染循环即使在毫无变化时也让 GPU 保持活跃,而数据路径是复制你的数组而非引用它。

就原始能力而言,LightningChart 是最接近的原生 WinForms 竞争者。差异归结于数组复制、连续循环功耗,以及激活/许可——而不在于它能否渲染这些点。

库 4:DevExpress——SwiftPlot(仅 WinForms)+ 逐点对象

DevExpress 是有趣的反转:其快速大数据视图 SwiftPlotSeriesView 仅限 WinForms——WPF ChartControl 中并不存在。因此对于大数据,WinForms 实际上是 DevExpress 更强的表面。

// DevExpress — Plot 100 Million Points (WinForms)
// NuGet: DevExpress.Win.Charts
// NOTE: SwiftPlotSeriesView is WinForms-ONLY — it does not exist in WPF.

// 1. Create data model — DevExpress uses object-per-point
public class DataPoint {
    public double Argument { get; set; }
    public double Value { get; set; }
}

// 2. Allocate the objects (object-per-point is the binding constraint)
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
    });
// ⚠ Billions of bytes of DataPoint objects before rendering starts.
//   DevExpress markets 20M+ "without preprocessing" — short of 100M.

// 3. SwiftPlotDiagram + SwiftPlotSeriesView (the WinForms fast path)
var series = new Series("Signal", ViewType.SwiftPlot);
series.DataSource = data;
series.ArgumentDataMember = "Argument";
series.ValueDataMembers.AddRange("Value");
chartControl.Series.Add(series);
((SwiftPlotDiagram)chartControl.Diagram).EnableAxisXScrolling = true;

SwiftPlot 使用一种精简的生成算法和逐点对象数据模型。在 1 亿点时,你必须在渲染开始前分配海量 SeriesPoint 对象;实际上这远在渲染器之前就成为约束瓶颈。DevExpress 宣传无需预处理的 2000 万+ 点——远低于 1 亿。

库 5:Syncfusion——没有 WinForms 快速路径

Syncfusion 的招牌 "Fast Series"(FastLineSeries、FastLineBitmapSeries)只存在于 WPF、WinUI 和 UWP。WinForms 上根本没有 fast-series 路径——Syncfusion 自己的支持团队登记了该功能请求,并表示没有立即实现的计划。

这就只剩下没有 GPU 加速的标准 GDI+ 渲染供 WinForms 使用。在 1 亿点时这并非可行场景;Syncfusion WinForms 对于约 10 万点以下的业务仪表板是不错的选择,而非大规模科学采集。

库 6:ScottPlot——免费、CPU、抽稀

ScottPlot 是占主导地位的免费/开源 WinForms 选项。它在 CPU 上经 System.Drawing 再到 SkiaSharp 渲染,并依赖数据抽稀——将你的数据集缩减为具有代表性的可见点子集——以保持响应。

// ScottPlot — Plot 100 Million Points (WinForms, free / open-source)
// NuGet: Install-Package ScottPlot.WinForms

// 1. Allocate your data
double[] yData = new double[100_000_000];
for (int i = 0; i < yData.Length; i++)
    yData[i] = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1;

// 2. SignalPlot is the large-data path (assumes even X spacing)
formsPlot1.Plot.Add.Signal(yData);

// 3. ScottPlot renders on the CPU (System.Drawing -> SkiaSharp) and
//    relies on DECIMATION — it draws a representative subset, not all
//    100M points. Init for 10M+ runs 100+ ms; the displayed line is
//    not lossless.
formsPlot1.Refresh();

对于 1000 万+ 点,初始化需要 100+ 毫秒,且显示结果是抽稀的,而非无损。ScottPlot 维护者指出,点数组在托管与原生之间的封送是根本上限。对于适中的数据它物超所值;但它不是无损的 1 亿点引擎。

并排结果

当每个库尝试在原生 WinForms 窗体上绘制 100,000,000 个 float 值时,会发生以下情况:

FactorProEssentialsSciChartLightningChartDevExpressSyncfusionScottPlot
Native WinForms?✅ Yes❌ ElementHost✅ Yes✅ Yes✅ Yes✅ Yes
Plots 100M?✅ Lossless⚠️ via WPF, resampled✅ Lossless⚠️ object-per-point❌ No fast path⚠️ Decimated
Data loadingZero-copy pointerCopy into double[]Array copyObject-per-pointGDI+ iterationArray (managed↔native)
Library overhead~0 MB~800 MB~400 MB~2,400 MB+~copy
Render time~15 ms (GPU)via WPFFastFast (lightened)Slow (GDI+)100+ ms init
Data fidelity100% losslessResampled100%Feature-reducedLossless but slowDecimated
GPU renderingCompute shaders(WPF) DirectXDirectXGDI+/DirectXNoneCPU
Present pathDirect to hDCWPF compositorDirectDirectDirectDirect
vs WPF (same engine)~5% faster

为何 Zero-Copy + hDC 改变一切

在 WinForms 上,两个架构事实叠加。第一,当 ProEssentials 调用 UseDataAtLocation() 时,它存储指向你数组的指针而非复制它——零内存开销。第二,完成的帧被直接呈现到 hDC,没有 WPF 的纹理复制与合成开销。

好处层层递进。加载时间是即时的(指针赋值,而非遍历 1 亿个值)。内存平稳保持在你原始的 400 MB。而且呈现路径比 WPF 更短,这正是约 5% 端到端优势的来源——同一个引擎,减去合成器。

那些复制(LightningChart 的 AddSamples)、转换(double[] 存储)或包装(逐点对象)的竞争者会成倍增加每值内存。在 1 亿点时,这些倍数就是平稳 400 MB 与数 GB 之间的差别——或者是图表尚未绘制就抛出 OutOfMemoryException。

Data ModelOverhead (100M pts)Used By
Zero-copy pointer~0 MBProEssentials
Array copy (float)~400 MBLightningChart
Array copy (double)~800 MBSciChart (via WPF)
Object-per-point~2,400 MB+DevExpress
CPU decimationcopy + subsetScottPlot

算一笔账:

1 亿个 float × 4 字节 = 400 MB(你的数据)。ProEssentials 增加约 0 MB 并呈现到 hDC。同一引擎的 WPF 版本每帧增加一次纹理复制 + 合成器同步——这正是原生 WinForms 路径节省的那约 5%。

Resampling 和抽稀的代价

Telerik、ScottPlot 以及 DevExpress 的精简视图都以某种形式缩减数据——显示具有代表性的子集而非每一个点——以在大规模下保持响应。对于趋势概览,这通常在视觉上没问题。

但抽稀后的图表可能隐藏单个采样事件:一次 ECG 心律失常、一个振动共振尖峰、一次微秒级半导体异常。如果子集恰好不包含那个采样,你就永远看不到它。通过平均来"处理"数百万个点,意味着渲染的是你数据的摘要,而非你的数据。

ProEssentials 用其 Filter2D3D GPU Compute Shader 采取了不同的方法。它不丢弃点,而是在全部 1 亿个值上计算每个像素列正确的 min/max,因此每个尖峰都得以保留——无损,在 hDC 上。

在 WinForms 上绘制 1 亿个点的代价

在原生 WinForms 选项中,只有 ProEssentials 和 LightningChart 能现实地无损渲染 1 亿;DevExpress 以逐点对象在更低处止步,SciChart 没有原生控件,Syncfusion 没有快速路径,ScottPlot 进行抽稀。以下是实用总结:

ProEssentials 是唯一一个将 zero-copy 数据加载、无损 GPU Compute Shader 过滤、on-demand 渲染和直接 hDC 呈现 结合起来的 WinForms 库——而且比 WPF 上的同一引擎快约 5%,并有一个公开的 clone-and-run 仓库为证。

结论

多个库都可以在 WinForms 中尝试 1 亿个点,但其格局比 WPF 更窄。只有 ProEssentials 以零内存开销、无损保真度和直接 hDC 呈现做到了——而且比它自己的 WPF 构建更快。

SciChart 没有原生 WinForms 控件。Syncfusion 没有 WinForms 快速路径。ScottPlot 进行抽稀。DevExpress 的 SwiftPlot 仅限 WinForms 但是逐点对象。LightningChart 是真正的原生竞争者,带有数组复制和连续循环的取舍。

如果你的 WinForms 应用需要显示 1 亿个点——传感器数据、信号处理、科学采集、LiDAR——那么 hDC 耦合的 Compute Shader 路径是现有最快的无损选项,也是今天唯一一个你可以克隆并复现的选项。

WinForms 性能深入解析

完整的 hDC 耦合故事、8 个库的 WinForms 对比,以及可复现的仓库。

阅读更多
AI 代码辅助

pe_query.py 针对编译后的 DLL 二进制验证每一条属性路径——没有臆造的 API 调用。

阅读更多
定价与支持对比

主要库的 5 年 TCO。永久授权 vs 订阅,免费无限支持。

阅读更多
来源与参考

本文中所有竞争对手的代码示例和主张均来源于官方文档、公开 GitHub 仓库和供应商支持声明。请直接核实:

SciChart

LightningChart

DevExpress

Syncfusion

ComponentOne、Telerik 和 ScottPlot

亲自试试——无需账户

ProEssentials 可在 NuGet 上获取,无需注册、无需账户、无需销售通话。安装 WinForms 包,克隆 1 亿点仓库,复现本页的每一个数字。

联系 ProEssentials 团队 →

我们的任务

我们的首要目标是通过为您的机构和终端用户提供最简单、最专业的服务,达成您的成功。

我们是工程师

ProEssentials是由需要自定义图表组件的专业电气工程师创立的。加入使用ProEssentials的顶级工程公司名单。

谢谢

感谢您成为ProEssentials的客户,也感谢您研究ProEssentials图表引擎。