[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:
Loretta 2026-05-05 15:06:11 +02:00
parent 58f7a1c286
commit 651e2a0b9f
14 changed files with 318 additions and 106 deletions

View File

@ -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;

View File

@ -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,

View File

@ -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
}

View File

@ -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 &lt;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, 0x800xBF) — produce no char, skip.
/// • All other start bytes (0xxxxxxx, 110xxxxx, 1110xxxx) — produce 1 char each.
/// • 4-byte start bytes (11110xxx, 0xF00xF7) — 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++)
{

View File

@ -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

View File

@ -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" />

View File

@ -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;
}
*/

View File

@ -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;
}
}
*/

View File

@ -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;
}
*/

View File

@ -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.

View File

@ -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 |

View File

@ -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`)

View File

@ -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.