在WPF中绘制1亿数据点

ProEssentials、SciChart、LightningChart、Syncfusion和DevExpress的可运行代码、内存基准测试和GPU架构分析——根据供应商文档和公开源代码验证

100 million points WPF
large dataset chart
GPU compute shader
zero copy data
WPF chart performance
chart benchmark
C# charting
plot millions

在WPF中绘制1亿数据点:
5个库C#代码比较

如何在WPF折线图中显示1亿数据点?答案完全取决于您使用哪个图表库。有些用15行代码零内存开销就能处理。有些需要特殊系列类型并牺牲数据保真度。有些根本做不到。

本文为五个主要WPF图表库——ProEssentials、SciChart、LightningChart、Syncfusion和DevExpress——提供完整的、可运行的C#代码,尝试将相同的1亿float值绘制为单系列折线图。我们比较每个方面:代码行数、内存消耗、渲染时间、数据保真度,以及每个库在幕后实际对您的数据做了什么。

如果您搜索过"WPF绘制百万点"或"WPF图表性能",这就是具体的代码级答案。

亲自试试:安装ProEssentials包含一个名为GigaPrime2D的演示(附完整示例项目源代码)。该演示持续绑制1亿个数据点——每次更新都会更改所有1亿个值。代码刻意保持精简,使任何人都可以轻松接入竞争库,构建1亿数据点的实际并排比较。

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

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


测试:1亿Float值

每个库接受相同的测试:从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)
PlatformWPF (.NET 8), Windows 11, mid-range GPU
What we measureLines of code, memory overhead, render time, data fidelity

下面我们展示每个库的完整C#代码,并注释每个步骤对数据的操作。注意两点:库如何接收数据(复制vs指针),以及如何渲染(每个点vs重采样子集)。

库1:ProEssentials — 零拷贝 + GPU Compute Shader

ProEssentials使用UseDataAtLocation()将图表直接指向您现有的float数组。无复制、无转换、无重复。图表的原生DLL存储指向您内存的指针,在渲染期间读取它。结合ComputeShader = true,GPU的Compute Shader使用可能2,000+个着色器核心构建图表图像——CPU从不遍历1亿值。

// ProEssentials — Plot 100 Million Points (WPF, ~15 lines)
// NuGet: Install-Package Gigasoft.ProEssentials

// 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;
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
Pesgo1.PeFunction.ReinitializeResetImage();

关键代码是UseDataAtLocation(yData, yData.Length)。这不是复制操作——是指针赋值。图表就地读取您的数组。使用Filter2D3D = true,ProEssentials在渲染管线中插入一个预备GPU Compute Shader,在最终渲染着色器运行之前执行无损min/max过滤。与SciChart和DevExpress使用的激进视口宽度重采样——可将1亿点减少到几千个——不同,ProEssentials的过滤器故意保守:系列在过滤后始终保留至少500,000个点,因为当过滤着色器和渲染着色器都在GPU上以微秒级执行时,没有理由丢弃超过必要的数据。结果是保留信号中每个峰值、谷值和瞬态的无损表示。

Force3dxVerticeRebuildForce3dxNewColors标志告诉Direct3D引擎从源数组重建顶点和颜色数据。如果您之后修改yData中的值并在调用ReinitializeResetImage()之前再次设置这些标志,图表会立即反映更改,因为它在读取相同的内存。整个设置15行代码。无需特殊系列类型——处理100个点的同一个Pesgo(Scientific Graph Object)处理1亿个。ProEssentials自带的示例115在5个系列中绘制2,000万点(总计1亿),以交互速度渲染——每系列2,000万对Compute Shader管线来说根本不算什么。

结果:

~15行C#。~0MB内存开销。~15ms渲染时间。通过GPU Compute Shader的无损min/max过滤。10个点或1亿个点使用相同的Pesgo控件。

库2:SciChart — 数组复制 + 重采样

SciChart可以显示1亿点,但路径涉及两个重大权衡。首先,dataSeries.Append()将全部1亿值复制到SciChart的内部存储为double[]——将内存从float(4字节)翻倍到double(8字节)。其次,SciChart的渲染管线将数据下采样到大约2倍视口宽度(1920像素显示器上约3,840个点)。

// SciChart — Plot 100 Million Points (WPF)
// NuGet: Install-Package SciChart, SciChart.Wpf

// 1. Allocate your data
double[] yData = new double[100_000_000];  // Note: SciChart uses double[], not float[]
double[] xData = new double[100_000_000];
for (int i = 0; i < yData.Length; i++) {
    xData[i] = i;
    yData[i] = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1;
}

// 2. Create data series — copies all 100M values into internal storage
var dataSeries = new XyDataSeries<double, double>();
dataSeries.Append(xData, yData);  // ~800 MB internal copy (float→double + overhead)

// 3. Configure resampling — without this, 100M points will not render
var lineSeries = new FastLineRenderableSeries();
lineSeries.DataSeries = dataSeries;
lineSeries.ResamplingMode = ResamplingMode.Auto;  // downsamples to ~2×viewport width

// 4. Add to chart surface
sciChartSurface.RenderableSeries.Add(lineSeries);

ResamplingMode.Auto设置使SciChart能处理1亿点。没有它,GPU管线需要处理1亿数据点的顶点缓冲区——这不是SciChart的游戏引擎架构的工作方式。相反,CPU端重采样器选择代表性子集(使用每像素桶的min/max),只有该子集被发送到GPU进行渲染。

这意味着SciChart渲染1亿中的约3,840个点——约占数据的0.004%。在完全缩小的视觉概览中,这通常是可以接受的。但如果您放大到10小时录音的1秒窗口,重采样器会在CPU上重新扫描相关范围以生成新的代表性子集。原始1亿值存储在SciChart的内部double[]数组中消耗~800MB,等待按需重采样。

库3:LightningChart — 数组复制 + 特殊系列

LightningChart可以处理1亿点,但仅通过其SampleDataSeries——专为固定间隔(均匀采样)数据设计的特殊系列类型。标准系列类型(PointLineSeries、FreeformPointLineSeries)无法处理此规模。数据通过AddSamples(float[])方法复制到内部缓冲区,LightningChart的DirectX即时模式渲染器随后处理。更新的变体SampleDataBlockSeries(v10.1+)为流式/扫描场景提供更好的性能。

// LightningChart — Plot 100 Million Points (WPF)
// NuGet: Install-Package Arction.LightningChart.Ultimate

// 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 chart for large data — must use Non-Bindable edition for best performance
lightningChart.BeginUpdate();
lightningChart.ViewXY.XAxes[0].SetRange(0, 100_000_000);

// 3. Create SampleDataSeries (fixed-interval data) — standard PointLineSeries can't scale
var series = new SampleDataSeries(lightningChart.ViewXY, lightningChart.ViewXY.XAxes[0],
    lightningChart.ViewXY.YAxes[0]);
series.FirstSampleTimeStamp = 0;
series.SamplingFrequency = 1;  // 1 sample per X unit (sequential)

// 4. Add all 100M samples — copies float[] into internal buffer
series.AddSamples(yData, false);

// 5. Add series to chart
lightningChart.ViewXY.SampleDataSeries.Add(series);
lightningChart.EndUpdate();

// Note: SampleDataBlockSeries (v10.1+) is the newer, faster alternative
//       for streaming/sweeping data. Both require Non-Bindable edition
//       for best performance — MVVM data binding is limited.

关键限制:为获得最佳性能,LightningChart需要Non-Bindable版本。LightningChart的WPF产品提供Bindable和Non-Bindable版本——Non-Bindable版本更快且支持多线程,但牺牲了WPF的MVVM数据绑定基础设施。如果您的应用使用WPF的数据绑定模式(如大多数现代WPF应用),您面临性能与架构兼容性之间的权衡。

LightningChart通过DirectX管线无损渲染全部1亿点。权衡是数组复制(~400MB额外内存)、必须使用特殊系列类型、Non-Bindable版本建议,以及LightningChart的连续渲染循环即使图表空闲也保持GPU活跃。

库4:Syncfusion — 逐对象数据架构

Syncfusion的WPF图表使用逐点对象数据模型。每个数据值必须包装在类实例中(通常有X和Y属性)并添加到IEnumerable集合。在1亿点时,这意味着创建1亿个.NET对象——每个有24字节对象头(64位上)、两个double属性(16字节)和填充。总计:在原始400MB源数据之上约2.4GB的托管堆分配。

// Syncfusion — Plot 100 Million Points (WPF)
// NuGet: Install-Package Syncfusion.SfChart.WPF

// 1. Create data model class — Syncfusion requires object-per-point
public class DataPoint {
    public double X { get; set; }
    public double Y { get; set; }
}

// 2. Allocate 100 million objects (~2.4 GB+ with object headers)
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
    });
// ⚠ This loop alone takes minutes and will likely trigger OutOfMemoryException
//    Syncfusion's practical ceiling is ~1 million points

// 3. Use FastLineBitmapSeries for best performance
var series = new FastLineBitmapSeries();
series.ItemsSource = data;
series.XBindingPath = "X";
series.YBindingPath = "Y";
chart.Series.Add(series);

实际上,这个分配循环需要数分钟,通常在完成前触发OutOfMemoryException,特别是在32位进程或内存有限的系统上。Syncfusion响应式图表的实际上限约为使用FastLineBitmapSeries的100万点。该库非常适合适中数据量的商业仪表板,但不是为1亿点的科学/工程场景设计的。

库5:DevExpress — 逐点对象 + 重采样

DevExpress WPF使用LineSeries2D配合AllowResample = true处理大数据。注意:在性能讨论中经常被引用的DevExpress SwiftPlotSeriesView仅限WinForms,在WPF图表控件中不存在。在WPF中,AllowResample(v20.1引入)启用视口感知重采样,将渲染限制在可见范围。DevExpress自己的博客文章测试了该方法到5,000万点。然而数据模型仍然是逐点对象——在1亿时,渲染开始前必须分配~2.4GB的DataPoint对象。

// DevExpress — Plot 100 Million Points (WPF)
// NuGet: Install-Package DevExpress.Wpf.Charts

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

// 2. Allocate 100 million objects (~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 {
        Argument = i,
        Value = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1
    });
// ⚠ Minutes of allocation, ~2.4 GB managed heap
//    DevExpress tested AllowResample up to 50M (v20.1 blog post)

// 3. Use LineSeries2D with AllowResample (resamples to viewport)
var series = new LineSeries2D();
series.DataSource = data;
series.ArgumentDataMember = "Argument";
series.ValueDataMember = "Value";
series.AllowResample = true;   // critical — resamples to viewport width
series.MarkerVisible = false;  // required for large-data performance
series.LineStyle.Thickness = 1;

// 4. Add to XYDiagram2D
var diagram = (XYDiagram2D)chartControl.Diagram;
diagram.Series.Add(series);

// Note: SwiftPlotSeriesView is WinForms-only — NOT available in WPF.
//       WPF uses LineSeries2D + AllowResample for large data.

在1亿点时,在有足够RAM的64位系统上创建1亿个.NET对象勉强可行(包括源数据总共~2.8GB)。如果分配成功,AllowResample将仅渲染视口子集——类似于SciChart的重采样方法。但与SciChart(在平面数组中存储原始double)不同,DevExpress存储带24字节头的包装对象,使每点内存成本约高6倍。DevExpress测试的上限是5,000万;1亿在其发布的基准测试中是未经测试的领域。

结果并排对比

当每个库尝试绘制1亿float值时发生的情况:

FactorProEssentialsSciChartLightningChartSyncfusionDevExpress
Plots 100M?✅ Yes — natively⚠️ With resampling⚠️ Specific series type❌ OOM at ~16M⚠️ ~50M tested (resample)
Lines of C#~15~18~22N/A (OOM)~22
Data loadingZero-copy pointerFull array copyArray copy (AddSamples)Object-per-pointObject-per-point
Internal data typefloat (your array)double (internal copy)float (internal buffer)object wrapperobject wrapper
Memory: your data400 MB400 MB400 MB400 MB400 MB
Memory: library overhead~0 MB~800 MB~400 MB~2,400 MB+~2,400 MB+
Total memory~400 MB~1,200 MB~800 MBOOM crash~2,800 MB (borderline)
Load timeInstant (pointer)Seconds (copy)Sub-second (memcpy)Minutes / crashMinutes (100M objects)
Render time~15 ms (GPU compute)Fast (resampled subset)Fast (SampleDataSeries)Fast (resampled subset)
Data fidelity100% — lossless GPU filter~0.004% — resampled100%Resampled (viewport only)
Special series type?No — same PesgoResamplingMode requiredSampleDataSeriesFastLineBitmapSeriesLineSeries2D + AllowResample
MVVM compatible?YesYesNo (non-bindable)YesYes
GPU renderingCompute shadersGame-engine pipelineDirectX immediateCPU onlyDirectX (optional)

零拷贝为何改变一切

上面的内存比较揭示了根本的架构差异。当ProEssentials调用UseDataAtLocation(yData, yData.Length)时,它存储一个指针——x64上8字节。图表的原生DLL在渲染期间直接读取float[]。无float到double的转换。无中间DataSeries对象。无包装每个值的托管集合。

这带来了超越内存节省的连锁好处。加载时间是即时的(指针赋值vs遍历1亿值)。GC压力为零(无托管分配意味着渲染期间无垃圾回收暂停)。如果您有两个显示相同数据不同视图的图表——如3D曲面和2D等高线——两者都可以对同一数组调用UseDataAtLocation。两个图表,一个数组,零重复。

SciChart的Append()模型将float[]复制到内部double[]——将每值内存增加三倍(源4字节→内部8字节+开销)。LightningChart的AddSamples()将数组复制到内部缓冲区。Syncfusion和DevExpress将每个值包装在带24字节头的对象中。这些是内置到每个库数据管线中的架构决策,不是您可以更改的配置选项。

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

计算:

1亿float × 4字节 = 400MB(您的数据)。ProEssentials增加~0MB。SciChart增加~800MB。LightningChart增加~400MB。Syncfusion/DevExpress增加~2,400MB——如果没有先崩溃的话。

重采样的代价

SciChart和DevExpress都使用重采样作为实用的工程权衡:显示数据的代表性子集而非全部渲染。在1920像素宽的图表上,最多有1,920个水平像素列。将1亿点渲染到1,920列意味着~52,083个点竞争每个像素。SciChart的重采样器选择每桶的min和max值,产生约3,840个代表性点——足以保留数据的视觉包络。DevExpress的AllowResample使用类似的视口限制方法。

对于概览显示("显示整个数据集"),这通常在视觉上与无损渲染无法区分。问题出现在精度重要时:科学数据中的异常检测、信号处理中的精确峰值识别,或医疗/制药记录中每个样本必须可验证的监管合规。如果1个样本的瞬态出现在第47,291,033点,将52,083个点减少到每像素列2个代表的重采样器可能选择也可能不选择该特定点。

ProEssentials通过其Filter2D3D GPU Compute Shader采取了根本不同的方法。Filter2D3D着色器不是将1亿点激进地减少到少数屏幕宽度代表,而是在GPU上执行保守的、无损的min/max预过滤——结果始终保留至少500,000个点。逻辑很简单:当过滤着色器和最终渲染着色器都在GPU上以微秒级执行时,没有理由激进地丢弃数据。过滤器保留每个过滤窗口内的每个min和max,因此瞬态、尖峰和异常永远不会被丢弃。它是根本无损的——渲染图像与从完整数据集生成的完全相同。LightningChart也通过DirectX管线无损渲染,但需要数组复制(~400MB开销)和连续渲染循环。ProEssentials以零内存开销和按需渲染实现相同的无损结果。

绘制1亿点的成本

四个库可以尝试渲染1亿点,尽管只有三个在该规模上经过测试。以下是每个的成本——包括许可模式,因为一个能处理1亿点但5年花费$288,750的库与一次性花费$11,999的是不同的提案:

FactorProEssentialsSciChartLightningChartDevExpress
License (1 dev)$4,799 perpetual~$1,749 / year~$5,775 / year~$1,799 / year
10 devs × 5 years$11,999 total~$87,450~$288,750~$89,950
Account to evaluate?NoYesYes (name + phone)Yes
NuGet install1 public package3–5 packages1 packagePrivate feed
Deploy size5–8 MB15–25 MB80–150 MB20–40 MB
SupportFree, unlimited, lifetime10 tickets / year2–10 tickets / yearSubscription-tier support
Tested at 100M?YesYes (via resampling)Yes50M max (their blog)

ProEssentials是唯一将零拷贝数据加载、无损GPU Compute Shader过滤、按需帧模型、永久许可和免费无限支持结合在一起的库——仅为能处理该数据集的其他库订阅成本的一小部分。

底线

四个库可以在WPF中尝试1亿点。只有ProEssentials以零内存开销、无损GPU过滤和按需渲染做到——用15行代码。SciChart通过重采样处理该规模(快但有损)。LightningChart通过特殊系列类型处理(无损但需要数组复制、Non-Bindable版本、连续GPU)。DevExpress可以用AllowResample尝试(重采样但有2.4GB对象开销——自家基准测试中超过5,000万未测试)。

Syncfusion不是为此场景设计的。其逐点对象架构在远低于1亿时就达到内存限制,自家论坛记录了在1,600万点时的内存不足崩溃。DevExpress面临相同的逐点对象瓶颈但通过视口重采样部分缓解——如果初始分配成功。两个库都在100万点以下的数据集的商业仪表板中表现出色,其广泛的UI控件套件提供了专用图表库所没有的价值。

如果您的应用需要显示1亿点——传感器数据、信号处理、科学采集、工业监控——上面的代码精确展示了每个库需要什么。复制它,运行它,测量它。数字不言自明。

GPU架构深度分析

Compute Shader、按需渲染和零拷贝数据加载在底层如何工作。

了解更多
AI代码辅助

pe_query.py针对编译的DLL二进制文件验证每个属性路径——没有幻觉的API调用。

了解更多
定价与支持比较

五个库的10名开发者5年TCO。永久vs订阅许可。

了解更多
来源与参考

本文中所有竞争对手代码示例均根据官方文档、公开GitHub仓库和供应商论坛帖子构建。API名称、方法签名和架构声明均根据下列来源验证。链接截至2026年2月。

SciChart

LightningChart

Syncfusion

DevExpress

亲自试试——无需账户

ProEssentials在NuGet上可用,无需注册、无需创建账户、无需销售电话。安装包,粘贴上面的代码,运行它。如果有问题,支持免费、无限制,由构建引擎的开发者直接提供。

联系ProEssentials团队 →

我们的任务

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

我们是工程师

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

谢谢

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