Skip to content

Commit 2844394

Browse files
authored
Swinging door compression: fix compression to avoid unnecessary datapoints (#60)
* Test data * Show trend1 graph with current implementation * Revert "Show trend1 graph with current implementation" This reverts commit 6de4573. * Tests to reproduce the missed compression * Project hygiene * Optimized CloseTheDoor ``` | Method | Mean | Error | StdDev | Ratio | RatioSD | Code Size | |--------- |---------:|----------:|----------:|------:|--------:|----------:| | Current | 4.549 ns | 0.1294 ns | 0.1976 ns | 1.00 | 0.00 | 233 B | | Inlined | 2.993 ns | 0.0969 ns | 0.1294 ns | 0.66 | 0.04 | 178 B | | Inlined1 | 1.915 ns | 0.0748 ns | 0.1208 ns | 0.42 | 0.04 | 129 B | | Inlined2 | 1.219 ns | 0.0608 ns | 0.1016 ns | 0.27 | 0.03 | 102 B | ``` `Inlined2` is implemented now. * Eliminate one branch Cf. #60 (comment) * Fixed SwingingDoorCompression * Cleanup (a little bit) * NoCompressionTests updated * ArrayBuilder better register usage * seal members * DeadBandCompression fixed * Clean up unused arguments and made state machine use constants * Code style: add braces to switch cases * Fixed buildj + XML comment * CI fix
1 parent 246ed95 commit 2844394

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1032
-519
lines changed

Directory.Build.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<Project>
22
<PropertyGroup>
33
<VersionMajor Condition="'$(VersionMajor)' == ''">2</VersionMajor>
4-
<VersionMinor Condition="'$(VersionMinor)' == ''">1</VersionMinor>
4+
<VersionMinor Condition="'$(VersionMinor)' == ''">2</VersionMinor>
55
<VersionPatch Condition="'$(VersionPatch)' == ''">0</VersionPatch>
6-
<BuildNumber Condition="'$(BuildNumber)' == ''">137</BuildNumber>
6+
<BuildNumber Condition="'$(BuildNumber)' == ''">150</BuildNumber>
77
<VersionSuffix Condition="'$(Configuration)' == 'Debug' and '$(VersionSuffix)' == ''">dev</VersionSuffix>
88
<Authors>gfoidl</Authors>
99
<Company>Foidl Günther</Company>

data/swinging-door/trend1.plt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
reset
2+
3+
#set terminal dumb
4+
5+
set grid
6+
set title 'Swinging door compression -- Trend1'
7+
set xlabel 'Time'
8+
set ylabel 'Value'
9+
10+
set xrange [0:7.5]
11+
set yrange [0:2.5]
12+
13+
set style line 2 lc rgb 'green' pt 9 # triangle
14+
15+
#set datafile separator ";"
16+
17+
# replot is also possible for the second plot
18+
plot 'trend1_raw.csv' with linespoints title 'raw', \
19+
'trend1_compressed.csv' with points ls 2 title 'compressed'
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#x y
2+
0 1
3+
2 1.2
4+
4 2
5+
6 2
6+
7 1.2

data/swinging-door/trend1_raw.csv

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#x y
2+
0 1
3+
1 1.1
4+
2 1.2
5+
3 1.6
6+
4 2
7+
5 2
8+
6 2
9+
7 1.2

data/swinging-door/trend2.plt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
reset
2+
3+
#set terminal dumb
4+
5+
set grid
6+
set title 'Swinging door compression -- Trend2'
7+
set xlabel 'Time'
8+
set ylabel 'Value'
9+
10+
set xrange [0:7.5]
11+
set yrange [0:2.5]
12+
13+
set style line 2 lc rgb 'green' pt 9 # triangle
14+
15+
#set datafile separator ";"
16+
17+
# replot is also possible for the second plot
18+
plot 'trend2_raw.csv' with linespoints title 'raw', \
19+
'trend2_compressed.csv' with points ls 2 title 'compressed'
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#x y
2+
0 1
3+
2 1.2
4+
4 2
5+
7 2

data/swinging-door/trend2_raw.csv

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#x y
2+
0 1
3+
1 1.1
4+
2 1.2
5+
3 1.6
6+
4 2
7+
5 2
8+
6 2
9+
7 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// (c) gfoidl, all rights reserved
2+
3+
using System.Diagnostics;
4+
using System.Runtime.CompilerServices;
5+
using BenchmarkDotNet.Attributes;
6+
using Algorithm = gfoidl.DataCompression.SwingingDoorCompression;
7+
8+
namespace gfoidl.DataCompression.Benchmarks
9+
{
10+
[DisassemblyDiagnoser]
11+
public class SwingingDoorCompressionIteratorCloseTheDoorBenchmarks
12+
{
13+
private readonly Algorithm _swingingDoorCompression = new(1d);
14+
private (double Max, double Min) _slope = (double.PositiveInfinity, double.NegativeInfinity);
15+
16+
private DataPoint _lastArchived = (1d, 0.5);
17+
private DataPoint _incoming = (2d, 2d);
18+
//---------------------------------------------------------------------
19+
[Benchmark(Baseline = true)]
20+
public void Current() => this.Current(_incoming, _lastArchived);
21+
22+
[Benchmark]
23+
public void Inlined() => this.Inlined(_incoming, _lastArchived);
24+
25+
[Benchmark]
26+
public void Inlined1() => this.Inlined1(_incoming, _lastArchived);
27+
28+
[Benchmark]
29+
public void Inlined2() => this.Inlined2(_incoming, _lastArchived);
30+
//---------------------------------------------------------------------
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
protected void Current(in DataPoint incoming, in DataPoint lastArchived)
33+
{
34+
Debug.Assert(_swingingDoorCompression is not null);
35+
36+
double upperSlope = lastArchived.Gradient(incoming, _swingingDoorCompression.CompressionDeviation);
37+
double lowerSlope = lastArchived.Gradient(incoming, -_swingingDoorCompression.CompressionDeviation);
38+
39+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
40+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
41+
}
42+
//---------------------------------------------------------------------
43+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44+
protected void Inlined(in DataPoint incoming, in DataPoint lastArchived)
45+
{
46+
Debug.Assert(_swingingDoorCompression is not null);
47+
48+
double delta_x = incoming.X - lastArchived.X;
49+
50+
double upperSlope;
51+
double lowerSlope;
52+
53+
if (delta_x == 0)
54+
{
55+
upperSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
56+
lowerSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
57+
}
58+
else
59+
{
60+
double delta_y = incoming.Y - lastArchived.Y;
61+
double delta_yUpper = delta_y + _swingingDoorCompression.CompressionDeviation;
62+
double delta_yLower = delta_y - _swingingDoorCompression.CompressionDeviation;
63+
64+
upperSlope = delta_yUpper / delta_x;
65+
lowerSlope = delta_yLower / delta_x;
66+
}
67+
68+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
69+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
70+
}
71+
//---------------------------------------------------------------------
72+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
73+
protected void Inlined1(in DataPoint incoming, in DataPoint lastArchived)
74+
{
75+
Debug.Assert(_swingingDoorCompression is not null);
76+
77+
double delta_x = incoming.X - lastArchived.X;
78+
79+
if (delta_x == 0)
80+
{
81+
GradientEquality(incoming, lastArchived);
82+
return;
83+
}
84+
85+
double delta_y = incoming.Y - lastArchived.Y;
86+
double delta_yUpper = delta_y + _swingingDoorCompression.CompressionDeviation;
87+
double delta_yLower = delta_y - _swingingDoorCompression.CompressionDeviation;
88+
89+
double upperSlope = delta_yUpper / delta_x;
90+
double lowerSlope = delta_yLower / delta_x;
91+
92+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
93+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
94+
95+
[MethodImpl(MethodImplOptions.NoInlining)]
96+
void GradientEquality(in DataPoint incoming, in DataPoint lastArchived)
97+
{
98+
double upperSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
99+
double lowerSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
100+
101+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
102+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
103+
}
104+
}
105+
//---------------------------------------------------------------------
106+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
107+
protected void Inlined2(in DataPoint incoming, in DataPoint lastArchived)
108+
{
109+
Debug.Assert(_swingingDoorCompression is not null);
110+
111+
double delta_x = incoming.X - lastArchived.X;
112+
113+
if (delta_x > 0)
114+
{
115+
double delta_y = incoming.Y - lastArchived.Y;
116+
double delta_yUpper = delta_y + _swingingDoorCompression.CompressionDeviation;
117+
double delta_yLower = delta_y - _swingingDoorCompression.CompressionDeviation;
118+
119+
double upperSlope = delta_yUpper / delta_x;
120+
double lowerSlope = delta_yLower / delta_x;
121+
122+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
123+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
124+
}
125+
else
126+
{
127+
GradientEquality(incoming, lastArchived);
128+
}
129+
130+
[MethodImpl(MethodImplOptions.NoInlining)]
131+
void GradientEquality(in DataPoint incoming, in DataPoint lastArchived)
132+
{
133+
double upperSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
134+
double lowerSlope = lastArchived.GradientEquality(incoming, return0OnEquality: true);
135+
136+
if (upperSlope < _slope.Max) _slope.Max = upperSlope;
137+
if (lowerSlope > _slope.Min) _slope.Min = lowerSlope;
138+
}
139+
}
140+
}
141+
}

source/gfoidl.DataCompression/Builders/ArrayBuilder.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// (c) gfoidl, all rights reserved
1+
// (c) gfoidl, all rights reserved
22

33
using System;
44
using System.Collections.Generic;
@@ -43,11 +43,15 @@ public void Add(T item)
4343
int index = _index;
4444

4545
if ((uint)index >= (uint)buffer.Length)
46+
{
4647
this.AddWithBufferAllocation(item);
48+
}
4749
else
50+
{
4851
buffer[index] = item;
52+
_index = index + 1;
53+
}
4954

50-
_index++;
5155
_count++;
5256
}
5357
//---------------------------------------------------------------------
@@ -56,7 +60,9 @@ public void Add(T item)
5660
private void AddWithBufferAllocation(T item)
5761
{
5862
this.AllocateBuffer();
59-
_currentBuffer[_index] = item;
63+
int index = _index;
64+
_currentBuffer[index] = item;
65+
_index = index + 1;
6066
}
6167
//---------------------------------------------------------------------
6268
public void AddRange(IEnumerable<T> items)

source/gfoidl.DataCompression/Compression.cs

+14-43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// (c) gfoidl, all rights reserved
1+
// (c) gfoidl, all rights reserved
22

33
using System;
44
using System.Collections.Generic;
@@ -10,11 +10,8 @@ namespace gfoidl.DataCompression
1010
/// </summary>
1111
public abstract class Compression : ICompression
1212
{
13-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
14-
protected internal readonly double _maxDeltaX;
15-
protected internal readonly bool _minDeltaXHasValue;
16-
protected internal readonly double _minDeltaX;
17-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
13+
private protected readonly double? _maxDeltaX;
14+
private protected readonly double? _minDeltaX;
1815
//---------------------------------------------------------------------
1916
/// <summary>
2017
/// Creates a new instance of <see cref="Compression" />.
@@ -28,42 +25,20 @@ public abstract class Compression : ICompression
2825
/// </param>
2926
protected Compression(double? maxDeltaX = null, double? minDeltaX = null)
3027
{
31-
_maxDeltaX = maxDeltaX ?? double.MaxValue;
32-
33-
if (minDeltaX.HasValue)
34-
{
35-
_minDeltaXHasValue = true;
36-
_minDeltaX = minDeltaX.Value;
37-
}
28+
_maxDeltaX = maxDeltaX;
29+
_minDeltaX = minDeltaX;
3830
}
3931
//---------------------------------------------------------------------
40-
/// <summary>
41-
/// Length of x before for sure a value gets recorded.
42-
/// </summary>
43-
/// <remarks>
44-
/// Cf. ExMax in documentation.<br />
45-
/// When specified as <see cref="DateTime" /> the <see cref="DateTime.Ticks" />
46-
/// are used.
47-
/// <para>
48-
/// When value is <c>null</c>, no value -- except the first and last -- are
49-
/// guaranteed to be recorded.
50-
/// </para>
51-
/// </remarks>
52-
public double? MaxDeltaX => _maxDeltaX == double.MaxValue ? (double?)null : _maxDeltaX;
32+
/// <inheritdoc />
33+
public abstract bool ArchiveIncoming { get; }
5334
//---------------------------------------------------------------------
54-
/// <summary>
55-
/// Length of x/time within no value gets recorded (after the last archived value)
56-
/// </summary>
57-
public double? MinDeltaX => _minDeltaXHasValue ? _minDeltaX : (double?)null;
35+
/// <inheritdoc />
36+
public double? MaxDeltaX => _maxDeltaX;
5837
//---------------------------------------------------------------------
59-
/// <summary>
60-
/// Performs the compression / filtering of the input data.
61-
/// </summary>
62-
/// <param name="data">Input data</param>
63-
/// <returns>The compressed / filtered data.</returns>
64-
/// <exception cref="System.ArgumentNullException">
65-
/// <paramref name="data" /> is <c>null</c>.
66-
/// </exception>
38+
/// <inheritdoc />
39+
public double? MinDeltaX => _minDeltaX;
40+
//---------------------------------------------------------------------
41+
/// <inheritdoc />
6742
public DataPointIterator Process(IEnumerable<DataPoint> data)
6843
{
6944
if (data is null) ThrowHelper.ThrowArgumentNull(ThrowHelper.ExceptionArgument.data);
@@ -72,11 +47,7 @@ public DataPointIterator Process(IEnumerable<DataPoint> data)
7247
}
7348
//---------------------------------------------------------------------
7449
#if NETSTANDARD2_1
75-
/// <summary>
76-
/// Performs the compression / filtering of the input data.
77-
/// </summary>
78-
/// <param name="data">Input data</param>
79-
/// <returns>The compressed / filtered data.</returns>
50+
/// <inheritdoc />
8051
public DataPointIterator ProcessAsync(IAsyncEnumerable<DataPoint> data)
8152
{
8253
if (data is null) ThrowHelper.ThrowArgumentNull(ThrowHelper.ExceptionArgument.data);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ClassDiagram MajorVersion="1" MinorVersion="1">
3+
<Class Name="gfoidl.DataCompression.Compression" Collapsed="true">
4+
<Position X="5.5" Y="1.5" Width="1.5" />
5+
<TypeIdentifier>
6+
<HashCode>IAEAAAAAAAEAYAAAAgAAAAAAAAAEAAAAAAAAAAEAEAA=</HashCode>
7+
<FileName>Compression.cs</FileName>
8+
</TypeIdentifier>
9+
<Lollipop Position="0.2" />
10+
</Class>
11+
<Class Name="gfoidl.DataCompression.DeadBandCompression" Collapsed="true">
12+
<Position X="3.25" Y="2.75" Width="1.5" />
13+
<TypeIdentifier>
14+
<HashCode>IAAAAAACAAAAAAAAIAAAAAAAAAAAAAAAAEAAAAAEEAA=</HashCode>
15+
<FileName>Compression\DeadBandCompression\DeadBandCompression.cs</FileName>
16+
</TypeIdentifier>
17+
</Class>
18+
<Class Name="gfoidl.DataCompression.NoCompression" Collapsed="true">
19+
<Position X="5.5" Y="2.75" Width="1.5" />
20+
<TypeIdentifier>
21+
<HashCode>IAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAEAA=</HashCode>
22+
<FileName>Compression\NoCompression\NoCompression.cs</FileName>
23+
</TypeIdentifier>
24+
</Class>
25+
<Class Name="gfoidl.DataCompression.SwingingDoorCompression" Collapsed="true">
26+
<Position X="7.75" Y="2.75" Width="1.5" />
27+
<TypeIdentifier>
28+
<HashCode>IAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAEAEAAAEEAA=</HashCode>
29+
<FileName>Compression\SwingingDoorCompression\SwingingDoorCompression.cs</FileName>
30+
</TypeIdentifier>
31+
</Class>
32+
<Font Name="Segoe UI" Size="9.16" />
33+
</ClassDiagram>

source/gfoidl.DataCompression/Compression/DeadBandCompression/AsyncEnumerableIterator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// (c) gfoidl, all rights reserved
1+
// (c) gfoidl, all rights reserved
22

33
using System;
44
using System.Collections.Generic;

0 commit comments

Comments
 (0)