Skip to content

Commit 073d51f

Browse files
committed
Implementation
1 parent 45f8a4d commit 073d51f

26 files changed

+1587
-1
lines changed

.github/workflows/benchmark.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This workflow will build a .NET project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3+
4+
name: Benchmark
5+
6+
on:
7+
workflow_dispatch:
8+
9+
jobs:
10+
benchmark:
11+
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Setup .NET
17+
uses: actions/setup-dotnet@v4
18+
with:
19+
dotnet-version: 9.0.x
20+
21+
- name: Restore dependencies
22+
run: dotnet restore FastFibCode.sln
23+
24+
- name: Build
25+
run: dotnet build -c Release --no-restore
26+
27+
- name: Run Benchmark
28+
run: cd ./FastFibCode.Benchmark && dotnet run -c Release --no-build

.github/workflows/buildAndTest.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This workflow will build a .NET project
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
3+
4+
name: Build & Test
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
workflow_dispatch:
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Setup .NET
20+
uses: actions/setup-dotnet@v4
21+
with:
22+
dotnet-version: 9.0.x
23+
24+
- name: Restore dependencies
25+
run: dotnet restore FastFibCode.sln
26+
27+
- name: Build
28+
run: dotnet build -c Release --no-restore
29+
30+
- name: Test
31+
run: dotnet test -c Release --no-build --verbosity normal

.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
#Ignore thumbnails created by Windows
3+
Thumbs.db
4+
#Ignore files built by Visual Studio
5+
*.obj
6+
*.exe
7+
*.pdb
8+
*.user
9+
*.aps
10+
*.pch
11+
*.vspscc
12+
*_i.c
13+
*_p.c
14+
*.ncb
15+
*.suo
16+
*.tlb
17+
*.tlh
18+
*.bak
19+
*.cache
20+
*.ilk
21+
*.log
22+
[Bb]in
23+
[Dd]ebug*/
24+
*.lib
25+
*.sbr
26+
obj/
27+
[Rr]elease*/
28+
_ReSharper*/
29+
[Tt]est[Rr]esult*
30+
.vs/
31+
.idea/
32+
#Nuget packages folder
33+
packages/
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using BenchmarkDotNet.Attributes;
2+
using System.Numerics;
3+
using System.Runtime.CompilerServices;
4+
5+
namespace FastFibCode.Benchmark;
6+
7+
public class CompareWithEliasDelta
8+
{
9+
private static uint[] data = new uint[1_000_000];
10+
11+
[GlobalSetup]
12+
public void GlobalSetup()
13+
{
14+
for (int i = 0; i < data.Length; i++)
15+
{
16+
data[i] = DistributionData.ExponentialDistributionRandom(1000_000);
17+
}
18+
}
19+
20+
[Benchmark(Baseline = true)]
21+
public ulong EliasDeltaEncode()
22+
{
23+
ulong r = 0;
24+
foreach (var v in data)
25+
r ^= EliasDeltaEncode(v);
26+
return r;
27+
}
28+
29+
[Benchmark]
30+
public ulong FibonacciEncode()
31+
{
32+
ulong r = 0;
33+
foreach (var v in data)
34+
r ^= Fibonacci.EncodeUInt(v);
35+
return r;
36+
}
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
public static ulong EliasDeltaEncode(uint value)
40+
{
41+
value++;
42+
var log2 = BitOperations.Log2(value);
43+
var logLog2 = BitOperations.Log2((uint)log2 + 1);
44+
ulong result = ((ulong)log2 + 1) ^ (1UL << logLog2);
45+
result <<= (logLog2 + 1);
46+
result |= (1U << logLog2);
47+
// highest bit not cleared and bit ordering is different but it's ok for the test
48+
result |= value << (2 * logLog2 + 1);
49+
return result;
50+
}
51+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace FastFibCode.Benchmark;
4+
5+
[InvocationCount(10)]
6+
public class DecodeBenchmarks
7+
{
8+
[ParamsAllValues]
9+
public Distribution Distribution { get; set; }
10+
11+
private ulong[] codes;
12+
13+
[GlobalSetup]
14+
public void GlobalSetup()
15+
{
16+
DistributionData.Init();
17+
}
18+
19+
[IterationSetup]
20+
public void IterationSetup()
21+
{
22+
codes = DistributionData.GetCodes(Distribution);
23+
}
24+
25+
[Benchmark(Baseline = true)]
26+
public uint DecodeConventional()
27+
{
28+
uint r = 0;
29+
foreach (var v in codes)
30+
r ^= FibonacciSlow.DecodeAsUInt(v, true);
31+
return r;
32+
}
33+
34+
[Benchmark]
35+
public uint DecodeFast()
36+
{
37+
uint r = 0;
38+
foreach (var v in codes)
39+
r ^= Fibonacci.DecodeAsUInt(v);
40+
return r;
41+
}
42+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace FastFibCode.Benchmark;
4+
5+
public class DecodeByteBenchmarks
6+
{
7+
private static ushort[] codes = new ushort[1_000_000];
8+
9+
[GlobalSetup]
10+
public void GlobalSetup()
11+
{
12+
for (int i = 0; i < codes.Length; i++)
13+
{
14+
codes[i] = Fibonacci.EncodeByte((byte)Random.Shared.Next(256));
15+
}
16+
}
17+
18+
[Benchmark(Baseline = true)]
19+
public ushort DecodeAsUShort()
20+
{
21+
ushort r = 0;
22+
foreach (var v in codes)
23+
r ^= Fibonacci.DecodeAsUShort(v);
24+
return r;
25+
}
26+
27+
[Benchmark]
28+
public byte DecodeAsByte()
29+
{
30+
byte r = 0;
31+
foreach (var v in codes)
32+
r ^= Fibonacci.DecodeAsByte(v);
33+
return r;
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace FastFibCode.Benchmark;
4+
5+
public class DecodeUIntBenchmarks
6+
{
7+
private static ulong[] codes = new ulong[1_000_000];
8+
9+
[GlobalSetup]
10+
public void GlobalSetup()
11+
{
12+
for (int i = 0; i < codes.Length; i++)
13+
{
14+
codes[i] = Fibonacci.EncodeUInt((uint)Random.Shared.Next());
15+
}
16+
}
17+
18+
[Benchmark(Baseline = true)]
19+
public ulong DecodeAsULong()
20+
{
21+
ulong r = 0;
22+
foreach (var v in codes)
23+
r ^= Fibonacci.DecodeAsULong(v);
24+
return r;
25+
}
26+
27+
[Benchmark]
28+
public uint DecodeAsUInt()
29+
{
30+
uint r = 0;
31+
foreach (var v in codes)
32+
r ^= Fibonacci.DecodeAsUInt(v);
33+
return r;
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace FastFibCode.Benchmark;
4+
5+
public class DecodeUShortBenchmarks
6+
{
7+
private static uint[] codes = new uint[1_000_000];
8+
9+
[GlobalSetup]
10+
public void GlobalSetup()
11+
{
12+
for (int i = 0; i < codes.Length; i++)
13+
{
14+
codes[i] = Fibonacci.EncodeUShort((ushort)Random.Shared.Next(ushort.MaxValue));
15+
}
16+
}
17+
18+
[Benchmark(Baseline = true)]
19+
public uint DecodeAsUInt()
20+
{
21+
uint r = 0;
22+
foreach (var v in codes)
23+
r ^= Fibonacci.DecodeAsUInt(v);
24+
return r;
25+
}
26+
27+
[Benchmark]
28+
public ushort DecodeAsUShort()
29+
{
30+
ushort r = 0;
31+
foreach (var v in codes)
32+
r ^= Fibonacci.DecodeAsUShort(v);
33+
return r;
34+
}
35+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace FastFibCode.Benchmark;
2+
3+
public enum Distribution
4+
{
5+
FoldedNormal_100,
6+
FoldedNormal_1K,
7+
FoldedNormal_10K,
8+
FoldedNormal_100K,
9+
FoldedNormal_1M,
10+
FoldedNormal_10M,
11+
Exponential_100,
12+
Exponential_1K,
13+
Exponential_10K,
14+
Exponential_100K,
15+
Exponential_1M,
16+
Exponential_10M,
17+
Uniform_5M
18+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace FastFibCode.Benchmark;
2+
3+
using static Distribution;
4+
5+
public static class DistributionData
6+
{
7+
private static readonly Dictionary<Distribution, uint[]> values = new Dictionary<Distribution, uint[]>();
8+
private static readonly Dictionary<Distribution, ulong[]> codes = new Dictionary<Distribution, ulong[]>();
9+
10+
public static uint[] GetValues(Distribution distribution) => values[distribution];
11+
public static ulong[] GetCodes(Distribution distribution) => codes[distribution];
12+
13+
public static void Init()
14+
{
15+
values.Clear();
16+
17+
values[FoldedNormal_100] = Create(() => FoldedNormalDistributionRandom(100));
18+
values[FoldedNormal_1K] = Create(() => FoldedNormalDistributionRandom(1000));
19+
values[FoldedNormal_10K] = Create(() => FoldedNormalDistributionRandom(10_000));
20+
values[FoldedNormal_100K] = Create(() => FoldedNormalDistributionRandom(100_000));
21+
values[FoldedNormal_1M] = Create(() => FoldedNormalDistributionRandom(1000_000));
22+
values[FoldedNormal_10M] = Create(() => FoldedNormalDistributionRandom(10_000_000));
23+
24+
values[Exponential_100] = Create(() => ExponentialDistributionRandom(100));
25+
values[Exponential_1K] = Create(() => ExponentialDistributionRandom(1000));
26+
values[Exponential_10K] = Create(() => ExponentialDistributionRandom(10_000));
27+
values[Exponential_100K] = Create(() => ExponentialDistributionRandom(100_000));
28+
values[Exponential_1M] = Create(() => ExponentialDistributionRandom(1_000_000));
29+
values[Exponential_10M] = Create(() => ExponentialDistributionRandom(10_000_000));
30+
31+
values[Uniform_5M] = Create(() => (uint)Random.Shared.Next(0, 5_000_000));
32+
33+
codes.Clear();
34+
foreach (var p in values)
35+
{
36+
var cs = new ulong[p.Value.Length];
37+
for (int i = 0; i < p.Value.Length; i++)
38+
cs[i] = Fibonacci.EncodeUInt(p.Value[i]);
39+
codes[p.Key] = cs;
40+
}
41+
}
42+
43+
private static uint[] Create(Func<uint> rnd)
44+
{
45+
uint[] data = new uint[1000_000];
46+
for (int i = 0; i < data.Length; i++)
47+
data[i] = rnd();
48+
return data;
49+
}
50+
51+
public static uint ExponentialDistributionRandom(uint sigma)
52+
=> (uint)Math.Round(-Math.Log(1 - Random.Shared.NextDouble()) * sigma);
53+
54+
public static uint FoldedNormalDistributionRandom(uint sigma)
55+
{
56+
// https://en.wikipedia.org/wiki/Folded_normal_distribution
57+
double s = sigma;
58+
var normalSigma = Math.Sqrt(s * s / (1 - 2 / Math.PI));
59+
double u1, u2;
60+
do
61+
{
62+
u1 = Random.Shared.NextDouble();
63+
}
64+
while (u1 == 0);
65+
u2 = Random.Shared.NextDouble();
66+
67+
var mag = normalSigma * Math.Sqrt(-2.0 * Math.Log(u1));
68+
return (uint)Math.Abs(mag * Math.Cos(2 * Math.PI * u2));
69+
}
70+
}

0 commit comments

Comments
 (0)