

1억 개의 포인트는 대규모를 위해 만들어진 차트 라이브러리와 대시보드를 위해 만들어진 차트 라이브러리를 갈라놓는 스트레스 테스트입니다. WinUI에서 그 구분은 뚜렷한데, 대부분의 WinUI 차트가 XAML 계층을 통해 그리는 매니지드 .NET 컨트롤인 반면, ProEssentials는 그 아래에 네이티브 Direct3D 및 Direct2D C++ 엔진을 두고 있기 때문입니다.
저희는 동일한 1억 포인트 신호를 다섯 개의 WinUI 라이브러리, 즉 ProEssentials, Syncfusion, Telerik, DevExpress, ComponentOne에서 플롯했습니다. 아래 코드는 각 라이브러리에서 차트에 이르는 가장 짧고 정직한 경로입니다. 진짜 차이는 코드 줄 수가 아니라, 데이터가 화면에 도달하기까지 무슨 일이 벌어지는가입니다.
ProEssentials는 float 배열을 제자리에서 읽어 GPU에서 지오메트리를 재구성합니다. 네 개의 매니지드 제품군은 포인트당 하나씩 객체 컬렉션에 바인딩하므로, 동일한 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.
동일한 데이터, 동일한 머신, 다섯 개 라이브러리. 저희는 코드 줄 수, 메모리 오버헤드, 렌더링 여부, 그리고 표시가 손실이 없는지 아니면 다운샘플링된 근사치인지를 측정합니다.
| 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 |
아래 패턴은 다섯 개 라이브러리 전반에서 반복됩니다. 데이터를 로드하는 코드가 차트를 설정하는 코드보다 훨씬 더 중요합니다.
차트는 UseDataAtLocation을 통해 기존 float 배열을 읽으므로 두 번째 복사는 전혀 일어나지 않습니다. 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();표시(presentation)도 네이티브입니다. 엔진은 자체 DXGI flip-model composition swap chain에 렌더링한 뒤 ISwapChainPanelNative::SetSwapChain을 통해 XAML 호스트에 전달하므로, DWM이 중간 비트맵이나 프레임당 CPU 복사 없이 차트를 여러분의 비주얼 트리에 바로 합성합니다. 또한 SetMaximumFrameLatency(1)로 표시 큐를 제한하는데, 이것이 1억 포인트 차트가 프레임을 앞서서 버퍼링하지 않고 드래그를 따라가는 이유입니다.
차트가 추가하는 메모리는 작은 GPU 스테이징 버퍼뿐입니다. 400 MB의 데이터는 그대로 400 MB로 유지되며, 동일한 PesgoWinUI 컨트롤이 특별한 시리즈 유형 없이 10,000개 포인트든 1억 개든 처리합니다.
이어지는 네 가지 예제는 각 공급업체의 문서화된 API에 충실하며, 컴파일도 됩니다. 다만 차트를 그려 주지는 않습니다. 그중 어느 것이든 1억 개의 값을 가리키게 하면 현실적인 결과는 할당 루프 어딘가에서 발생하는 OutOfMemoryException이거나, 끝나기 전에 디버거를 강제 종료하게 될 만큼 긴 대기 시간입니다. 저희가 이 코드를 포함한 것은 차이를 구체적으로 보여 주기 때문이지, 누군가 이 규모로 실행하리라 기대해서가 아닙니다. 각 코드 조각에서 시사하는 바가 큰 부분은 하단의 차트 설정이 아니라, 상단에서 데이터 포인트마다 객체 하나를 할당하는 루프입니다.
성능 주장을 읽으실 때 이 점을 염두에 두실 필요가 있습니다. 네 곳 모두 자사 차트를 고성능으로 마케팅하며, 실제로 수치를 공개하는 플랫폼에서는 그것이 정당한 주장입니다. 문제는 그 수치들이 WinUI 수치가 아니라는 점입니다.
이 수치에 관한 참고 사항. 네 개 공급업체 중 어느 곳도 자사 WinUI 차트에 특화된 대용량 데이터 벤치마크를 공개하지 않습니다. Syncfusion의 100만 포인트 벤치마크는 WPF 기준입니다. DevExpress의 가장 큰 수치는 WinForms 전용 시리즈 뷰인 SwiftPlotSeriesView에 의존합니다. ComponentOne이 공개한 Direct2D 수치는 .NET 6 WinForms 빌드에서 약 5 ms에 50,000개 포인트입니다. Telerik의 대용량 데이터셋에 대한 가이드는 바인딩하기 전에 데이터를 집계하여 줄이라는 것입니다. 저희는 이들의 WinUI 컨트롤을 직접 벤치마크하지 않았으므로 그들에 대한 한계치를 지어내지는 않겠습니다. 다만 말씀드릴 수 있는 것은, 이들 중 어느 것에 대해서도 WinUI 대용량 데이터 성능에 관한 공개된 근거가 아직 존재하지 않는다는 점입니다.
기준을 잡기 위해 저희 자체 한계를 말씀드리겠습니다. ProEssentials의 Direct2D 경로는 약 300만 포인트부터 부담을 느끼기 시작하는데, 이는 이미 zero-copy 로딩과 포인트당 객체 오버헤드가 없는 상태에서입니다. 그것을 1억까지 끌어올리는 것은 Direct3D compute-shader 경로입니다. 포인트마다 객체를 할당하는 매니지드 XAML 컨트롤은 그보다 훨씬 뒤처진 지점에서 시작합니다.
Syncfusion의 fast series는 선을 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 — 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억 개의 바인딩된 객체는 픽셀 하나가 그려지기도 전에 수 기가바이트의 헤더와 참조를 지닙니다.
| 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 |
이 규모에서 모든 것을 좌우하는 수치는 초당 프레임 수가 아니라, 데이터가 화면에 도달하기까지 몇 번이나 복사되는가입니다.
ProEssentials는 데이터를 한 번도 복사하지 않습니다. 이미 할당하신 float 배열을 읽어 GPU에서 그림을 재구성합니다. 컬렉션에 바인딩하는 매니지드 차트는 각 값을 헤더와 참조가 붙은 객체로 복사하는데, 바로 여기서 수 기가바이트가 발생합니다.
수천 개의 포인트에서는 아무도 눈치채지 못합니다. 하지만 100 million에서는 그 하나의 설계 선택이 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는 애초에 그 문제를 만들지 않습니다.
다섯 개 라이브러리 모두 멋진 선 차트를 그립니다. 하지만 데이터를 다운샘플링하거나, 시리즈 유형을 바꾸거나, 수 기가바이트의 힙을 감수하도록 강요하지 않고 1억 개의 포인트를 손실 없이 실시간으로 그리는 것은 단 하나뿐입니다. 이것이 데이터를 이미 존재하는 위치에서 읽어 들이는 네이티브 엔진의 결실입니다.
WinUI 앱이 수천 개의 포인트를 표시한다면 이미 보유하신 어떤 제품군을 선택하셔도 됩니다. 수백만에서 수억 개의 포인트를 표시한다면 데이터 모델이 전부이며, ProEssentials가 바로 그것을 위해 만들어진 제품입니다.
경쟁 제품의 렌더링 방식과 바인딩 API는 각 공급업체의 자체 문서에서 인용했습니다:
Syncfusion (WinUI SfCartesianChart)
Telerik (UI for WinUI RadChart)
DevExpress (WinUI ChartControl)
ComponentOne / MESCIUS (FlexChart for WinUI)
얼마나 많은 포인트를 플로팅해야 하는지, 그리고 그것이 어떻게 업데이트되는지 알려주십시오. 엔진을 직접 만든 개발자가 ProEssentials가 적합한지 솔직하게 말씀드립니다.
Contact us귀사의 조직과 최종 사용자들에게 가장 쉽고 가장 전문적인 혜택을 제공함으로써 귀사께서 성공하시는 것이 당사의 최우선 목표입니다.
프로에센셜은 자체 차트 컴포넌트가 필요한 전기 공학 전문가들로부터 태어났습니다. 프로에센셜을 사용하는 탑 엔지니어링 기업들 명단에 참여히세요.
프로에센셜 고객이 되어주셔서 감사드리며, 프로에센셜 차트 제작 엔진을 연구해주셔서 감사드립니다.