Skip to content

Commit

Permalink
Add BlitPackFormatterAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Nov 20, 2022
1 parent 9ebf18d commit 45f7170
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 9 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ public partial class Employee

CustomFormatter
---
If implements `MemoryPackCustomFormatterAttribute<T>`, you can configure to use custom formatter to MemoryPackObject's member.
If implements `MemoryPackCustomFormatterAttribute<T>` or `MemoryPackCustomFormatterAttribute<TFormatter, T>`(more performant but complex), you can configure to use custom formatter to MemoryPackObject's member.

```csharp
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
Expand All @@ -593,17 +593,23 @@ public abstract class MemoryPackCustomFormatterAttribute<T> : Attribute
}
```

In built-in attribtues, `Utf8StringFormatterAttribute`, `Utf16StringFormatterAttribute`, `OrdinalIgnoreCaseStringDictionaryFormatter<TValue>` exsits.
In built-in attribtues, `Utf8StringFormatterAttribute`, `Utf16StringFormatterAttribute`, `InternStringFormatterAttribute`, `OrdinalIgnoreCaseStringDictionaryFormatterAttribtue<TValue>`, `BitPackFormatterAttribtue` exsits.

```csharp
[MemoryPackable]
public partial class Sample
{
// serialize this member as UTF16 String, it is performant than UTF8 but in ASCII, size is larger(but non ASCII, sometimes smaller).
[Utf16StringFormatter]
public string? Text { get; set; }

// In deserialize, Dictionary is initialized with StringComparer.OrdinalIgnoreCase.
[OrdinalIgnoreCaseStringDictionaryFormatter<int>]
public Dictionary<string, int>? Ids { get; set; }

// In deserialize time, all string is interned(see: String.Intern). If similar values come repeatedly, it saves memory.
[InternStringFormatter]
public string? Flag { get; set; }
}
```

Expand All @@ -621,6 +627,19 @@ public sealed class OrdinalIgnoreCaseStringDictionaryFormatter<TValue> : MemoryP
}
```

`BitPackFormatter` is for `bool[]`, same serialzied result as `BitArray`. In other words, bool is normally 1byte, but since it is treated as 1bit, eight bools are stored in one byte. Therefore, the size after serialization is 1/8.

```csharp
[MemoryPackable]
public partial class Sample
{
public int Id { get; set; }

[BitPackFormatter]
public bool[]? Data { get; set; }
}
```

Performance
---
TODO for describe details, stay tuned.
Expand Down
110 changes: 110 additions & 0 deletions src/MemoryPack.Core/Compression/BitPackFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using MemoryPack.Internal;
using System.Runtime.CompilerServices;

namespace MemoryPack.Compression;

[Preserve]
public sealed class BitPackFormatter : MemoryPackFormatter<bool[]>
{
public static readonly BitPackFormatter Default = new BitPackFormatter();

[Preserve]
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref bool[]? value)
{
if (value == null)
{
writer.WriteNullCollectionHeader();
return;
}
writer.WriteCollectionHeader(value.Length);

var data = 0;
var bit = 0;
foreach (var item in value)
{
Set(ref data, bit, item);

bit += 1;

if (bit == 32)
{
writer.WriteUnmanaged(data);
data = 0;
bit = 0;
}
}

if (bit != 0)
{
writer.WriteUnmanaged(data);
}
}

[Preserve]
public override void Deserialize(ref MemoryPackReader reader, scoped ref bool[]? value)
{
if (!reader.DangerousTryReadCollectionHeader(out var length))
{
value = null;
return;
}

if (length == 0)
{
value = Array.Empty<bool>();
return;
}

var readCount = ((length - 1) / 32) + 1;
var requireSize = readCount * 4;
if (reader.Remaining < requireSize)
{
MemoryPackSerializationException.ThrowInsufficientBufferUnless(length);
}

if (value == null || value.Length != length)
{
value = new bool[length];
}

var bit = 0;
var data = 0;
for (int i = 0; i < value.Length; i++)
{
if (bit == 0)
{
reader.ReadUnmanaged(out data);
}

value[i] = Get(data, bit);

bit += 1;

if (bit == 32)
{
data = 0;
bit = 0;
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Get(int data, int index)
{
return (data & (1 << index)) != 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set(ref int data, int index, bool value)
{
int bitMask = 1 << index;
if (value)
{
data |= bitMask;
}
else
{
data &= ~bitMask;
}
}
}
11 changes: 10 additions & 1 deletion src/MemoryPack.Core/CustomFormatterAttributes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MemoryPack.Formatters;
using MemoryPack.Compression;
using MemoryPack.Formatters;

namespace MemoryPack;

Expand Down Expand Up @@ -38,4 +39,12 @@ public override InternStringFormatter GetFormatter()
}
}

public sealed class BitPackFormatterAttribute : MemoryPackCustomFormatterAttribute<BitPackFormatter, bool[]>
{
public override BitPackFormatter GetFormatter()
{
return BitPackFormatter.Default;
}
}

#endif
3 changes: 3 additions & 0 deletions src/MemoryPack.Core/MemoryPack.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
<ItemGroup>
<TargetFiles1 Include="$(MSBuildProjectDirectory)\**\*.cs" Exclude="**\bin\**\*.*;**\obj\**\*.*;**\CompressedArray.cs;**\CollectionsMarshalEx.cs;**\ImmutableCollectionFormatters.cs" />
</ItemGroup>
<ItemGroup>
<TargetFiles1 Remove="BitPackFormatter.cs" />
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$(TargetFramework) == 'net7.0'">
<Copy SourceFiles="@(TargetFiles1)" DestinationFiles="$(DestinationRoot)\%(RecursiveDir)%(Filename)%(Extension)" SkipUnchangedFiles="true" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

#nullable enable
using MemoryPack.Internal;
using System.Runtime.CompilerServices;

namespace MemoryPack.Compression {

[Preserve]
public sealed class BitPackFormatter : MemoryPackFormatter<bool[]>
{
public static readonly BitPackFormatter Default = new BitPackFormatter();

[Preserve]
public override void Serialize(ref MemoryPackWriter writer, ref bool[]? value)
{
if (value == null)
{
writer.WriteNullCollectionHeader();
return;
}
writer.WriteCollectionHeader(value.Length);

var data = 0;
var bit = 0;
foreach (var item in value)
{
Set(ref data, bit, item);

bit += 1;

if (bit == 32)
{
writer.WriteUnmanaged(data);
data = 0;
bit = 0;
}
}

if (bit != 0)
{
writer.WriteUnmanaged(data);
}
}

[Preserve]
public override void Deserialize(ref MemoryPackReader reader, ref bool[]? value)
{
if (!reader.DangerousTryReadCollectionHeader(out var length))
{
value = null;
return;
}

if (length == 0)
{
value = Array.Empty<bool>();
return;
}

var readCount = ((length - 1) / 32) + 1;
var requireSize = readCount * 4;
if (reader.Remaining < requireSize)
{
MemoryPackSerializationException.ThrowInsufficientBufferUnless(length);
}

if (value == null || value.Length != length)
{
value = new bool[length];
}

var bit = 0;
var data = 0;
for (int i = 0; i < value.Length; i++)
{
if (bit == 0)
{
reader.ReadUnmanaged(out data);
}

value[i] = Get(data, bit);

bit += 1;

if (bit == 32)
{
data = 0;
bit = 0;
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Get(int data, int index)
{
return (data & (1 << index)) != 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set(ref int data, int index, bool value)
{
int bitMask = 1 << index;
if (value)
{
data |= bitMask;
}
else
{
data &= ~bitMask;
}
}
}

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;

#nullable enable
using MemoryPack.Compression;
using MemoryPack.Formatters;

namespace MemoryPack {
Expand Down Expand Up @@ -47,6 +48,14 @@ public override InternStringFormatter GetFormatter()
}
}

public sealed class BitPackFormatterAttribute : MemoryPackCustomFormatterAttribute<BitPackFormatter, bool[]>
{
public override BitPackFormatter GetFormatter()
{
return BitPackFormatter.Default;
}
}

#endif

}
Loading

0 comments on commit 45f7170

Please sign in to comment.