Skip to content

Commit e865acd

Browse files
Copilotnnhy
andcommitted
feat: 新增 BenchmarkDotNet 性能测试项目
Co-authored-by: nnhy <506367+nnhy@users.noreply.github.com>
1 parent 61cf7a9 commit e865acd

9 files changed

Lines changed: 426 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ bld/
2424
/BinTest
2525
/BinUnitTest
2626
/MapApi/Locations/Config
27+
/BenchmarkDotNet.Artifacts

Benchmark/AntiViralConfig.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using BenchmarkDotNet.Columns;
2+
using BenchmarkDotNet.Configs;
3+
using BenchmarkDotNet.Jobs;
4+
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
5+
6+
namespace Benchmark;
7+
8+
/// <summary>自定义配置,使用 InProcess 工具链避免 Deterministic 构建冲突</summary>
9+
public class AntiViralConfig : ManualConfig
10+
{
11+
public AntiViralConfig()
12+
{
13+
AddJob(Job.Default
14+
.WithToolchain(InProcessNoEmitToolchain.Instance)
15+
.WithWarmupCount(3)
16+
.WithIterationCount(5));
17+
AddColumn(StatisticColumn.OperationsPerSecond);
18+
}
19+
}

Benchmark/Benchmark.csproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<OutputPath>..\Bin\Benchmark</OutputPath>
7+
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<Nullable>enable</Nullable>
10+
<LangVersion>latest</LangVersion>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\NewLife.NovaDb\NewLife.NovaDb.csproj" />
19+
</ItemGroup>
20+
21+
</Project>

Benchmark/EmbeddedSqlBenchmark.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using BenchmarkDotNet.Attributes;
2+
using NewLife.NovaDb.Core;
3+
using NewLife.NovaDb.Sql;
4+
5+
namespace Benchmark;
6+
7+
/// <summary>嵌入模式 SQL 引擎基准测试</summary>
8+
[MemoryDiagnoser]
9+
[Config(typeof(AntiViralConfig))]
10+
public class EmbeddedSqlBenchmark
11+
{
12+
private String _dbPath = null!;
13+
private SqlEngine _engine = null!;
14+
private Int32 _counter;
15+
16+
[Params(WalMode.None, WalMode.Normal)]
17+
public WalMode WalMode { get; set; }
18+
19+
[GlobalSetup]
20+
public void Setup()
21+
{
22+
_dbPath = Path.Combine(Path.GetTempPath(), $"NovaBench_Sql_{WalMode}_{Guid.NewGuid():N}");
23+
_engine = new SqlEngine(_dbPath, new DbOptions { Path = _dbPath, WalMode = WalMode });
24+
_engine.Execute("CREATE TABLE bench (id INT PRIMARY KEY, name VARCHAR, age INT, score DOUBLE)");
25+
26+
// 预置查询数据
27+
for (var i = 1; i <= 1000; i++)
28+
_engine.Execute($"INSERT INTO bench VALUES ({i}, 'user{i}', {20 + i % 50}, {60.0 + i % 40})");
29+
30+
_counter = 2000;
31+
}
32+
33+
[GlobalCleanup]
34+
public void Cleanup()
35+
{
36+
_engine.Dispose();
37+
try { Directory.Delete(_dbPath, true); } catch { }
38+
}
39+
40+
[Benchmark(Description = "Insert 单行插入")]
41+
public void Insert()
42+
{
43+
var id = Interlocked.Increment(ref _counter);
44+
_engine.Execute($"INSERT INTO bench VALUES ({id}, 'bench{id}', 25, 88.5)");
45+
}
46+
47+
[Benchmark(Description = "Select 主键查询")]
48+
public void SelectByPK()
49+
{
50+
_engine.Execute("SELECT * FROM bench WHERE id = 500");
51+
}
52+
53+
[Benchmark(Description = "Select 范围查询")]
54+
public void SelectRange()
55+
{
56+
_engine.Execute("SELECT * FROM bench WHERE age > 30 AND age < 50");
57+
}
58+
59+
[Benchmark(Description = "Update 单行更新")]
60+
public void Update()
61+
{
62+
_engine.Execute("UPDATE bench SET score = 99.9 WHERE id = 100");
63+
}
64+
65+
[Benchmark(Description = "Delete 单行删除 + 重插")]
66+
public void DeleteAndReinsert()
67+
{
68+
var id = Interlocked.Increment(ref _counter);
69+
_engine.Execute($"INSERT INTO bench VALUES ({id}, 'tmp', 20, 70.0)");
70+
_engine.Execute($"DELETE FROM bench WHERE id = {id}");
71+
}
72+
73+
[Benchmark(Description = "聚合查询 COUNT/AVG")]
74+
public void Aggregate()
75+
{
76+
_engine.Execute("SELECT COUNT(*), AVG(score) FROM bench WHERE age >= 25");
77+
}
78+
}

Benchmark/FluxEngineBenchmark.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using BenchmarkDotNet.Attributes;
2+
using NewLife.NovaDb.Core;
3+
using NewLife.NovaDb.Engine.Flux;
4+
5+
namespace Benchmark;
6+
7+
/// <summary>时序引擎基准测试</summary>
8+
[MemoryDiagnoser]
9+
[Config(typeof(AntiViralConfig))]
10+
public class FluxEngineBenchmark
11+
{
12+
private String _basePath = null!;
13+
private FluxEngine _engine = null!;
14+
private Int64 _baseTicks;
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
_basePath = Path.Combine(Path.GetTempPath(), $"NovaBench_Flux_{Guid.NewGuid():N}");
20+
_engine = new FluxEngine(_basePath, new DbOptions());
21+
_baseTicks = DateTime.UtcNow.Ticks;
22+
23+
// 预置查询数据
24+
for (var i = 0; i < 1000; i++)
25+
{
26+
_engine.Append(new FluxEntry
27+
{
28+
Timestamp = _baseTicks + i * TimeSpan.TicksPerSecond,
29+
Fields = new Dictionary<String, Object?> { ["temperature"] = 20.0 + i % 30, ["humidity"] = 40.0 + i % 50 },
30+
Tags = new Dictionary<String, String> { ["device"] = $"sensor_{i % 10}" }
31+
});
32+
}
33+
}
34+
35+
[GlobalCleanup]
36+
public void Cleanup()
37+
{
38+
_engine.Dispose();
39+
try { Directory.Delete(_basePath, true); } catch { }
40+
}
41+
42+
[Benchmark(Description = "Append 单条写入")]
43+
public void AppendSingle()
44+
{
45+
_engine.Append(new FluxEntry
46+
{
47+
Timestamp = _baseTicks + Random.Shared.Next(0, 3600) * TimeSpan.TicksPerSecond,
48+
Fields = new Dictionary<String, Object?> { ["temperature"] = 25.5, ["humidity"] = 60.0 },
49+
Tags = new Dictionary<String, String> { ["device"] = "sensor_0" }
50+
});
51+
}
52+
53+
[Benchmark(Description = "AppendBatch 批量写入(100条)")]
54+
public void AppendBatch()
55+
{
56+
var entries = new List<FluxEntry>(100);
57+
var ts = _baseTicks;
58+
for (var i = 0; i < 100; i++)
59+
{
60+
entries.Add(new FluxEntry
61+
{
62+
Timestamp = ts + i * TimeSpan.TicksPerMillisecond,
63+
Fields = new Dictionary<String, Object?> { ["temperature"] = 20.0 + i, ["humidity"] = 50.0 },
64+
Tags = new Dictionary<String, String> { ["device"] = "batch_sensor" }
65+
});
66+
}
67+
_engine.AppendBatch(entries);
68+
}
69+
70+
[Benchmark(Description = "QueryRange 时间范围查询")]
71+
public List<FluxEntry> QueryRange()
72+
{
73+
var start = _baseTicks;
74+
var end = _baseTicks + 100 * TimeSpan.TicksPerSecond;
75+
return _engine.QueryRange(start, end);
76+
}
77+
}

Benchmark/KvStoreBenchmark.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using BenchmarkDotNet.Attributes;
2+
using NewLife.NovaDb.Core;
3+
using NewLife.NovaDb.Engine.KV;
4+
5+
namespace Benchmark;
6+
7+
/// <summary>KV 引擎基准测试</summary>
8+
[MemoryDiagnoser]
9+
[Config(typeof(AntiViralConfig))]
10+
public class KvStoreBenchmark
11+
{
12+
private String _storePath = null!;
13+
private KvStore _store = null!;
14+
private Int32 _counter;
15+
private Byte[] _value = null!;
16+
17+
[Params(64, 1024)]
18+
public Int32 ValueSize { get; set; }
19+
20+
[GlobalSetup]
21+
public void Setup()
22+
{
23+
_storePath = Path.Combine(Path.GetTempPath(), $"NovaBench_Kv_{ValueSize}_{Guid.NewGuid():N}");
24+
Directory.CreateDirectory(_storePath);
25+
_store = new KvStore(new DbOptions { DefaultKvTtl = TimeSpan.Zero }, _storePath);
26+
_value = new Byte[ValueSize];
27+
Random.Shared.NextBytes(_value);
28+
29+
// 预置数据
30+
for (var i = 0; i < 1000; i++)
31+
_store.Set($"key:{i}", _value);
32+
33+
_counter = 2000;
34+
}
35+
36+
[GlobalCleanup]
37+
public void Cleanup()
38+
{
39+
try { Directory.Delete(_storePath, true); } catch { }
40+
}
41+
42+
[Benchmark(Description = "Set 写入")]
43+
public void Set()
44+
{
45+
var id = Interlocked.Increment(ref _counter);
46+
_store.Set($"key:{id}", _value);
47+
}
48+
49+
[Benchmark(Description = "Get 读取")]
50+
public Byte[]? Get()
51+
{
52+
return _store.Get("key:500");
53+
}
54+
55+
[Benchmark(Description = "Set+Get 读写混合")]
56+
public Byte[]? SetThenGet()
57+
{
58+
var id = Interlocked.Increment(ref _counter);
59+
var key = $"key:{id}";
60+
_store.Set(key, _value);
61+
return _store.Get(key);
62+
}
63+
64+
[Benchmark(Description = "Delete 删除")]
65+
public void Delete()
66+
{
67+
var id = Interlocked.Increment(ref _counter);
68+
_store.Set($"del:{id}", _value);
69+
_store.Delete($"del:{id}");
70+
}
71+
}

Benchmark/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using BenchmarkDotNet.Running;
2+
3+
namespace Benchmark;
4+
5+
class Program
6+
{
7+
static void Main(String[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
8+
}

Benchmark/ServerModeBenchmark.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using BenchmarkDotNet.Attributes;
2+
using NewLife.NovaDb.Client;
3+
using NewLife.NovaDb.Core;
4+
using NovaServer = NewLife.NovaDb.Server.NovaServer;
5+
6+
namespace Benchmark;
7+
8+
/// <summary>独立服务模式基准测试(通过 TCP 协议访问 NovaServer)</summary>
9+
[MemoryDiagnoser]
10+
[Config(typeof(AntiViralConfig))]
11+
public class ServerModeBenchmark
12+
{
13+
private NovaServer _server = null!;
14+
private NovaConnection _conn = null!;
15+
private String _dbPath = null!;
16+
private Int32 _counter;
17+
18+
[GlobalSetup]
19+
public void Setup()
20+
{
21+
_dbPath = Path.Combine(Path.GetTempPath(), $"NovaBench_Server_{Guid.NewGuid():N}");
22+
23+
// 使用随机端口避免冲突
24+
var port = Random.Shared.Next(20000, 60000);
25+
_server = new NovaServer(port)
26+
{
27+
DbPath = _dbPath,
28+
Options = new ServerDbOptions { WalMode = WalMode.None }
29+
};
30+
_server.Start();
31+
32+
// 通过 ADO.NET 连接
33+
_conn = new NovaConnection($"Server=127.0.0.1;Port={port}");
34+
_conn.Open();
35+
36+
using var cmd = _conn.CreateCommand();
37+
cmd.CommandText = "CREATE TABLE bench (id INT PRIMARY KEY, name VARCHAR, age INT, score DOUBLE)";
38+
cmd.ExecuteNonQuery();
39+
40+
// 预置数据
41+
for (var i = 1; i <= 1000; i++)
42+
{
43+
cmd.CommandText = $"INSERT INTO bench VALUES ({i}, 'user{i}', {20 + i % 50}, {60.0 + i % 40})";
44+
cmd.ExecuteNonQuery();
45+
}
46+
47+
_counter = 2000;
48+
}
49+
50+
[GlobalCleanup]
51+
public void Cleanup()
52+
{
53+
_conn.Dispose();
54+
_server.Dispose();
55+
try { Directory.Delete(_dbPath, true); } catch { }
56+
}
57+
58+
[Benchmark(Description = "Server Insert 插入")]
59+
public void Insert()
60+
{
61+
var id = Interlocked.Increment(ref _counter);
62+
using var cmd = _conn.CreateCommand();
63+
cmd.CommandText = $"INSERT INTO bench VALUES ({id}, 'bench{id}', 25, 88.5)";
64+
cmd.ExecuteNonQuery();
65+
}
66+
67+
[Benchmark(Description = "Server Select 主键查询")]
68+
public void SelectByPK()
69+
{
70+
using var cmd = _conn.CreateCommand();
71+
cmd.CommandText = "SELECT * FROM bench WHERE id = 500";
72+
using var reader = cmd.ExecuteReader();
73+
while (reader.Read()) { }
74+
}
75+
76+
[Benchmark(Description = "Server Select 范围查询")]
77+
public void SelectRange()
78+
{
79+
using var cmd = _conn.CreateCommand();
80+
cmd.CommandText = "SELECT * FROM bench WHERE age > 30 AND age < 50";
81+
using var reader = cmd.ExecuteReader();
82+
while (reader.Read()) { }
83+
}
84+
85+
[Benchmark(Description = "Server Update 更新")]
86+
public void Update()
87+
{
88+
using var cmd = _conn.CreateCommand();
89+
cmd.CommandText = "UPDATE bench SET score = 99.9 WHERE id = 100";
90+
cmd.ExecuteNonQuery();
91+
}
92+
93+
[Benchmark(Description = "Server 聚合查询")]
94+
public void Aggregate()
95+
{
96+
using var cmd = _conn.CreateCommand();
97+
cmd.CommandText = "SELECT COUNT(*), AVG(score) FROM bench WHERE age >= 25";
98+
using var reader = cmd.ExecuteReader();
99+
while (reader.Read()) { }
100+
}
101+
}

0 commit comments

Comments
 (0)