Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.6" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="NUnit" Version="4.4.0" />
Expand Down
73 changes: 73 additions & 0 deletions src/BitSoft.BinaryTools.Benchmarks/BinaryPatchBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BitSoft.BinaryTools.Patch;

namespace BitSoft.BinaryTools.Benchmarks;

[ShortRunJob]
[MemoryDiagnoser]
public class BinaryPatchBenchmark
{
private byte[]? _source;
private byte[]? _modified;

private Stream? _sourceStream;
private Stream? _modifiedStream;
private Stream? _patchStream;

[Params(1024 * 1024, 10 * 1024 * 1024)]
public int BufferLength { get; set; }

[Params(5)]
public int ChangedBlocks { get; set; }

[Params(512)] public int ChangeSize { get; set; }

[Params(1024, 4096)] public int BlockSize { get; set; }

[IterationSetup]
public void GlobalSetUp()
{
_source = new byte[BufferLength];
_modified = new byte[BufferLength];

Random.Shared.NextBytes(_source);

Array.Copy(sourceArray: _source, destinationArray: _modified, length: _source.Length);

var changeBlockSize = _source.Length / (ChangedBlocks + 1);
for (var b = 1; b <= ChangedBlocks; b++)
{
var position = changeBlockSize * b;

var span = _modified.AsSpan(start: position, length: ChangeSize);

Random.Shared.NextBytes(span);
}

_sourceStream = new MemoryStream(_source);
_modifiedStream = new MemoryStream(_modified);
_patchStream = new MemoryStream();
}

[IterationCleanup]
public void Cleanup()
{
_sourceStream?.Dispose();
_modifiedStream?.Dispose();
_patchStream?.Dispose();
}

[Benchmark]
public async Task CreateBinaryPatch()
{
await BinaryPatch.CreateAsync(
source: _sourceStream!,
modified: _modifiedStream!,
output: _patchStream!,
blockSize: BlockSize
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

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

<ItemGroup>
<ProjectReference Include="..\BitSoft.BinaryTools\BitSoft.BinaryTools.csproj" />
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions src/BitSoft.BinaryTools.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Reflection;
using BenchmarkDotNet.Running;

namespace BitSoft.BinaryTools.Benchmarks;

class Program
{
static void Main(string[] args)
{
new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args);
}
}
31 changes: 31 additions & 0 deletions src/BitSoft.BinaryTools.Benchmarks/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Benchmarks

```
| Method | BufferLength | ChangedBlocks | ChangeSize | BlockSize | Mean | Error | StdDev | Allocated |
|------------------ |------------- |-------------- |----------- |---------- |----------:|---------:|---------:|----------:|
| CreateBinaryPatch | 1048576 | 5 | 512 | 1024 | 36.85 ms | 10.66 ms | 0.584 ms | 2.3 MB |
| CreateBinaryPatch | 1048576 | 5 | 512 | 4096 | 37.47 ms | 11.53 ms | 0.632 ms | 2.19 MB |
| CreateBinaryPatch | 10485760 | 5 | 512 | 1024 | 397.60 ms | 28.31 ms | 1.552 ms | 34.86 MB |
| CreateBinaryPatch | 10485760 | 5 | 512 | 4096 | 376.37 ms | 58.44 ms | 3.203 ms | 32.71 MB |
```
## Legends
```
BufferLength : Value of the 'BufferLength' parameter
ChangedBlocks : Value of the 'ChangedBlocks' parameter
ChangeSize : Value of the 'ChangeSize' parameter
BlockSize : Value of the 'BlockSize' parameter
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
1 us : 1 Microsecond (0.000001 sec)
```

## Additional info
```
BenchmarkDotNet v0.15.6, macOS 26.1 (25B78) [Darwin 25.1.0]
Apple M1 Pro, 1 CPU, 10 logical and 10 physical cores
.NET SDK 10.0.100
[Host] : .NET 8.0.22 (8.0.22, 8.0.2225.52707), Arm64 RyuJIT armv8.0-a
ShortRun : .NET 8.0.22 (8.0.22, 8.0.2225.52707), Arm64 RyuJIT armv8.0-a
```
11 changes: 11 additions & 0 deletions src/BitSoft.BinaryTools.Benchmarks/Utils/Create.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace BitSoft.BinaryTools.Benchmarks.Utils;

public static class Create
{
public static void RandomData(Span<byte> buffer)
{
Random.Shared.NextBytes(buffer);
}
}
128 changes: 125 additions & 3 deletions src/BitSoft.BinaryTools.Tests/Patch/BinaryPatchTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using BitSoft.BinaryTools.Patch;
Expand All @@ -11,10 +13,70 @@ public class BinaryPatchTests
private static IEnumerable<TestCaseData> TestCases()
{
yield return new TestCaseData(
new byte[] { 0x0, 0x1, 0x0, 0x1, 0x0 },
new byte[] { 0x0, 0x0, 0x1, 0x0, 0x0 },
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x1, 0x2, 0x3, 0x4 },
2
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x1, 0x2, 0x3, 0x4 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x2, 0x3, 0x4 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x7, 0x3, 0x4, 0x5, 0x6 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x7, 0x4, 0x5, 0x6 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x7, 0x8, 0x9, 0x2 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x2, 0x3, 0x9, 0x9 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x1, 0x2, 0x9, 0x9, 0x9 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5 },
new byte[] { 0x9, 0x9, 0x1, 0x2, 0x3 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 },
new byte[] { 0x1, 0x2, 0x9, 0x4, 0x5, 0x6, 0x7, 0x8 },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC },
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0xA, 0xA, 0xA, 0xA, 0xB, 0xA, 0xC },
3
);
yield return new TestCaseData(
new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 },
new byte[] { 0x1, 0x2, 0xA, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 },
4
);
yield return new TestCaseData(
new byte[] { 0x0, 0x1 },
new byte[] { 0x0 },
Expand Down Expand Up @@ -60,4 +122,64 @@ await BinaryPatch.CreateAsync(
Assert.That(patched.Length, Is.EqualTo(modified.Length));
Assert.That(patched, Is.EqualTo(modified));
}
}

[Ignore("Performance test")]
[TestCase(3 * 4, 4, 2, 2)]
[TestCase(100 * 4 * 4, 4, 5, 6)]
public async Task Should_CreatePatch(int bufferLength, int blockSize, int changedBlocks, int changeSize)
{
// Arrange
var source = new byte[bufferLength];
var modified = new byte[bufferLength];

Random.Shared.NextBytes(source);

Array.Copy(sourceArray: source, destinationArray: modified, length: source.Length);

var changeBlockSize = source.Length / (changedBlocks + 1);

for (var b = 1; b <= changedBlocks; b++)
{
var position = changeBlockSize * b;

var span = modified.AsSpan(start: position, length: changeSize);

Random.Shared.NextBytes(span);
}

using var sourceStream = new MemoryStream(source);
using var modifiedStream = new MemoryStream(modified);
using var patchStream = new MemoryStream();

// Act
var stopwatch = Stopwatch.StartNew();

await BinaryPatch.CreateAsync(
source: sourceStream,
modified: modifiedStream,
output: patchStream,
blockSize: blockSize
);

stopwatch.Stop();

// Assert
Console.WriteLine("Source length: {0}", sourceStream.Length);
Console.WriteLine("Block size: {0}", blockSize);
Console.WriteLine("Patch length: {0}", patchStream.Position);
Console.WriteLine("Create time: {0:g}", stopwatch.Elapsed);

sourceStream.Position = 0;
patchStream.Position = 0;

using var patchedStream = new MemoryStream();

stopwatch.Restart();
await BinaryPatch.ApplyAsync(source: sourceStream, patch: patchStream, output: patchedStream);
stopwatch.Stop();

Console.WriteLine("Apply time: {0:g}", stopwatch.Elapsed);

Assert.That(patchedStream.ToArray(), Is.EqualTo(modified));
}
}
Loading