

如何在 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.
在看代码之前,先看一个重新定义全局的架构要点。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 合成器。
每个库都接受相同的测试:在原生 WinForms 窗体上,从 1 亿个连续 float 值渲染一个单系列折线图。我们测量超出源数组的内存开销、渲染时间,以及该库是显示每一个点还是显示降采样后的近似。
| 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 | WinForms (.NET 8), Windows 11, mid-range GPU |
| What we measure | Lines of code, memory overhead, render time, data fidelity |
下面我们展示每个库的完整 C# 代码,并标注每一步中你的数据发生了什么。请尤其关注一件事:这 1 亿个值是被复制、被转换,还是被包装成对象——以及图表是直接呈现到窗口,还是经过一个 interop 层。
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 是唯一会存在的副本;图表增加的开销基本为零。
Force3dxVerticeRebuild 和 Force3dxNewColors 标志告诉 Direct3D 引擎在 GPU 上重建顶点和颜色。配合 ComputeShader = true 和 Filter2D3D = true,GPU 预过滤器会计算每个像素列的无损 min/max,因此不会丢失任何尖峰——结果在没有 WPF 合成器环节的情况下呈现到 hDC。
约 15 行 C#。约 0 MB 内存开销。约 15 ms 渲染时间,比 WPF 上的相同代码快约 5%。通过 GPU Compute Shader 进行无损 min/max 过滤,直接呈现到 WinForms hDC。
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 而非代码示例的形式呈现。
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 竞争者。差异归结于数组复制、连续循环功耗,以及激活/许可——而不在于它能否渲染这些点。
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 亿。
Syncfusion 的招牌 "Fast Series"(FastLineSeries、FastLineBitmapSeries)只存在于 WPF、WinUI 和 UWP。WinForms 上根本没有 fast-series 路径——Syncfusion 自己的支持团队登记了该功能请求,并表示没有立即实现的计划。
这就只剩下没有 GPU 加速的标准 GDI+ 渲染供 WinForms 使用。在 1 亿点时这并非可行场景;Syncfusion WinForms 对于约 10 万点以下的业务仪表板是不错的选择,而非大规模科学采集。
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 值时,会发生以下情况:
| Factor | ProEssentials | SciChart | LightningChart | DevExpress | Syncfusion | ScottPlot |
|---|---|---|---|---|---|---|
| Native WinForms? | ✅ Yes | ❌ ElementHost | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Plots 100M? | ✅ Lossless | ⚠️ via WPF, resampled | ✅ Lossless | ⚠️ object-per-point | ❌ No fast path | ⚠️ Decimated |
| Data loading | Zero-copy pointer | Copy into double[] | Array copy | Object-per-point | GDI+ iteration | Array (managed↔native) |
| Library overhead | ~0 MB | ~800 MB | ~400 MB | ~2,400 MB+ | — | ~copy |
| Render time | ~15 ms (GPU) | via WPF | Fast | Fast (lightened) | Slow (GDI+) | 100+ ms init |
| Data fidelity | 100% lossless | Resampled | 100% | Feature-reduced | Lossless but slow | Decimated |
| GPU rendering | Compute shaders | (WPF) DirectX | DirectX | GDI+/DirectX | None | CPU |
| Present path | Direct to hDC | WPF compositor | Direct | Direct | Direct | Direct |
| vs WPF (same engine) | ~5% faster | — | — | — | — | — |
在 WinForms 上,两个架构事实叠加。第一,当 ProEssentials 调用 UseDataAtLocation() 时,它存储指向你数组的指针而非复制它——零内存开销。第二,完成的帧被直接呈现到 hDC,没有 WPF 的纹理复制与合成开销。
好处层层递进。加载时间是即时的(指针赋值,而非遍历 1 亿个值)。内存平稳保持在你原始的 400 MB。而且呈现路径比 WPF 更短,这正是约 5% 端到端优势的来源——同一个引擎,减去合成器。
那些复制(LightningChart 的 AddSamples)、转换(double[] 存储)或包装(逐点对象)的竞争者会成倍增加每值内存。在 1 亿点时,这些倍数就是平稳 400 MB 与数 GB 之间的差别——或者是图表尚未绘制就抛出 OutOfMemoryException。
| Data Model | Overhead (100M pts) | Used By |
|---|---|---|
| Zero-copy pointer | ~0 MB | ProEssentials |
| Array copy (float) | ~400 MB | LightningChart |
| Array copy (double) | ~800 MB | SciChart (via WPF) |
| Object-per-point | ~2,400 MB+ | DevExpress |
| CPU decimation | copy + subset | ScottPlot |
1 亿个 float × 4 字节 = 400 MB(你的数据)。ProEssentials 增加约 0 MB 并呈现到 hDC。同一引擎的 WPF 版本每帧增加一次纹理复制 + 合成器同步——这正是原生 WinForms 路径节省的那约 5%。
Telerik、ScottPlot 以及 DevExpress 的精简视图都以某种形式缩减数据——显示具有代表性的子集而非每一个点——以在大规模下保持响应。对于趋势概览,这通常在视觉上没问题。
但抽稀后的图表可能隐藏单个采样事件:一次 ECG 心律失常、一个振动共振尖峰、一次微秒级半导体异常。如果子集恰好不包含那个采样,你就永远看不到它。通过平均来"处理"数百万个点,意味着渲染的是你数据的摘要,而非你的数据。
ProEssentials 用其 Filter2D3D GPU Compute Shader 采取了不同的方法。它不丢弃点,而是在全部 1 亿个值上计算每个像素列正确的 min/max,因此每个尖峰都得以保留——无损,在 hDC 上。
在原生 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 路径是现有最快的无损选项,也是今天唯一一个你可以克隆并复现的选项。
本文中所有竞争对手的代码示例和主张均来源于官方文档、公开 GitHub 仓库和供应商支持声明。请直接核实:
SciChart
LightningChart
DevExpress
Syncfusion
ComponentOne、Telerik 和 ScottPlot
ProEssentials 可在 NuGet 上获取,无需注册、无需账户、无需销售通话。安装 WinForms 包,克隆 1 亿点仓库,复现本页的每一个数字。
联系 ProEssentials 团队 →我们的首要目标是通过为您的机构和终端用户提供最简单、最专业的服务,达成您的成功。
ProEssentials是由需要自定义图表组件的专业电气工程师创立的。加入使用ProEssentials的顶级工程公司名单。
感谢您成为ProEssentials的客户,也感谢您研究ProEssentials图表引擎。