Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a7660ec
优化字符串与UTF-8字节数组池化与编码性能
VAllens Mar 4, 2026
93ed327
二进制序列化优化:统一用BinaryPrimitives写入
VAllens Mar 4, 2026
60566ff
重构字节数组池化管理,统一为PooledBytes
VAllens Mar 4, 2026
68d564b
优化文件头读写,提升性能并兼容多版本.NET
VAllens Mar 4, 2026
5577780
支持 .NET Core 3.1 的 Span/stackalloc 条件编译
VAllens Mar 5, 2026
fa27a72
GeoPoint空间解析与SQL空间函数性能优化
VAllens Mar 5, 2026
ec17691
引入HashHelper统一哈希算法实现并优化调用
VAllens Mar 5, 2026
bacb5da
HashHelper内部新增私有哈希算法枚举,解决面向.NET 4.5平台HashAlgorithmName结构体不可用的问题
VAllens Mar 5, 2026
8a82cdc
提升序列化与持久化性能,减少内存分配
VAllens Mar 5, 2026
8f443cb
Merge remote-tracking branch 'remotes/origin/master' into memory-opti…
VAllens Mar 6, 2026
a935740
新增PooledBufferWriter无参构造函数,默认容量256
VAllens Mar 6, 2026
45e140e
新增向量编码的 buffer 边界单元测试
VAllens Mar 6, 2026
e2a73e3
统一基础类型为 .NET 标准类型,规范代码风格
VAllens Mar 6, 2026
5efce97
优化 using 指令与命名空间声明,提升代码规范性
VAllens Mar 6, 2026
8614c66
对 EncodingExtensions、HashHelper、PooledBufferWriter、PooledBytes 四个工具类文…
VAllens Mar 6, 2026
5475185
修正了 valueBytesLengthTotal 计算中的括号优先级,确保当值为 null 时能正确返回 0。
VAllens Mar 6, 2026
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
10 changes: 9 additions & 1 deletion NewLife.NovaDb/Core/CompressionCodec.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Buffers.Binary;
using System.IO.Compression;

namespace NewLife.NovaDb.Core;
Expand Down Expand Up @@ -38,8 +39,15 @@ public Byte[] Compress(Byte[] data)
output.WriteByte((Byte)Algorithm);

// 写入原始长度(4 字节,用于预分配解压缓冲区)
var lenBytes = BitConverter.GetBytes(data.Length);
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
Span<Byte> lenBytes = stackalloc Byte[4];
BinaryPrimitives.WriteInt32LittleEndian(lenBytes, data.Length);
output.Write(lenBytes);
#else
var lenBytes = new Byte[4];
BinaryPrimitives.WriteInt32LittleEndian(lenBytes, data.Length);
output.Write(lenBytes, 0, 4);
#endif

// 压缩
using (var compressStream = CreateCompressStream(output))
Expand Down
364 changes: 332 additions & 32 deletions NewLife.NovaDb/Core/DataType.cs

Large diffs are not rendered by default.

272 changes: 224 additions & 48 deletions NewLife.NovaDb/Core/IDataCodec.cs

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions NewLife.NovaDb/Core/MetadataLock.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Threading;

namespace NewLife.NovaDb.Core;
namespace NewLife.NovaDb.Core;

/// <summary>元数据读写锁,用于 DDL 与 DML/SELECT 的并发控制</summary>
/// <remarks>
Expand Down
4 changes: 1 addition & 3 deletions NewLife.NovaDb/Core/NovaMetrics.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Threading;

namespace NewLife.NovaDb.Core;
namespace NewLife.NovaDb.Core;

/// <summary>NovaDb 运行时指标</summary>
/// <remarks>所有计数器均为线程安全,支持多线程并发递增</remarks>
Expand Down
3 changes: 1 addition & 2 deletions NewLife.NovaDb/Core/SlowQueryLog.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using NewLife.Log;
using NewLife.Log;

namespace NewLife.NovaDb.Core;

Expand Down
4 changes: 1 addition & 3 deletions NewLife.NovaDb/Engine/ColdIndexDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Linq;

namespace NewLife.NovaDb.Engine;
namespace NewLife.NovaDb.Engine;

/// <summary>冷段目录项(稀疏索引)</summary>
public class ColdDirectoryEntry
Expand Down
188 changes: 125 additions & 63 deletions NewLife.NovaDb/Engine/Flux/FluxEngine.Persist.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
using NewLife.NovaDb.Utilities;
using NewLife.Security;

namespace NewLife.NovaDb.Engine.Flux;
Expand All @@ -22,6 +25,7 @@ public partial class FluxEngine
private const Byte RecordType_FluxAppend = 1;
private const Byte RecordType_FluxPurge = 2;

private static readonly Encoding _encoding = Encoding.UTF8;
private static readonly Byte[] FluxLogMagic = [(Byte)'N', (Byte)'F', (Byte)'L', (Byte)'G'];
private const Int32 FluxLogHeaderSize = 32;

Expand Down Expand Up @@ -63,12 +67,24 @@ private void OpenFluxLog()
/// <summary>写入文件头</summary>
private void WriteFluxLogHeader()
{
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
Span<Byte> header = stackalloc Byte[FluxLogHeaderSize];
FluxLogMagic.AsSpan().CopyTo(header.Slice(0, 4));
#else
var header = new Byte[FluxLogHeaderSize];
Array.Copy(FluxLogMagic, 0, header, 0, 4);
FluxLogMagic.AsSpan().CopyTo(header.AsSpan(0, 4));
#endif

header[4] = 1; // Version

_fluxLogStream!.Position = 0;

#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
_fluxLogStream.Write(header);
#else
_fluxLogStream.Write(header, 0, header.Length);
#endif

_fluxLogStream.Flush();
}

Expand All @@ -78,9 +94,13 @@ private void ValidateFluxLogHeader()
if (_fluxLogStream!.Length < FluxLogHeaderSize) return;

_fluxLogStream.Position = 0;
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
Span<Byte> header = stackalloc Byte[FluxLogHeaderSize];
if (_fluxLogStream.Read(header) < FluxLogHeaderSize) return;
#else
var header = new Byte[FluxLogHeaderSize];
if (_fluxLogStream.Read(header, 0, header.Length) < FluxLogHeaderSize) return;

#endif
if (header[0] != FluxLogMagic[0] || header[1] != FluxLogMagic[1] ||
header[2] != FluxLogMagic[2] || header[3] != FluxLogMagic[3])
throw new InvalidOperationException("Invalid Flux log file header");
Expand All @@ -96,31 +116,38 @@ private void PersistFluxAppend(FluxEntry entry)
{
if (_fluxLogStream == null) return;

using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
// 初始容量给个经验值:头部(8+4) + fields/tags 数量 + 少量字符串
// 不够会自动扩容(扩容也不会产生 GC 垃圾,走 ArrayPool)
var w = new PooledBufferWriter(initialCapacity: 1024);
try
{
w.WriteInt64(entry.Timestamp);
w.WriteInt32(entry.SequenceId);

bw.Write(entry.Timestamp);
bw.Write(entry.SequenceId);
// Fields
var fields = entry.Fields;
w.WriteInt32(fields.Count);
foreach (var kv in fields)
{
WriteString(ref w, kv.Key);
WriteFieldValue(ref w, kv.Value);
}

// Fields
var fields = entry.Fields;
bw.Write(fields.Count);
foreach (var kv in fields)
{
WriteString(bw, kv.Key);
WriteFieldValue(bw, kv.Value);
}
// Tags
var tags = entry.Tags;
w.WriteInt32(tags.Count);
foreach (var kv in tags)
{
WriteString(ref w, kv.Key);
WriteString(ref w, kv.Value);
}

// Tags
var tags = entry.Tags;
bw.Write(tags.Count);
foreach (var kv in tags)
WriteFluxRecord(1, w.Buffer, 0, w.WrittenCount);
}
finally
{
WriteString(bw, kv.Key);
WriteString(bw, kv.Value);
w.Dispose();
}

WriteFluxRecord(RecordType_FluxAppend, ms.ToArray());
}

/// <summary>持久化 Purge 记录(删除过期分区)</summary>
Expand All @@ -129,88 +156,123 @@ private void PersistFluxPurge(String cutoffKey)
{
if (_fluxLogStream == null) return;

var data = Encoding.UTF8.GetBytes(cutoffKey);
WriteFluxRecord(RecordType_FluxPurge, data);
using var pooledBytes = _encoding.GetPooledEncodedBytes(cutoffKey);
WriteFluxRecord(RecordType_FluxPurge, pooledBytes.Buffer, 0, pooledBytes.Length);
}

/// <summary>写入一条记录</summary>
private void WriteFluxRecord(Byte recordType, Byte[] data)
private void WriteFluxRecord(Byte recordType, Byte[] data, Int32 offset, Int32 count)
{
var recordLength = 1 + data.Length + 4;
// recordLength: [recordType(1)] + [payload(count)] + [crc32(4)]
var recordLength = checked(1 + count + 4);
var totalLength = checked(4 + recordLength); // length prefix + record

Byte[]? rented = null;
var buffer = totalLength <= 1024
? stackalloc Byte[totalLength]
: (rented = ArrayPool<Byte>.Shared.Rent(totalLength)).AsSpan(0, totalLength);

using var ms = new MemoryStream(4 + recordLength);
using var bw = new BinaryWriter(ms);
try
{
// 写 recordLength (Little Endian)
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), recordLength);

bw.Write(recordLength);
bw.Write(recordType);
bw.Write(data);
// 写 recordType
buffer[4] = recordType;

// CRC32 校验
var checkBuffer = new Byte[1 + data.Length];
checkBuffer[0] = recordType;
Array.Copy(data, 0, checkBuffer, 1, data.Length);
var checksum = Crc32.Compute(checkBuffer, 0, checkBuffer.Length);
bw.Write(checksum);
// 写 payload
data.AsSpan(offset, count).CopyTo(buffer.Slice(5, count));

var buffer = ms.ToArray();
_fluxLogStream!.Position = _fluxLogStream.Length;
_fluxLogStream.Write(buffer, 0, buffer.Length);
_fluxLogStream.Flush();
// 计算 CRC32:覆盖 [recordType + payload],也就是 buffer[4..(5+count))
var checksum = Crc32.Compute(buffer.Slice(4, 1 + count));

// 写 CRC32 (Little Endian)
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(5 + count, 4), checksum);

// 写入到底层流
_fluxLogStream!.Seek(0, SeekOrigin.End);
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
_fluxLogStream.Write(buffer); // .NET Standard 2.1+ 有 Span 重载;低版本见下面兼容写法
#else
if (rented is not null)
{
_fluxLogStream.Write(rented, 0, totalLength);
}
else
{
var tempBuffer = ArrayPool<Byte>.Shared.Rent(totalLength);
try
{
buffer.CopyTo(tempBuffer.AsSpan(0, totalLength));
_fluxLogStream.Write(tempBuffer, 0, totalLength);
}
finally
{
ArrayPool<Byte>.Shared.Return(tempBuffer);
}
}
#endif
_fluxLogStream.Flush();
}
finally
{
if (rented is not null)
ArrayPool<Byte>.Shared.Return(rented);
}
}

#endregion

#region 字段序列化

/// <summary>写入 UTF-8 字符串(长度前缀)</summary>
private static void WriteString(BinaryWriter bw, String value)
private static void WriteString(ref PooledBufferWriter w, String value)
{
var bytes = Encoding.UTF8.GetBytes(value);
bw.Write(bytes.Length);
bw.Write(bytes);
using var bytes = value.ToPooledUtf8Bytes();
w.WriteInt32(bytes.Length);
w.WriteBytes(bytes.AsSpan());
}

/// <summary>读取 UTF-8 字符串(长度前缀)</summary>
private static String ReadString(BinaryReader br)
{
var len = br.ReadInt32();
var bytes = br.ReadBytes(len);
return Encoding.UTF8.GetString(bytes);
return _encoding.GetString(bytes);
}

/// <summary>写入字段值(带类型标签)</summary>
private static void WriteFieldValue(BinaryWriter bw, Object? value)
private static void WriteFieldValue(ref PooledBufferWriter w, Object? value)
{
switch (value)
{
case null:
bw.Write(TypeTag_Null);
w.WriteByte(TypeTag_Null);
break;
case Int32 i:
bw.Write(TypeTag_Int32);
bw.Write(i);
w.WriteByte(TypeTag_Int32);
w.WriteInt32(i);
break;
case Int64 l:
bw.Write(TypeTag_Int64);
bw.Write(l);
w.WriteByte(TypeTag_Int64);
w.WriteInt64(l);
break;
case Double d:
bw.Write(TypeTag_Double);
bw.Write(d);
w.WriteByte(TypeTag_Double);
w.WriteDouble(d);
break;
case Boolean b:
bw.Write(TypeTag_Boolean);
bw.Write(b);
w.WriteByte(TypeTag_Boolean);
w.WriteBool(b);
break;
case Byte[] bytes:
bw.Write(TypeTag_Bytes);
bw.Write(bytes.Length);
bw.Write(bytes);
w.WriteByte(TypeTag_Bytes);
w.WriteInt32(bytes.Length);
w.WriteBytes(bytes);
break;
default:
// 其他类型统一转为 String
bw.Write(TypeTag_String);
WriteString(bw, value.ToString() ?? "");
w.WriteByte(TypeTag_String);
WriteString(ref w, value.ToString() ?? String.Empty);
break;
}
}
Expand Down Expand Up @@ -324,7 +386,7 @@ private void ReplayFluxAppend(Byte[] body, Int32 offset, Int32 dataLength)
/// <summary>回放 Purge 记录</summary>
private void ReplayFluxPurge(Byte[] body, Int32 offset, Int32 dataLength)
{
var cutoffKey = Encoding.UTF8.GetString(body, offset, dataLength);
var cutoffKey = _encoding.GetString(body, offset, dataLength);

var toRemove = new List<String>();
foreach (var key in _partitions.Keys)
Expand Down
2 changes: 1 addition & 1 deletion NewLife.NovaDb/Engine/Flux/FluxEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static (Int64 timestamp, Int32 seq) ParseMessageId(String id)
if (dashIndex < 0)
throw new FormatException($"Invalid message ID format: '{id}'");

#if NETSTANDARD2_1_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
var timestamp = Int64.Parse(id.AsSpan(0, dashIndex));
var seq = Int32.Parse(id.AsSpan(dashIndex + 1));
return (timestamp, seq);
Expand Down
4 changes: 2 additions & 2 deletions NewLife.NovaDb/Engine/Flux/MessageId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static MessageId Parse(String value)
if (dashIndex < 0)
throw new FormatException($"Invalid MessageId format: '{value}'");

#if NETSTANDARD2_1_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
var timestamp = Int64.Parse(value.AsSpan(0, dashIndex));
var sequence = Int32.Parse(value.AsSpan(dashIndex + 1));
return new MessageId(timestamp, sequence);
Expand Down Expand Up @@ -93,7 +93,7 @@ public Boolean Equals(MessageId? other)
/// <returns>哈希码</returns>
public override Int32 GetHashCode()
{
#if NETSTANDARD2_1_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
return HashCode.Combine(Timestamp, Sequence);
#else
unchecked
Expand Down
5 changes: 4 additions & 1 deletion NewLife.NovaDb/Engine/KV/KvEntry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace NewLife.NovaDb.Engine.KV;
using System.Runtime.CompilerServices;

namespace NewLife.NovaDb.Engine.KV;

/// <summary>KV 内存索引项。Bitcask 模型仅索引驻留内存,值保留在磁盘按需读取</summary>
/// <remarks>
Expand All @@ -17,5 +19,6 @@ public struct KvEntry
public DateTime ExpiresAt;

/// <summary>检查是否已过期</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Boolean IsExpired() => ExpiresAt < DateTime.MaxValue && DateTime.UtcNow >= ExpiresAt;
}
Loading
Loading