[LOADED_DOCS: 3 files, no new loads]
SIMD UTF-8 upgrades, i18n test data, MVC disabled - Switch all test/benchmark data to Hungarian UTF-8 strings for i18n coverage - Add AVX-512BW, Vector256, and Vector128 SIMD paths for UTF-8/UTF-16 encode/decode (ASCII and multi-byte) in binary serializer/deserializer - Update WireMode docs for encoding guidance per workload/host - Block-comment and disable MVC formatters and Microsoft.AspNetCore.App reference due to .NET 10 Hybrid client conflict; update docs to reflect temporary state - Update appsettings: replace WaitForFlush with FlushPolicy - Revise BINARY_TODO.md for SIMD transcoder progress and next steps
This commit is contained in:
parent
58f7a1c286
commit
651e2a0b9f
|
|
@ -19,8 +19,8 @@ public static class BenchmarkTestDataProvider
|
|||
public static TestOrder CreateProfilerOrder()
|
||||
{
|
||||
TestDataFactory.ResetIdCounter();
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedTag = TestDataFactory.CreateTag("KözösCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
|
||||
return TestDataFactory.CreateOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 3,
|
||||
|
|
@ -34,8 +34,8 @@ public static class BenchmarkTestDataProvider
|
|||
{
|
||||
if (resetId) TestDataFactory.ResetIdCounter();
|
||||
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedTag = TestDataFactory.CreateTag("KözösCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
|
||||
|
||||
var order = TestDataFactory.CreateOrder(
|
||||
itemCount: 2,
|
||||
|
|
@ -54,16 +54,16 @@ public static class BenchmarkTestDataProvider
|
|||
{
|
||||
if (resetId) TestDataFactory.ResetIdCounter();
|
||||
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||
var sharedTag = TestDataFactory.CreateTag("KözösCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
|
||||
var sharedMeta = TestDataFactory.CreateMetadata("közös", withChild: true);
|
||||
|
||||
var sharedPreferences = new UserPreferences
|
||||
{
|
||||
Theme = "dark",
|
||||
Language = "en-US",
|
||||
Theme = "sötét",
|
||||
Language = "magyar",
|
||||
NotificationsEnabled = true,
|
||||
EmailDigestFrequency = "weekly"
|
||||
EmailDigestFrequency = "hetenkénti"
|
||||
};
|
||||
sharedUser.Preferences = sharedPreferences;
|
||||
|
||||
|
|
@ -86,15 +86,15 @@ public static class BenchmarkTestDataProvider
|
|||
{
|
||||
if (resetId) TestDataFactory.ResetIdCounter();
|
||||
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedTag = TestDataFactory.CreateTag("KözösCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("közösfelhasználó");
|
||||
|
||||
var sharedPreferences = new UserPreferences
|
||||
{
|
||||
Theme = "light",
|
||||
Language = "de-DE",
|
||||
Theme = "világos",
|
||||
Language = "német",
|
||||
NotificationsEnabled = false,
|
||||
EmailDigestFrequency = "daily"
|
||||
EmailDigestFrequency = "naponkénti"
|
||||
};
|
||||
sharedUser.Preferences = sharedPreferences;
|
||||
|
||||
|
|
@ -116,15 +116,15 @@ public static class BenchmarkTestDataProvider
|
|||
{
|
||||
if (resetId) TestDataFactory.ResetIdCounter();
|
||||
|
||||
var sharedTag = TestDataFactory.CreateTag("RepeatedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("repeateduser");
|
||||
var sharedTag = TestDataFactory.CreateTag("IsmétlődőCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("ismétlődőfelhasználó");
|
||||
|
||||
var sharedPreferences = new UserPreferences
|
||||
{
|
||||
Theme = "dark",
|
||||
Language = "en-US",
|
||||
Theme = "sötét",
|
||||
Language = "magyar",
|
||||
NotificationsEnabled = true,
|
||||
EmailDigestFrequency = "weekly"
|
||||
EmailDigestFrequency = "hetenkénti"
|
||||
};
|
||||
sharedUser.Preferences = sharedPreferences;
|
||||
|
||||
|
|
@ -162,16 +162,16 @@ public static class BenchmarkTestDataProvider
|
|||
{
|
||||
if (resetId) TestDataFactory.ResetIdCounter();
|
||||
|
||||
var sharedTag = TestDataFactory.CreateTag("DeepTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("deepuser");
|
||||
var sharedCategory = TestDataFactory.CreateCategory("DeepCategory");
|
||||
var sharedTag = TestDataFactory.CreateTag("MélyCímke");
|
||||
var sharedUser = TestDataFactory.CreateUser("mélyfelhasználó");
|
||||
var sharedCategory = TestDataFactory.CreateCategory("MélyKategória");
|
||||
|
||||
var sharedPreferences = new UserPreferences
|
||||
{
|
||||
Theme = "light",
|
||||
Language = "fr-FR",
|
||||
Theme = "világos",
|
||||
Language = "francia",
|
||||
NotificationsEnabled = false,
|
||||
EmailDigestFrequency = "monthly"
|
||||
EmailDigestFrequency = "havonkénti"
|
||||
};
|
||||
sharedUser.Preferences = sharedPreferences;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ namespace AyCode.Core.Tests.TestModels;
|
|||
/// <summary>
|
||||
/// Factory for creating test data hierarchies.
|
||||
/// Used by both unit tests and benchmarks.
|
||||
///
|
||||
/// All placeholder strings use Hungarian (UTF-8 multi-byte) content to exercise the UTF-8
|
||||
/// encoder/decoder path rather than the ASCII fast-path. This makes the benchmark reflect
|
||||
/// realistic i18n payloads, not just the FixStrAscii / StringAscii marker fast-paths.
|
||||
/// </summary>
|
||||
public static class TestDataFactory
|
||||
{
|
||||
|
|
@ -29,12 +33,12 @@ public static class TestDataFactory
|
|||
return new SharedTag
|
||||
{
|
||||
Id = id,
|
||||
Name = name ?? $"Tag-{id}",
|
||||
Color = color ?? $"#{id:X2}{(id * 10) % 256:X2}{(id * 20) % 256:X2}",
|
||||
Name = name ?? $"Címke-{id}",
|
||||
Color = color ?? $"Szín-#{id:X2}{(id * 10) % 256:X2}{(id * 20) % 256:X2}",
|
||||
Priority = id % 5,
|
||||
IsActive = id % 2 == 0,
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-id),
|
||||
Description = $"Description for tag {id}"
|
||||
Description = $"Címke leírása {id}"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +51,8 @@ public static class TestDataFactory
|
|||
return new SharedCategory
|
||||
{
|
||||
Id = id,
|
||||
Name = name ?? $"Category-{id}",
|
||||
Description = $"Category description {id}",
|
||||
Name = name ?? $"Kategória-{id}",
|
||||
Description = $"Kategória leírása {id}",
|
||||
SortOrder = id * 100,
|
||||
IsDefault = id == 1,
|
||||
ParentCategoryId = parentId,
|
||||
|
|
@ -66,20 +70,20 @@ public static class TestDataFactory
|
|||
return new SharedUser
|
||||
{
|
||||
Id = id,
|
||||
Username = username ?? $"user{id}",
|
||||
Email = $"user{id}@test.com",
|
||||
FirstName = $"First{id}",
|
||||
LastName = $"Last{id}",
|
||||
Username = username ?? $"felhasználó{id}",
|
||||
Email = $"felhasználó{id}@teszt.hu",
|
||||
FirstName = $"Vezetéknév{id}",
|
||||
LastName = $"Keresztnév{id}",
|
||||
IsActive = true,
|
||||
Role = role,
|
||||
LastLoginAt = DateTime.UtcNow.AddHours(-id),
|
||||
CreatedAt = DateTime.UtcNow.AddYears(-1),
|
||||
Preferences = new UserPreferences
|
||||
{
|
||||
Theme = id % 2 == 0 ? "dark" : "light",
|
||||
Language = "en-US",
|
||||
Theme = id % 2 == 0 ? "sötét" : "világos",
|
||||
Language = "magyar",
|
||||
NotificationsEnabled = true,
|
||||
EmailDigestFrequency = "daily"
|
||||
EmailDigestFrequency = "naponkénti"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -92,10 +96,10 @@ public static class TestDataFactory
|
|||
var id = _idCounter++;
|
||||
return new MetadataInfo
|
||||
{
|
||||
Key = key ?? $"Meta-{id}",
|
||||
Value = $"MetaValue-{id}",
|
||||
Key = key ?? $"Metaadat-{id}",
|
||||
Value = $"MetaÉrték-{id}",
|
||||
Timestamp = DateTime.UtcNow.AddMinutes(-id * 10),
|
||||
ChildMetadata = withChild ? CreateMetadata($"Child-{id}", false) : null
|
||||
ChildMetadata = withChild ? CreateMetadata($"Gyermek-{id}", false) : null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +124,11 @@ public static class TestDataFactory
|
|||
{
|
||||
// If sharedUser is provided but no sharedPreferences, use the user's preferences as shared
|
||||
sharedPreferences ??= sharedUser?.Preferences;
|
||||
|
||||
|
||||
var order = new TestOrder
|
||||
{
|
||||
Id = _idCounter++,
|
||||
OrderNumber = $"ORD-{_idCounter:D4}",
|
||||
OrderNumber = $"Megrendelés-{_idCounter:D4}",
|
||||
Status = TestStatus.Pending,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
TotalAmount = 1000m + _idCounter * 100,
|
||||
|
|
@ -144,12 +148,12 @@ public static class TestDataFactory
|
|||
for (int i = 0; i < itemCount; i++)
|
||||
{
|
||||
var item = CreateOrderItem(
|
||||
palletsPerItem,
|
||||
measurementsPerPallet,
|
||||
pointsPerMeasurement,
|
||||
sharedTag,
|
||||
sharedUser,
|
||||
sharedMetadata,
|
||||
palletsPerItem,
|
||||
measurementsPerPallet,
|
||||
pointsPerMeasurement,
|
||||
sharedTag,
|
||||
sharedUser,
|
||||
sharedMetadata,
|
||||
sharedPreferences,
|
||||
sharedCategory);
|
||||
item.ParentOrder = order;
|
||||
|
|
@ -181,11 +185,11 @@ public static class TestDataFactory
|
|||
assignee = CreateUser();
|
||||
assignee.Preferences = sharedPreferences;
|
||||
}
|
||||
|
||||
|
||||
var item = new TestOrderItem
|
||||
{
|
||||
Id = _idCounter++,
|
||||
ProductName = $"Product-{_idCounter}",
|
||||
ProductName = $"Termék-{_idCounter}",
|
||||
Quantity = 10 + _idCounter,
|
||||
UnitPrice = 5.5m * _idCounter,
|
||||
Status = TestStatus.Pending,
|
||||
|
|
@ -198,8 +202,8 @@ public static class TestDataFactory
|
|||
{
|
||||
// Pass shared references to all levels - creates many shared refs!
|
||||
var pallet = CreatePallet(
|
||||
measurementsPerPallet,
|
||||
pointsPerMeasurement,
|
||||
measurementsPerPallet,
|
||||
pointsPerMeasurement,
|
||||
sharedMetadata,
|
||||
sharedTag, // IId shared ref
|
||||
sharedUser, // IId shared ref
|
||||
|
|
@ -228,7 +232,7 @@ public static class TestDataFactory
|
|||
var pallet = new TestPallet
|
||||
{
|
||||
Id = _idCounter++,
|
||||
PalletCode = $"PLT-{_idCounter:D4}",
|
||||
PalletCode = $"Raklapkód-{_idCounter:D4}",
|
||||
TrayCount = 5 + _idCounter % 10,
|
||||
Status = TestStatus.Pending,
|
||||
Weight = 100.5 + _idCounter,
|
||||
|
|
@ -259,7 +263,7 @@ public static class TestDataFactory
|
|||
var measurement = new TestMeasurement
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Name = $"Measurement-{_idCounter}",
|
||||
Name = $"Mérés-{_idCounter}",
|
||||
TotalWeight = 100.5 + _idCounter,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Tag = sharedTag,
|
||||
|
|
@ -287,7 +291,7 @@ public static class TestDataFactory
|
|||
return new TestMeasurementPoint
|
||||
{
|
||||
Id = id,
|
||||
Label = $"Point-{id}",
|
||||
Label = $"MérőPont-{id}",
|
||||
Value = 10.5 + (id * 0.1),
|
||||
MeasuredAt = DateTime.UtcNow,
|
||||
Tag = sharedTag,
|
||||
|
|
@ -310,23 +314,23 @@ public static class TestDataFactory
|
|||
int pointsPerMeasurement = 5)
|
||||
{
|
||||
ResetIdCounter();
|
||||
|
||||
|
||||
// Create shared references that will be used throughout
|
||||
var sharedTags = Enumerable.Range(1, 10).Select(_ => CreateTag()).ToList();
|
||||
var sharedUser = CreateUser("benchuser", TestUserRole.Admin);
|
||||
var sharedMetadata = CreateMetadata("benchmark", withChild: true);
|
||||
var sharedUser = CreateUser("mérőfelhasználó", TestUserRole.Admin);
|
||||
var sharedMetadata = CreateMetadata("mérőteszt", withChild: true);
|
||||
|
||||
var order = new TestOrder
|
||||
{
|
||||
Id = _idCounter++,
|
||||
OrderNumber = $"BENCH-{_idCounter:D6}",
|
||||
OrderNumber = $"MÉRŐTESZT-{_idCounter:D6}",
|
||||
Status = TestStatus.Processing,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
TotalAmount = 999999.99m,
|
||||
PrimaryTag = sharedTags[0],
|
||||
SecondaryTag = sharedTags[0],
|
||||
Owner = sharedUser,
|
||||
Category = CreateCategory("Benchmark"),
|
||||
Category = CreateCategory("Mérőteszt"),
|
||||
OrderMetadata = sharedMetadata,
|
||||
AuditMetadata = sharedMetadata,
|
||||
Tags = sharedTags.Take(3).ToList()
|
||||
|
|
@ -337,7 +341,7 @@ public static class TestDataFactory
|
|||
var item = new TestOrderItem
|
||||
{
|
||||
Id = _idCounter++,
|
||||
ProductName = $"BenchProduct-{i}",
|
||||
ProductName = $"MérőTermék-{i}",
|
||||
Quantity = 100 + i * 10,
|
||||
UnitPrice = 25.99m + i,
|
||||
Status = (TestStatus)(i % 5),
|
||||
|
|
@ -352,7 +356,7 @@ public static class TestDataFactory
|
|||
var pallet = new TestPallet
|
||||
{
|
||||
Id = _idCounter++,
|
||||
PalletCode = $"PLT-{i}-{p}",
|
||||
PalletCode = $"Raklapkód-{i}-{p}",
|
||||
TrayCount = 10 + p,
|
||||
Status = (TestStatus)(p % 4),
|
||||
Weight = 500.0 + p * 50,
|
||||
|
|
@ -365,7 +369,7 @@ public static class TestDataFactory
|
|||
var measurement = new TestMeasurement
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Name = $"Meas-{i}-{p}-{m}",
|
||||
Name = $"Mérés-{i}-{p}-{m}",
|
||||
TotalWeight = 50.0 + m * 10,
|
||||
CreatedAt = DateTime.UtcNow.AddMinutes(-m)
|
||||
};
|
||||
|
|
@ -376,7 +380,7 @@ public static class TestDataFactory
|
|||
var point = new TestMeasurementPoint
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Label = $"Pt-{i}-{p}-{m}-{pt}",
|
||||
Label = $"MérőPnt-{i}-{p}-{m}-{pt}",
|
||||
Value = 1.0 + pt * 0.5,
|
||||
MeasuredAt = DateTime.UtcNow.AddSeconds(-pt)
|
||||
};
|
||||
|
|
@ -409,17 +413,17 @@ public static class TestDataFactory
|
|||
int pointsPerMeasurement = 4)
|
||||
{
|
||||
ResetIdCounter();
|
||||
|
||||
|
||||
// Create shared references - these will be heavily reused (tests $ref handling)
|
||||
var sharedTags = Enumerable.Range(1, 50).Select(_ => CreateTag()).ToList();
|
||||
var sharedUsers = Enumerable.Range(1, 20).Select(i => CreateUser($"user{i}", (TestUserRole)(i % 4))).ToList();
|
||||
var sharedMetadata = CreateMetadata("large-scale", withChild: true);
|
||||
var sharedCategories = Enumerable.Range(1, 10).Select(i => CreateCategory($"Cat-{i}")).ToList();
|
||||
var sharedUsers = Enumerable.Range(1, 20).Select(i => CreateUser($"felhasználó{i}", (TestUserRole)(i % 4))).ToList();
|
||||
var sharedMetadata = CreateMetadata("nagy-méretű", withChild: true);
|
||||
var sharedCategories = Enumerable.Range(1, 10).Select(i => CreateCategory($"Kategória-{i}")).ToList();
|
||||
|
||||
var order = new TestOrder
|
||||
{
|
||||
Id = _idCounter++,
|
||||
OrderNumber = $"LARGE-{_idCounter:D8}",
|
||||
OrderNumber = $"NAGYMÉRET-{_idCounter:D8}",
|
||||
Status = TestStatus.Processing,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
TotalAmount = 9999999.99m,
|
||||
|
|
@ -437,7 +441,7 @@ public static class TestDataFactory
|
|||
var item = new TestOrderItem
|
||||
{
|
||||
Id = _idCounter++,
|
||||
ProductName = $"Product-{i}",
|
||||
ProductName = $"Termék-{i}",
|
||||
Quantity = 100 + i,
|
||||
UnitPrice = 10.99m + (i % 100),
|
||||
Status = (TestStatus)(i % 5),
|
||||
|
|
@ -452,7 +456,7 @@ public static class TestDataFactory
|
|||
var pallet = new TestPallet
|
||||
{
|
||||
Id = _idCounter++,
|
||||
PalletCode = $"P-{i}-{p}",
|
||||
PalletCode = $"Raklapkód-{i}-{p}",
|
||||
TrayCount = 5 + (p % 10),
|
||||
Status = (TestStatus)(p % 4),
|
||||
Weight = 100.0 + p * 10,
|
||||
|
|
@ -465,7 +469,7 @@ public static class TestDataFactory
|
|||
var measurement = new TestMeasurement
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Name = $"M-{i}-{p}-{m}",
|
||||
Name = $"Mérés-{i}-{p}-{m}",
|
||||
TotalWeight = 10.0 + m,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
|
@ -476,7 +480,7 @@ public static class TestDataFactory
|
|||
var point = new TestMeasurementPoint
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Label = $"Pt-{i}-{p}-{m}-{pt}",
|
||||
Label = $"MérőPnt-{i}-{p}-{m}-{pt}",
|
||||
Value = pt * 0.1,
|
||||
MeasuredAt = DateTime.UtcNow
|
||||
};
|
||||
|
|
@ -518,7 +522,7 @@ public static class TestDataFactory
|
|||
DecimalValue = 12345.6789m,
|
||||
FloatValue = 1.5f,
|
||||
BoolValue = true,
|
||||
StringValue = "Test String ?? ????",
|
||||
StringValue = "Teszt Szöveg árvíztűrőtükörfúrógép",
|
||||
GuidValue = Guid.Parse("12345678-1234-1234-1234-123456789abc"),
|
||||
DateTimeValue = new DateTime(2024, 12, 25, 12, 30, 45, DateTimeKind.Utc),
|
||||
EnumValue = TestStatus.Shipped,
|
||||
|
|
|
|||
|
|
@ -116,17 +116,24 @@ public enum ReferenceHandlingMode : byte
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire encoding mode for binary serialization.
|
||||
/// Wire encoding mode. Pick by content + host:
|
||||
/// ASCII-heavy → <see cref="Compact"/> (smaller wire, faster CPU).
|
||||
/// UTF-8 multi-byte → <see cref="Fast"/> (larger wire, faster CPU).
|
||||
/// AVX-512BW host → <see cref="Compact"/> wins everywhere (smaller wire on all content, CPU parity with Fast).
|
||||
/// </summary>
|
||||
public enum WireMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Compact encoding: VarInt for integers, UTF-8 for strings. Smaller output.
|
||||
/// VarInt + UTF-8 with ASCII marker-dispatch fast-path. Smallest wire on all content types.
|
||||
/// Best for ASCII-heavy workloads, any workload where wire size matters (network, storage),
|
||||
/// or any workload on AVX-512BW hosts.
|
||||
/// </summary>
|
||||
Compact = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Fast encoding: fixed-width integers, UTF-16 for strings. Larger output, faster encode/decode.
|
||||
/// Fixed-width ints + UTF-16 raw memcpy (no UTF-8 transcoding). Larger wire (2 bytes/char fixed).
|
||||
/// Best for UTF-8 multi-byte workloads (i18n / CJK) on non-AVX-512 hosts where wire size
|
||||
/// matters less than CPU throughput (in-memory IPC, fast local transport).
|
||||
/// </summary>
|
||||
Fast = 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
|
||||
namespace AyCode.Core.Serializers.Binaries;
|
||||
|
|
@ -495,17 +496,21 @@ public static partial class AcBinaryDeserializer
|
|||
/// Counts UTF-16 chars produced by decoding the given UTF-8 byte span.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Vectorized via Vector256 (32 bytes/iter) using two bit-pattern checks:
|
||||
/// Layered SIMD: Vector512 (64 byte/iter) on AVX-512BW hosts → Vector256 (32 byte/iter)
|
||||
/// on AVX2 hosts → scalar tail. Both SIMD paths use the same two bit-pattern checks:
|
||||
/// • Non-continuation bytes (NOT 10xxxxxx, mask 0xC0 ≠ 0x80): each contributes 1 char.
|
||||
/// • 4-byte start bytes (11110xxx, mask 0xF8 == 0xF0): each contributes an EXTRA char (surrogate pair).
|
||||
///
|
||||
/// SIMD per-block result: <c>(32 - popcount(continuationMask)) + popcount(fourByteStartMask)</c>.
|
||||
/// Scalar tail handles the remaining <32 bytes.
|
||||
/// SIMD per-block result: <c>(N - popcount(continuationMask)) + popcount(fourByteStartMask)</c>
|
||||
/// where N = 64 (Vector512) or 32 (Vector256). Scalar tail handles the remaining bytes.
|
||||
///
|
||||
/// Char-count rules:
|
||||
/// • Continuation bytes (10xxxxxx, 0x80–0xBF) — produce no char, skip.
|
||||
/// • All other start bytes (0xxxxxxx, 110xxxxx, 1110xxxx) — produce 1 char each.
|
||||
/// • 4-byte start bytes (11110xxx, 0xF0–0xF7) — produce 2 chars (UTF-16 surrogate pair).
|
||||
///
|
||||
/// JIT-time path-selection: <c>Avx512BW.IsSupported</c> and <c>Vector256.IsHardwareAccelerated</c>
|
||||
/// are <c>[Intrinsic]</c> static booleans — the JIT/AOT constant-folds the dead branches per host.
|
||||
/// </remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CountUtf8Chars(ReadOnlySpan<byte> bytes)
|
||||
|
|
@ -514,8 +519,34 @@ public static partial class AcBinaryDeserializer
|
|||
var i = 0;
|
||||
ref var bytesRef = ref MemoryMarshal.GetReference(bytes);
|
||||
|
||||
// SIMD path: 32 bytes/iter via Vector256
|
||||
if (Vector256.IsHardwareAccelerated && bytes.Length >= 32)
|
||||
// SIMD path 1: 64 bytes/iter via Vector512 (AVX-512BW hosts)
|
||||
if (Avx512BW.IsSupported && bytes.Length >= 64)
|
||||
{
|
||||
var contMask512 = Vector512.Create((byte)0xC0);
|
||||
var contValue512 = Vector512.Create((byte)0x80);
|
||||
var fourByteMask512 = Vector512.Create((byte)0xF8);
|
||||
var fourByteValue512 = Vector512.Create((byte)0xF0);
|
||||
|
||||
do
|
||||
{
|
||||
var v = Vector512.LoadUnsafe(ref bytesRef, (uint)i);
|
||||
|
||||
// Non-continuation count: 64 - popcount(continuation byte mask)
|
||||
var contMatches = Vector512.Equals(v & contMask512, contValue512);
|
||||
var contBits = contMatches.ExtractMostSignificantBits(); // ulong
|
||||
count += 64 - System.Numerics.BitOperations.PopCount(contBits);
|
||||
|
||||
// 4-byte start count: popcount(fourByte start byte mask)
|
||||
var fourByteMatches = Vector512.Equals(v & fourByteMask512, fourByteValue512);
|
||||
var fourByteBits = fourByteMatches.ExtractMostSignificantBits();
|
||||
count += System.Numerics.BitOperations.PopCount(fourByteBits);
|
||||
|
||||
i += 64;
|
||||
} while (bytes.Length - i >= 64);
|
||||
}
|
||||
|
||||
// SIMD path 2: 32 bytes/iter via Vector256 (AVX2 hosts, also handles AVX-512 tail < 64)
|
||||
if (Vector256.IsHardwareAccelerated && bytes.Length - i >= 32)
|
||||
{
|
||||
var contMask = Vector256.Create((byte)0xC0);
|
||||
var contValue = Vector256.Create((byte)0x80);
|
||||
|
|
@ -540,6 +571,34 @@ public static partial class AcBinaryDeserializer
|
|||
} while (bytes.Length - i >= 32);
|
||||
}
|
||||
|
||||
// SIMD path 3: 16 bytes/iter via Vector128 (Apple Silicon NEON, WASM SIMD, legacy SSE2;
|
||||
// also handles tail < 32 from higher tiers). Cross-platform — Vector128.IsHardwareAccelerated
|
||||
// returns true on any host with a 128-bit SIMD ISA (NEON / SSE2 / WASM SIMD).
|
||||
if (Vector128.IsHardwareAccelerated && bytes.Length - i >= 16)
|
||||
{
|
||||
var contMask128 = Vector128.Create((byte)0xC0);
|
||||
var contValue128 = Vector128.Create((byte)0x80);
|
||||
var fourByteMask128 = Vector128.Create((byte)0xF8);
|
||||
var fourByteValue128 = Vector128.Create((byte)0xF0);
|
||||
|
||||
do
|
||||
{
|
||||
var v = Vector128.LoadUnsafe(ref bytesRef, (uint)i);
|
||||
|
||||
// Non-continuation count: 16 - popcount(continuation byte mask)
|
||||
var contMatches = Vector128.Equals(v & contMask128, contValue128);
|
||||
var contBits = contMatches.ExtractMostSignificantBits();
|
||||
count += 16 - System.Numerics.BitOperations.PopCount(contBits);
|
||||
|
||||
// 4-byte start count: popcount(fourByte start byte mask)
|
||||
var fourByteMatches = Vector128.Equals(v & fourByteMask128, fourByteValue128);
|
||||
var fourByteBits = fourByteMatches.ExtractMostSignificantBits();
|
||||
count += System.Numerics.BitOperations.PopCount(fourByteBits);
|
||||
|
||||
i += 16;
|
||||
} while (bytes.Length - i >= 16);
|
||||
}
|
||||
|
||||
// Scalar tail (and fallback for non-SIMD hardware)
|
||||
for (; i < bytes.Length; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Numerics;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using static AyCode.Core.Helpers.JsonUtilities;
|
||||
|
|
@ -904,7 +905,28 @@ public static partial class AcBinarySerializer
|
|||
ref ushort srcRefU16 = ref Unsafe.As<char, ushort>(ref srcRefChar);
|
||||
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
|
||||
|
||||
// Phase 1 — Vector256 ASCII narrow (16 chars/iter, falls out on first non-ASCII)
|
||||
// Phase 1a — Vector512 ASCII narrow (32 chars/iter on AVX-512BW hosts).
|
||||
// JIT-time path-selection via Avx512BW.IsSupported [Intrinsic] static bool — non-AVX-512
|
||||
// hosts get this branch eliminated by constant-folding (zero overhead in the generated asm).
|
||||
if (Avx512BW.IsSupported)
|
||||
{
|
||||
var asciiMask512 = Vector512.Create((ushort)0xFF80);
|
||||
while (src.Length - srcIdx >= Vector512<ushort>.Count) // 32 chars per Vector512<ushort>
|
||||
{
|
||||
var v = Vector512.LoadUnsafe(ref srcRefU16, (uint)srcIdx);
|
||||
// ASCII detect: any char's high bits set (>= 0x80)?
|
||||
if ((v & asciiMask512) != Vector512<ushort>.Zero) break;
|
||||
// Narrow 32 ushorts (Vector512) → 32 bytes (Vector256) via two 256-bit halves.
|
||||
// The JIT lowers this to AVX-512 VPACKUSWB on capable hosts (single-instruction pack).
|
||||
var bytes = Vector256.Narrow(v.GetLower(), v.GetUpper());
|
||||
bytes.StoreUnsafe(ref dstRef, (uint)dstIdx);
|
||||
srcIdx += Vector512<ushort>.Count;
|
||||
dstIdx += Vector512<ushort>.Count; // 32 chars → 32 bytes (1:1 for ASCII)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1b — Vector256 ASCII narrow (16 chars/iter on AVX2 hosts; also handles tail < 32 chars
|
||||
// after the AVX-512 path on capable hosts).
|
||||
if (Vector256.IsHardwareAccelerated)
|
||||
{
|
||||
var asciiMask = Vector256.Create((ushort)0xFF80);
|
||||
|
|
@ -921,6 +943,27 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
}
|
||||
|
||||
// Phase 1c — Vector128 ASCII narrow (16 chars/iter on Apple Silicon NEON, WASM SIMD,
|
||||
// legacy SSE2; also handles tail < 16 chars after higher tiers). Cross-platform —
|
||||
// Vector128.IsHardwareAccelerated is true on any 128-bit-SIMD-capable host.
|
||||
// Two Vector128<ushort> loads (8 + 8 = 16 chars) narrow to one Vector128<byte> (16 bytes).
|
||||
if (Vector128.IsHardwareAccelerated)
|
||||
{
|
||||
var asciiMask128 = Vector128.Create((ushort)0xFF80);
|
||||
while (src.Length - srcIdx >= 16) // 16 chars = 2 × Vector128<ushort>
|
||||
{
|
||||
var lo = Vector128.LoadUnsafe(ref srcRefU16, (uint)srcIdx);
|
||||
var hi = Vector128.LoadUnsafe(ref srcRefU16, (uint)(srcIdx + 8));
|
||||
// ASCII detect: any char's high bits set in either half?
|
||||
if (((lo | hi) & asciiMask128) != Vector128<ushort>.Zero) break;
|
||||
// Narrow 2× Vector128<ushort> (16 chars) → Vector128<byte> (16 bytes)
|
||||
var bytes = Vector128.Narrow(lo, hi);
|
||||
bytes.StoreUnsafe(ref dstRef, (uint)dstIdx);
|
||||
srcIdx += 16;
|
||||
dstIdx += 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2/3 — scalar with DWORD ASCII batch
|
||||
while (srcIdx < src.Length)
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,12 @@
|
|||
<Import Project="..//AyCode.Core.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<!-- TEMPORARILY DISABLED: pulls in Microsoft.AspNetCore.Mvc which collides with the downstream
|
||||
Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the AspNetCore.Mvc
|
||||
namespace). Re-enable when the binary serializer + MVC formatter is moved to its own
|
||||
solution / NuGet package. The Mvc/*.cs formatter sources are kept (block-commented).
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
-->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="9.0.11" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
|
||||
// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
|
||||
// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
|
||||
// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
|
||||
// AyCode.Services.csproj is also disabled in tandem.
|
||||
|
||||
/*
|
||||
using System.IO.Pipelines;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
|
@ -74,3 +81,4 @@ public class AcBinaryInputFormatter : InputFormatter
|
|||
|
||||
protected override bool CanReadType(Type type) => true;
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
|
||||
// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
|
||||
// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
|
||||
// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
|
||||
// AyCode.Services.csproj is also disabled in tandem.
|
||||
|
||||
/*
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -56,3 +63,4 @@ public static class AcBinaryMvcBuilderExtensions
|
|||
return builder;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
// TEMPORARILY DISABLED: this file references Microsoft.AspNetCore.Mvc.* which collides with the
|
||||
// downstream Hybrid client (FruitBankHybrid.Web.Client targets net10.0 and breaks on the
|
||||
// AspNetCore.Mvc namespace). Re-enable when the binary serializer + MVC formatter is moved to
|
||||
// its own solution / NuGet package. The FrameworkReference Microsoft.AspNetCore.App in
|
||||
// AyCode.Services.csproj is also disabled in tandem.
|
||||
|
||||
/*
|
||||
using System.IO.Pipelines;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
|
@ -41,3 +48,4 @@ public class AcBinaryOutputFormatter : OutputFormatter
|
|||
|
||||
protected override bool CanWriteType(Type? type) => true;
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# Mvc
|
||||
|
||||
> ⚠️ **TEMPORARILY DISABLED** — formatter sources block-commented (`/* ... */`) and `Microsoft.AspNetCore.App` FrameworkReference removed from `AyCode.Services.csproj` (downstream net10.0 Hybrid client conflict on `Microsoft.AspNetCore.Mvc` namespace). Re-enable when split into a separate NuGet package / solution. Description below documents the intended state.
|
||||
|
||||
ASP.NET Core MVC formatters for AcBinary wire format. Standard MVC pipeline integration — works with both controller-based MVC and Minimal API.
|
||||
|
||||
> **Topic docs:** `AyCode.Services/docs/MVC/README.md` — media type, request/response flow, configuration, ProblemDetails error model.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
|
|||
|---|---|
|
||||
| `SIGNALR/README.md` | Client-side SignalR transport (tags, wire protocol, req/resp flow) |
|
||||
| `SIGNALR_BINARY_PROTOCOL/README.md` | Binary-over-SignalR wire format, chunked framing |
|
||||
| `MVC/README.md` | ASP.NET Core MVC formatters for AcBinary (`application/vnd.acbinary`) |
|
||||
| `MVC/README.md` | ASP.NET Core MVC formatters for AcBinary (`application/vnd.acbinary`) — **temporarily disabled** |
|
||||
| `LOGGING/README.md` | Remote log writers (HTTP, browser console, SignalR) |
|
||||
|
||||
## Folder Structure
|
||||
|
|
@ -21,7 +21,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
|
|||
|---|---|
|
||||
| [`Loggers/`](Loggers/README.md) | Remote log writers: HTTP, browser console (JS interop), SignalR |
|
||||
| [`Logins/`](Logins/README.md) | Base and client-side login service implementations |
|
||||
| [`Mvc/`](Mvc/README.md) | ASP.NET Core MVC `InputFormatter` / `OutputFormatter` for AcBinary wire format |
|
||||
| [`Mvc/`](Mvc/README.md) | ASP.NET Core MVC `InputFormatter` / `OutputFormatter` for AcBinary wire format — **temporarily disabled** (block-commented, FrameworkReference removed) |
|
||||
| [`SignalRs/`](SignalRs/README.md) | Custom binary SignalR protocol, client base, message tagging, serialization |
|
||||
|
||||
## Dependencies
|
||||
|
|
@ -32,7 +32,7 @@ Shared service implementations: SignalR communication (custom binary protocol),
|
|||
| `AyCode.Entities` | Entity base classes |
|
||||
| `AyCode.Interfaces` | Service contracts |
|
||||
| `AyCode.Models` | DTOs |
|
||||
| `Microsoft.AspNetCore.App` (FrameworkReference) | ASP.NET Core MVC formatter base classes (.NET 9+) |
|
||||
| ~~`Microsoft.AspNetCore.App` (FrameworkReference)~~ | _temporarily disabled — see `Mvc/README.md`_ |
|
||||
| `Microsoft.AspNetCore.SignalR.Client` | SignalR client |
|
||||
| `Microsoft.AspNetCore.SignalR.Common` | `IHubProtocol` for custom binary protocol |
|
||||
| `Microsoft.AspNetCore.Authentication.JwtBearer` | JWT authentication |
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# MVC — AcBinary formatters
|
||||
|
||||
> ⚠️ **TEMPORARILY DISABLED** — formatter sources block-commented (`/* ... */`) and `Microsoft.AspNetCore.App` FrameworkReference removed from `AyCode.Services.csproj` (downstream net10.0 Hybrid client conflict on `Microsoft.AspNetCore.Mvc` namespace). Re-enable when split into a separate NuGet package / solution. Description below documents the intended state.
|
||||
|
||||
ASP.NET Core MVC `InputFormatter` / `OutputFormatter` pair for the AcBinary wire format. Works in controller-based MVC and Minimal API on .NET 9+. The wire payload is the raw `byte[]` produced by `AcBinarySerializer.Serialize(value, opts)` — bit-compatible with the single-shot byte[] API; no MVC-specific envelope.
|
||||
|
||||
> **Code:** `AyCode.Services/Mvc/` (`AcBinaryInputFormatter`, `AcBinaryOutputFormatter`, `AcBinaryMvcBuilderExtensions`)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ AyCode.Services ← AyCode.Services.Server
|
|||
- **AyCode.Database** — EF Core with generic DAL pattern. Session for reads, Transaction for writes. DAL pooling via `PooledDal`.
|
||||
|
||||
### Service Layer
|
||||
- **AyCode.Services** — Client-side: SignalR client, login service, loggers, ASP.NET Core MVC formatters for AcBinary (`Mvc/`).
|
||||
- **AyCode.Services** — Client-side: SignalR client, login service, loggers, ASP.NET Core MVC formatters for AcBinary (`Mvc/` — **temporarily disabled**, see `AyCode.Services/Mvc/README.md`).
|
||||
- **AyCode.Services.Server** — Server-side: SignalR hub with custom binary protocol, email (SendGrid), JWT auth.
|
||||
- **AyCode.Models.Server/DynamicMethods** — Reflection-based tag→method dispatch used by the SignalR hub.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue