Skip to content

Commit

Permalink
perf: ⚡envelope caching with MemoryCache and waveform utils improve…
Browse files Browse the repository at this point in the history
…ments (#24)

* perf: ⚡ more specialized waveform mixing

* perf: ⚡ envelope caching with `MemoryCache`
  • Loading branch information
kahojyun authored Jul 1, 2023
1 parent 2c28940 commit 5070ce2
Show file tree
Hide file tree
Showing 21 changed files with 947 additions and 201 deletions.
9 changes: 8 additions & 1 deletion Qynit.Pulsewave.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3B8B8B3E
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{6741804E-962A-4762-B289-CD4899A62AD8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaveGenDemo", "examples\WaveGenDemo\WaveGenDemo.csproj", "{B83CB224-4C00-4011-BF6A-C76C7D786908}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WaveGenDemo", "examples\WaveGenDemo\WaveGenDemo.csproj", "{B83CB224-4C00-4011-BF6A-C76C7D786908}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WaveGenBenchmarks", "examples\WaveGenBenchmarks\WaveGenBenchmarks.csproj", "{ECE2579D-700C-4BB3-9009-F35395177B7A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -38,6 +40,10 @@ Global
{B83CB224-4C00-4011-BF6A-C76C7D786908}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B83CB224-4C00-4011-BF6A-C76C7D786908}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B83CB224-4C00-4011-BF6A-C76C7D786908}.Release|Any CPU.Build.0 = Release|Any CPU
{ECE2579D-700C-4BB3-9009-F35395177B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECE2579D-700C-4BB3-9009-F35395177B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECE2579D-700C-4BB3-9009-F35395177B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECE2579D-700C-4BB3-9009-F35395177B7A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -46,6 +52,7 @@ Global
{8A71F7BA-E700-4DA3-BBE7-5E7C866466B8} = {3EBAD22F-230A-4BCB-B16C-A9063E2A4E9A}
{33659EDC-29F9-45DB-A9FA-E01E648BE4B9} = {3B8B8B3E-9095-4A7E-9309-661439C30D9A}
{B83CB224-4C00-4011-BF6A-C76C7D786908} = {6741804E-962A-4762-B289-CD4899A62AD8}
{ECE2579D-700C-4BB3-9009-F35395177B7A} = {6741804E-962A-4762-B289-CD4899A62AD8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1DC52AEE-D9FC-4B2F-9492-0C92B0479A3C}
Expand Down
43 changes: 43 additions & 0 deletions WaveGenBenchmarks.WaveformUtilsBench-report-github.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
``` ini

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1848/22H2/2022Update/SunValley2)
AMD Ryzen 5 5600, 1 CPU, 12 logical and 6 physical cores
.NET SDK=7.0.304
[Host] : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2


```
| Method | Length | Mean | Error | StdDev | Median | Ratio | RatioSD |
|------------------------ |------- |------------:|----------:|----------:|------------:|------:|--------:|
| **MixAddPlateau** | **16** | **12.94 ns** | **0.027 ns** | **0.023 ns** | **12.95 ns** | **0.25** | **0.00** |
| MixAddPlateauFrequency | 16 | 28.13 ns | 0.090 ns | 0.080 ns | 28.13 ns | 0.54 | 0.00 |
| MixAdd | 16 | 20.65 ns | 0.047 ns | 0.042 ns | 20.66 ns | 0.40 | 0.00 |
| MixAddFrequency | 16 | 38.46 ns | 0.113 ns | 0.088 ns | 38.49 ns | 0.74 | 0.00 |
| MixAddWithDrag | 16 | 29.16 ns | 0.143 ns | 0.134 ns | 29.16 ns | 0.56 | 0.00 |
| MixAddFrequencyWithDrag | 16 | 51.62 ns | 0.134 ns | 0.119 ns | 51.61 ns | 1.00 | 0.00 |
| Simple | 16 | 63.72 ns | 0.322 ns | 0.301 ns | 63.59 ns | 1.23 | 0.01 |
| | | | | | | | |
| **MixAddPlateau** | **64** | **42.69 ns** | **0.113 ns** | **0.105 ns** | **42.71 ns** | **0.53** | **0.01** |
| MixAddPlateauFrequency | 64 | 46.73 ns | 0.218 ns | 0.193 ns | 46.74 ns | 0.58 | 0.01 |
| MixAdd | 64 | 37.26 ns | 0.101 ns | 0.095 ns | 37.27 ns | 0.46 | 0.00 |
| MixAddFrequency | 64 | 62.61 ns | 0.192 ns | 0.160 ns | 62.61 ns | 0.78 | 0.01 |
| MixAddWithDrag | 64 | 51.32 ns | 0.466 ns | 0.363 ns | 51.19 ns | 0.64 | 0.01 |
| MixAddFrequencyWithDrag | 64 | 80.17 ns | 0.779 ns | 0.691 ns | 80.01 ns | 1.00 | 0.00 |
| Simple | 64 | 217.39 ns | 4.377 ns | 7.892 ns | 213.14 ns | 2.78 | 0.13 |
| | | | | | | | |
| **MixAddPlateau** | **256** | **72.68 ns** | **0.321 ns** | **0.300 ns** | **72.73 ns** | **0.37** | **0.00** |
| MixAddPlateauFrequency | 256 | 122.84 ns | 0.210 ns | 0.197 ns | 122.80 ns | 0.62 | 0.00 |
| MixAdd | 256 | 74.62 ns | 0.266 ns | 0.249 ns | 74.60 ns | 0.38 | 0.00 |
| MixAddFrequency | 256 | 163.94 ns | 0.327 ns | 0.306 ns | 164.00 ns | 0.83 | 0.01 |
| MixAddWithDrag | 256 | 141.93 ns | 0.305 ns | 0.285 ns | 141.89 ns | 0.72 | 0.00 |
| MixAddFrequencyWithDrag | 256 | 196.62 ns | 1.133 ns | 1.060 ns | 196.16 ns | 1.00 | 0.00 |
| Simple | 256 | 782.64 ns | 4.104 ns | 3.839 ns | 781.45 ns | 3.98 | 0.03 |
| | | | | | | | |
| **MixAddPlateau** | **1024** | **277.91 ns** | **0.331 ns** | **0.294 ns** | **277.91 ns** | **0.40** | **0.00** |
| MixAddPlateauFrequency | 1024 | 424.67 ns | 0.439 ns | 0.389 ns | 424.60 ns | 0.61 | 0.00 |
| MixAdd | 1024 | 274.94 ns | 1.182 ns | 1.048 ns | 275.11 ns | 0.39 | 0.00 |
| MixAddFrequency | 1024 | 566.09 ns | 1.301 ns | 1.086 ns | 566.40 ns | 0.81 | 0.00 |
| MixAddWithDrag | 1024 | 534.09 ns | 2.571 ns | 2.405 ns | 533.32 ns | 0.76 | 0.00 |
| MixAddFrequencyWithDrag | 1024 | 701.37 ns | 2.718 ns | 2.270 ns | 701.43 ns | 1.00 | 0.00 |
| Simple | 1024 | 3,061.59 ns | 12.202 ns | 10.189 ns | 3,060.17 ns | 4.37 | 0.02 |
5 changes: 5 additions & 0 deletions examples/WaveGenBenchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using BenchmarkDotNet.Running;

using WaveGenBenchmarks;

BenchmarkRunner.Run<WaveformUtilsBench>();
18 changes: 18 additions & 0 deletions examples/WaveGenBenchmarks/WaveGenBenchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Qynit.Pulsewave\Qynit.Pulsewave.csproj" />
</ItemGroup>

</Project>
97 changes: 97 additions & 0 deletions examples/WaveGenBenchmarks/WaveformUtilsBench.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System.Numerics;

using BenchmarkDotNet.Attributes;

using Qynit.Pulsewave;

namespace WaveGenBenchmarks;
public class WaveformUtilsBench
{
[Params(16, 64, 256, 1024)]
public int Length { get; set; }
private PooledComplexArray<double>? Source { get; set; }
private PooledComplexArray<double>? Target { get; set; }
private IqPair<double> Amplitude { get; } = new IqPair<double>(1, 1);
private IqPair<double> DragAmplitude { get; } = new IqPair<double>(1, 1);
private double DPhase { get; } = Math.PI / 11;

[GlobalSetup]
public void Init()
{
Source = new PooledComplexArray<double>(Length, true);
Source.DataI.Fill(0.125);
Source.DataQ.Fill(-0.125);
Target = new PooledComplexArray<double>(Length, true);
}

[Benchmark]
public void MixAddPlateau()
{
WaveformUtils.MixAddPlateau(Target!, Amplitude);
}

[Benchmark]
public void MixAddPlateauFrequency()
{
WaveformUtils.MixAddPlateauFrequency(Target!, Amplitude, DPhase);
}

[Benchmark]
public void MixAdd()
{
WaveformUtils.MixAdd(Target!, Source!, Amplitude);
}

[Benchmark]
public void MixAddFrequency()
{
WaveformUtils.MixAddFrequency(Target!, Source!, Amplitude, DPhase);
}

[Benchmark]
public void MixAddWithDrag()
{
WaveformUtils.MixAddWithDrag(Target!, Source!, Amplitude, DragAmplitude);
}

[Benchmark(Baseline = true)]
public void MixAddFrequencyWithDrag()
{
WaveformUtils.MixAddFrequencyWithDrag(Target!, Source!, Amplitude, DragAmplitude, DPhase);
}

[Benchmark]
public void Simple()
{
MixAddWithDragSimple(Target!, Source!, Amplitude, DragAmplitude, DPhase);
}

private static void MixAddWithDragSimple<T>(ComplexSpan<T> target, ComplexReadOnlySpan<T> source, IqPair<T> amplitude, IqPair<T> dragAmplitude, T dPhase)
where T : unmanaged, IFloatingPointIeee754<T>
{
var length = source.Length;
var sourceI = source.DataI;
var sourceQ = source.DataQ;
var targetI = target.DataI;
var targetQ = target.DataQ;

var carrier = amplitude;
var dragCarrier = dragAmplitude;
var phaser = IqPair<T>.FromPolarCoordinates(T.One, dPhase);
for (var i = 0; i < length; i++)
{
var diff = i switch
{
0 => new IqPair<T>(sourceI[i + 1], sourceQ[i + 1]) - new IqPair<T>(sourceI[i], sourceQ[i]),
_ when i == length - 1 => new IqPair<T>(sourceI[i], sourceQ[i]) - new IqPair<T>(sourceI[i - 1], sourceQ[i - 1]),
_ => (new IqPair<T>(sourceI[i + 1], sourceQ[i + 1]) - new IqPair<T>(sourceI[i - 1], sourceQ[i - 1])) * T.CreateChecked(0.5),
};
var sourceIq = new IqPair<T>(sourceI[i], sourceQ[i]);
var totalIq = sourceIq * carrier + diff * dragCarrier;
targetI[i] += totalIq.I;
targetQ[i] += totalIq.Q;
carrier *= phaser;
dragCarrier *= phaser;
}
}
}
44 changes: 0 additions & 44 deletions src/Qynit.Pulsewave/ComplexArrayReadOnlySpan.cs

This file was deleted.

39 changes: 0 additions & 39 deletions src/Qynit.Pulsewave/ComplexArraySpan.cs

This file was deleted.

44 changes: 44 additions & 0 deletions src/Qynit.Pulsewave/ComplexReadOnlySpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Qynit.Pulsewave;
public readonly ref struct ComplexReadOnlySpan<T>
where T : unmanaged
{
public ReadOnlySpan<T> DataI { get; }
public ReadOnlySpan<T> DataQ { get; }
public int Length => DataI.Length;
public bool IsEmpty => Length == 0;
internal ComplexReadOnlySpan(ReadOnlySpan<T> dataI, ReadOnlySpan<T> dataQ)
{
Debug.Assert(dataI.Length == dataQ.Length);
DataI = dataI;
DataQ = dataQ;
}
public static implicit operator ComplexReadOnlySpan<T>(PooledComplexArray<T> source)
{
return new ComplexReadOnlySpan<T>(source.DataI, source.DataQ);
}
public static implicit operator ComplexReadOnlySpan<T>(ComplexSpan<T> source)
{
return new ComplexReadOnlySpan<T>(source.DataI, source.DataQ);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexReadOnlySpan<T> Slice(int start)
{
return new ComplexReadOnlySpan<T>(DataI[start..], DataQ[start..]);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexReadOnlySpan<T> Slice(int start, int length)
{
return new ComplexReadOnlySpan<T>(DataI.Slice(start, length), DataQ.Slice(start, length));
}

public void CopyTo(ComplexSpan<T> destination)
{
DataI.CopyTo(destination.DataI);
DataQ.CopyTo(destination.DataQ);
}
}
49 changes: 49 additions & 0 deletions src/Qynit.Pulsewave/ComplexSpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Qynit.Pulsewave;
public readonly ref struct ComplexSpan<T>
where T : unmanaged
{
public Span<T> DataI { get; }
public Span<T> DataQ { get; }
public int Length => DataI.Length;
public bool IsEmpty => Length == 0;
internal ComplexSpan(Span<T> dataI, Span<T> dataQ)
{
Debug.Assert(dataI.Length == dataQ.Length);
DataI = dataI;
DataQ = dataQ;
}
public static implicit operator ComplexSpan<T>(PooledComplexArray<T> source)
{
return new ComplexSpan<T>(source.DataI, source.DataQ);
}

public void Fill(T i, T q)
{
DataI.Fill(i);
DataQ.Fill(q);
}
public void Clear()
{
DataI.Clear();
DataQ.Clear();
}
public void CopyTo(ComplexSpan<T> destination)
{
((ComplexReadOnlySpan<T>)this).CopyTo(destination);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexSpan<T> Slice(int start)
{
return new ComplexSpan<T>(DataI[start..], DataQ[start..]);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexSpan<T> Slice(int start, int length)
{
return new ComplexSpan<T>(DataI.Slice(start, length), DataQ.Slice(start, length));
}
}
2 changes: 2 additions & 0 deletions src/Qynit.Pulsewave/Envelope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public readonly record struct Envelope
public IPulseShape? Shape { get; }
public double Width { get; }
public double Plateau { get; }
public bool IsRectangle => Shape is null;
public bool IsZero => Width == 0 && Plateau == 0;
public Envelope(IPulseShape? shape, double width, double plateau)
{
Guard.IsGreaterThanOrEqualTo(width, 0);
Expand Down
2 changes: 2 additions & 0 deletions src/Qynit.Pulsewave/EnvelopeCacheKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
namespace Qynit.Pulsewave;
internal record EnvelopeCacheKey(EnvelopeInfo EnvelopeInfo, Envelope Envelope);
Loading

0 comments on commit 5070ce2

Please sign in to comment.