当前位置: 首页 > news >正文

高性能 JSON:System.Text.Json Source Generator vs 手写 Span(Utf8JsonReader/Writer)

高性能 JSON:System.Text.Json Source Generator vs 手写 Span(Utf8JsonReader/Writer)⚡️


📚 目录

  • 高性能 JSON:System.Text.Json Source Generator vs 手写 Span(Utf8JsonReader/Writer)⚡️
    • 1. 为什么比较 & 本文目标 🎯
    • 2. 参赛选手与实现基线 🧑‍💻
      • A) 反射默认(Baseline)
      • B) Source Generator(生成器)
      • C) 手写 Span(Reader/Writer)
      • 2.1 选型一图流 🧭
    • 3. 测试样本与评测矩阵 🧪
    • 4. 工程要点(把分配与拷贝压到最低)🛠️
      • 4.1 零拷贝数据流🔬
    • 5. 可复现仓库骨架(.NET 8/9)🧱
      • 5.1 统一接口与模型(`JsonPerf.Codecs`)🧩
      • 5.2 反射默认实现(**构造期选择只读 Options**)🧱
      • 5.3 Source Generator 实现 🧬
      • 5.4 手写 Span:Reader/Writer + 更稳的属性名比较 🧵
      • 5.5 BenchmarkDotNet(`JsonPerf.Bench`)📈
      • 5.6 Minimal API(`JsonPerf.Api`)——**三端点路径彻底隔离 & “收齐再解析” 修复** 🧵
      • 5.7 /order/span 时序图🕰️
      • 5.8 三路实现的可替换关系 🧩
    • 6. 端到端压测脚本 🧪
    • 7. 结果呈现与经验解读(建议)📈
    • 8. 选型建议表 🧭
    • 9. 反模式清单 🚫✅
    • 10. 复现步骤 🧪➡️🏁
    • 11. FAQ(工程化小贴士)📝


1. 为什么比较 & 本文目标 🎯

  • 反射默认(JsonSerializer + Options):易用灵活,但运行期反射与元数据访问有开销;在 Trim/NativeAOT 下可能禁用反射序列化,需要 Source Generator(SG)或 TypeInfoResolver 才能工作。
  • Source Generator(SG):编译期生成类型元数据/专用代码,冷启动更快、AOT/Trim 友好;调用入口三选一:传 JsonTypeInfo<T>、传 JsonSerializerContext、或设置 JsonSerializerOptions.TypeInfoResolver(.NET 7+)。
  • 手写 SpanUtf8JsonReader/Writer 直读直写 UTF-8,配 IBufferWriter<byte> 与管道实现低分配;适合顶级热路径、大批量与严格内存预算场景。

目标:在冷/热路径、S/M/B 对象规模与批量场景下,给出可复现的性能结论与可运维的选型标准。🧰


2. 参赛选手与实现基线 🧑‍💻

A) 反射默认(Baseline)

使用 JsonSerializer.Serialize/Deserialize(obj, options)全局复用一个 JsonSerializerOptions(线程安全),避免频繁创建导致缓存失效与性能回退。♻️

B) Source Generator(生成器)

定义 partial class XxxJsonContext : JsonSerializerContext,在类上用 [JsonSerializable(typeof(...))] 标注需要的类型。调用时三选一:

  • JsonTypeInfo<T>;或
  • JsonSerializerContext;或
  • XxxJsonContext.Default 赋给 JsonSerializerOptions.TypeInfoResolver(.NET 7+)后用常规重载。

⚠️ 不要把 optionscontext/typeinfo 混用到错误重载上。

C) 手写 Span(Reader/Writer)

Utf8JsonReader/Utf8JsonWriter + IBufferWriter<byte> 直读直写 UTF-8;端点用 HttpContext.Request.BodyReaderPipeReader循环读取(或采用本文默认的“收齐再解析”零拷贝方案),更稳更快。🔥

2.1 选型一图流 🧭

在这里插入图片描述


3. 测试样本与评测矩阵 🧪

数据模型

  • S(小对象):10–15 属性(数值/字符串/枚举/DateTimeOffset
  • M(中对象):3 层嵌套 + 数组(50–100 属性)
  • B(批量)List<S> 1k/10k(模拟埋点/日志批量)

工况维度

  • 冷启动 vs 热路径;序列化/反序列化分别测
  • 指标:吞吐(MB/s 或 req/s)、延迟分位(P50/P95/P99)、分配(B/op)、GC 次数、CPU 利用

指标权重(示例)

40%35%20%5%指标权重(可按团队目标调整)吞吐延迟 (P95/P99)分配 (B/op)稳定性/GC

工具

  • 微基准:BenchmarkDotNet([MemoryDiagnoser];可选线程/硬件计数器)
  • 端到端wrk/wrk2(吞吐+延迟分布)、hey(summary 含直方图/百分位,支持 CSV)

4. 工程要点(把分配与拷贝压到最低)🛠️

  1. Reader/Writer 直读直写:数值/布尔/枚举走 GetInt32()/GetDecimal()/GetBoolean()WriteNumber()/WriteBoolean(),避免 string 中转。
  2. 属性名匹配:优先 ValueTextEquals("id")(自动反转义);不要ValueSpan 直接比较文本。
  3. Buffer 复用:写出侧采用 Utf8JsonWriter(IBufferWriter<byte>, …);需要池化可用 ArrayPoolBufferWriter<T>
  4. BodyReader:端点读取使用 BodyReader.ReadAsync() 循环(或收齐再解析),避免 MemoryStream 中转。
  5. 编码/转义UnsafeRelaxedJsonEscaping 不转义 <, >, &HTML 敏感字符,仅在非 HTML/JS 注入语境使用。
  6. 多态:.NET 7+ 推荐 JsonPolymorphic/JsonDerivedType 标注层次;SG 与运行时均可识别。
  7. AOT/Trim:Trim/NativeAOT 下可能禁止反射序列化;需用 SG 或把 TypeInfoResolver 配好。
  8. Options 复用JsonSerializerOptions 建议静态复用(线程安全),避免频繁 new。

4.1 零拷贝数据流🔬

Kestrel
请求体: UTF-8 字节
BodyReader(PipeReader)
isCompleted?
ReadOnlySequence (全量)
Utf8JsonReader(finalBuffer)
OrderMan.Read
Utf8JsonWriter(BodyWriter)
响应: UTF-8 JSON

5. 可复现仓库骨架(.NET 8/9)🧱

JsonPerfPlayground/
├─ src/
│  ├─ JsonPerf.Codecs/      # 模型 & 三路实现(统一 IJsonCodec<T>)
│  ├─ JsonPerf.Bench/       # BenchmarkDotNet 基准
│  └─ JsonPerf.Api/         # Minimal API(端到端对比)
├─ scripts/
│  ├─ wrk.sh                # wrk/wrk2 压测脚本(已修正 BODY 传递)
│  └─ hey.sh                # hey 压测脚本
└─ Directory.Build.props    # TreatWarningsAsErrors, LangVersion 等

5.1 统一接口与模型(JsonPerf.Codecs)🧩

// IJsonCodec.cs
public interface IJsonCodec<T>
{byte[] Serialize(T value);T Deserialize(ReadOnlySpan<byte> utf8Json);
}// Models.cs(S/M/B 示例)
public enum OrderState : byte { New, Paid, Shipped, Cancelled }public sealed class OrderItem
{public int Id { get; set; }public string Sku { get; set; } = "";public int Qty { get; set; }public decimal Price { get; set; }
}public sealed class Order
{public int Id { get; set; }public string Customer { get; set; } = "";public DateTimeOffset CreatedAt { get; set; }public OrderState State { get; set; }public List<OrderItem> Items { get; set; } = new();public Dictionary<string, string> Tags { get; set; } = new();
}

5.2 反射默认实现(构造期选择只读 Options)🧱

// ReflectionCodec.cs
using System.Text.Json;
using System.Text.Encodings.Web;public sealed class ReflectionCodec<T> : IJsonCodec<T>
{private static readonly JsonSerializerOptions s_default = new(){PropertyNamingPolicy = JsonNamingPolicy.CamelCase,WriteIndented = false};private static readonly JsonSerializerOptions s_relaxed = new(){PropertyNamingPolicy = JsonNamingPolicy.CamelCase,WriteIndented = false,Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping // 仅非 HTML/JS 语境};private readonly JsonSerializerOptions _opt;public ReflectionCodec(bool relaxedEscaping = false)=> _opt = relaxedEscaping ? s_relaxed : s_default;public byte[] Serialize(T value) => JsonSerializer.SerializeToUtf8Bytes(value, _opt);public T Deserialize(ReadOnlySpan<byte> utf8) => JsonSerializer.Deserialize<T>(utf8, _opt)!;
}

5.3 Source Generator 实现 🧬

// PerfJsonContext.cs
using System.Text.Json.Serialization;[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
public partial class PerfJsonContext : JsonSerializerContext { }
// SourceGenCodec.cs —— 直接传 JsonSerializerContext
using System.Text.Json;public sealed class SourceGenCodec<T> : IJsonCodec<T>
{public byte[] Serialize(T value) =>JsonSerializer.SerializeToUtf8Bytes(value!, typeof(T), PerfJsonContext.Default);public T Deserialize(ReadOnlySpan<byte> utf8) =>(T)JsonSerializer.Deserialize(utf8, typeof(T), PerfJsonContext.Default)!;
}

5.4 手写 Span:Reader/Writer + 更稳的属性名比较 🧵

// OrderMan.cs(片段)
using System.Text.Json;public static class OrderMan
{public static void Write(ref Utf8JsonWriter w, in Order o){w.WriteStartObject();w.WriteNumber("id", o.Id);w.WriteString("customer", o.Customer);w.WriteString("createdAt", o.CreatedAt);w.WriteNumber("state", (byte)o.State);w.WritePropertyName("items");w.WriteStartArray();foreach (var it in o.Items){w.WriteStartObject();w.WriteNumber("id", it.Id);w.WriteString("sku", it.Sku);w.WriteNumber("qty", it.Qty);w.WriteNumber("price", it.Price);w.WriteEndObject();}w.WriteEndArray();w.WritePropertyName("tags");w.WriteStartObject();foreach (var kv in o.Tags) w.WriteString(kv.Key, kv.Value);w.WriteEndObject();w.WriteEndObject();}public static Order Read(ref Utf8JsonReader r){var o = new Order();// 若入口不确定:推进到 StartObjectif (r.TokenType != JsonTokenType.StartObject){while (r.Read() && r.TokenType != JsonTokenType.StartObject) { }if (r.TokenType != JsonTokenType.StartObject)throw new JsonException("Expected StartObject for Order.");}while (r.Read()){if (r.TokenType == JsonTokenType.EndObject) break;if (r.TokenType != JsonTokenType.PropertyName) continue;if (r.ValueTextEquals("id"))            { r.Read(); o.Id = r.GetInt32(); }else if (r.ValueTextEquals("customer")) { r.Read(); o.Customer = r.GetString()!; }else if (r.ValueTextEquals("createdAt")){ r.Read(); o.CreatedAt = r.GetDateTimeOffset(); }else if (r.ValueTextEquals("state"))    { r.Read(); o.State = (OrderState)r.GetByte(); }else if (r.ValueTextEquals("items")){r.Read();if (r.TokenType == JsonTokenType.Null) { o.Items = new(); continue; }if (r.TokenType != JsonTokenType.StartArray)throw new JsonException("Expected StartArray for 'items'.");var list = new List<OrderItem>();while (r.Read() && r.TokenType != JsonTokenType.EndArray)list.Add(ReadItem(ref r));o.Items = list;}else if (r.ValueTextEquals("tags")){r.Read();if (r.TokenType == JsonTokenType.Null) { o.Tags = new(); continue; }if (r.TokenType != JsonTokenType.StartObject)throw new JsonException("Expected StartObject for 'tags'.");var dict = new Dictionary<string,string>();while (r.Read() && r.TokenType != JsonTokenType.EndObject){if (r.TokenType != JsonTokenType.PropertyName)throw new JsonException("Expected PropertyName in 'tags'.");var key = r.GetString()!;r.Read();dict[key] = r.GetString()!;}o.Tags = dict;}else r.Skip(); // 跳未知字段}return o;}private static OrderItem ReadItem(ref Utf8JsonReader r){if (r.TokenType != JsonTokenType.StartObject)throw new JsonException("Expected StartObject for OrderItem.");var it = new OrderItem();while (r.Read()){if (r.TokenType == JsonTokenType.EndObject) break;if (r.TokenType != JsonTokenType.PropertyName) continue;if (r.ValueTextEquals("id"))    { r.Read(); it.Id = r.GetInt32(); }else if (r.ValueTextEquals("sku"))   { r.Read(); it.Sku = r.GetString()!; }else if (r.ValueTextEquals("qty"))   { r.Read(); it.Qty = r.GetInt32(); }else if (r.ValueTextEquals("price")) { r.Read(); it.Price = r.GetDecimal(); }else r.Skip();}return it;}
}
// SpanCodec.cs —— IBufferWriter 直写;Deserialize 加轻量守卫
using System.Buffers;
using System.Text.Json;public sealed class SpanCodec<T> : IJsonCodec<T>
{public byte[] Serialize(T value){var buffer = new ArrayBufferWriter<byte>(1024);using var w = new Utf8JsonWriter(buffer /*, new JsonWriterOptions { SkipValidation = false }*/);switch (value){case Order o: OrderMan.Write(ref w, o); break;default: throw new NotSupportedException(typeof(T).FullName);}w.Flush();return buffer.WrittenSpan.ToArray(); // demo 返回 byte[]}public T Deserialize(ReadOnlySpan<byte> utf8){var r = new Utf8JsonReader(utf8);// 轻量 guard:推进到 StartObject(与 OrderMan.Read 策略一致)if (r.TokenType != JsonTokenType.StartObject){while (r.Read() && r.TokenType != JsonTokenType.StartObject) { }if (r.TokenType != JsonTokenType.StartObject)throw new JsonException("Expected StartObject.");}object result = typeof(T) == typeof(Order)? OrderMan.Read(ref r): throw new NotSupportedException(typeof(T).FullName);return (T)result;}
}

5.5 BenchmarkDotNet(JsonPerf.Bench)📈

// Bench.cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
// using BenchmarkDotNet.Jobs; // 可选:指定 Runtime/Job[MemoryDiagnoser]
// [SimpleJob(RuntimeMoniker.Net80, warmupCount: 3, iterationCount: 8, invocationCount: 1, baseline: true)]
public class JsonBench
{private Order _order = SampleData.SmallOrder();private byte[] _json = default!;private readonly IJsonCodec<Order> _ref = new ReflectionCodec<Order>();private readonly IJsonCodec<Order> _sg  = new SourceGenCodec<Order>();private readonly IJsonCodec<Order> _sp  = new SpanCodec<Order>();[GlobalSetup] public void Setup() => _json = _ref.Serialize(_order);[Benchmark(Baseline = true)] public byte[] Ser_Reflection() => _ref.Serialize(_order);[Benchmark] public byte[] Ser_SourceGen() => _sg.Serialize(_order);[Benchmark] public byte[] Ser_Span() => _sp.Serialize(_order);[Benchmark] public Order Deser_Reflection() => _ref.Deserialize(_json);[Benchmark] public Order Deser_SourceGen() => _sg.Deserialize(_json);[Benchmark] public Order Deser_Span() => _sp.Deserialize(_json);
}public class Program
{public static void Main(string[] args) => BenchmarkRunner.Run<JsonBench>();
}

5.6 Minimal API(JsonPerf.Api)——三端点路径彻底隔离 & “收齐再解析” 修复 🧵

var builder = WebApplication.CreateBuilder(args);// 可选:全局绑定 SG(注意会影响默认 Results.Json 路径)
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(opt =>
{opt.SerializerOptions.TypeInfoResolver = PerfJsonContext.Default; // .NET 7+
});var app = builder.Build();// 反射 Baseline —— 显式传“独立 options”(不带 TypeInfoResolver)
app.MapPost("/order/ref", (Order o) =>
{var opts = new JsonSerializerOptions(JsonSerializerDefaults.Web);return Results.Json(o, opts); // 保证走反射默认路径
});// Source Generator —— 显式传 JsonTypeInfo 或 Context
app.MapPost("/order/sg", (Order o) =>Results.Json(o, PerfJsonContext.Default.Order)); // 或 Results.Json(o, PerfJsonContext.Default)// 手写 Span —— “收齐再解析”(零拷贝,解析完成后再 AdvanceTo)
app.MapPost("/order/span", async (HttpContext ctx) =>
{var pipe = ctx.Request.BodyReader;while (true){var result = await pipe.ReadAsync(ctx.RequestAborted);var buffer = result.Buffer;if (result.IsCompleted){// ✅ 先解析var reader = new Utf8JsonReader(buffer, isFinalBlock: true, state: default);var order = OrderMan.Read(ref reader);ctx.Response.ContentType = "application/json";using var w = new Utf8JsonWriter(ctx.Response.BodyWriter /*, new JsonWriterOptions { SkipValidation = false }*/);OrderMan.Write(ref w, order);await ctx.Response.BodyWriter.FlushAsync(ctx.RequestAborted);// ✅ 再 AdvanceTo,避免悬空引用pipe.AdvanceTo(buffer.End);break;}// 未结束:保留全部数据,继续累积pipe.AdvanceTo(buffer.Start, buffer.End);}
});app.Run();

5.7 /order/span 时序图🕰️

wrk/hey 客户端ASP.NET APIBodyReader(PipeReader)Utf8JsonReaderUtf8JsonWriterPOST /order/span (application/json)ReadAsync 循环(累计缓冲)ReadOnlySequence<byte> (isCompleted==true)new Utf8JsonReader(sequence, finalBlock=true)OrderMan.Read(ref reader)Write(order) → BodyWriterJSON 响应(UTF-8)wrk/hey 客户端ASP.NET APIBodyReader(PipeReader)Utf8JsonReaderUtf8JsonWriter

5.8 三路实现的可替换关系 🧩

IJsonCodec<T>
+byte[] Serialize(T value)
+T Deserialize(ReadOnlySpan utf8Json)
ReflectionCodec<T>
-JsonSerializerOptions _opt
+Serialize(T)
+Deserialize(ReadOnlySpan)
SourceGenCodec<T>
+Serialize(T)
+Deserialize(ReadOnlySpan)
SpanCodec<T>
+Serialize(T)
+Deserialize(ReadOnlySpan)

6. 端到端压测脚本 🧪

scripts/wrk.sh(Linux/Mac):

#!/usr/bin/env bash
URL=${1:-http://127.0.0.1:5000/order/sg} # 传 /ref 或 /span 即可切换
export BODY='{"id":1,"customer":"A","createdAt":"2024-01-01T00:00:00Z","state":1,"items":[{"id":1,"sku":"X","qty":2,"price":3.14}],"tags":{"k":"v"}}'wrk -t12 -c400 -d30s --latency -s <(cat <<'LUA'
wrk.method = "POST"
wrk.body   = os.getenv("BODY")
wrk.headers["Content-Type"] = "application/json"
LUA
) "$URL"

scripts/hey.sh

#!/usr/bin/env bash
URL=${1:-http://127.0.0.1:5000/order/sg}
BODY='{"id":1,"customer":"A","createdAt":"2024-01-01T00:00:00Z","state":1,"items":[{"id":1,"sku":"X","qty":2,"price":3.14}],"tags":{"k":"v"}}'
hey -n 200000 -c 200 -m POST -T 'application/json' -D <(echo "$BODY") "$URL"
# hey 的 summary 含直方图与百分位(可提取 P99)

7. 结果呈现与经验解读(建议)📈

  • 图表:三方案在 S/M/B 下的 MB/s(或 req/s)、P95/P99、B/op
  • 表格:开启 UnsafeRelaxedJsonEscaping、忽略条件、数字处理方式后的相对变化

常见趋势(仍需用你的数据验证)

  • 小对象/常规 APISource Generator 在吞吐、分配与冷启动方面稳定优于反射,改动成本小且 AOT/Trim 友好。
  • 批量/顶级热路径手写 Span 反序列化优势明显(尤其避免中转字符串/装箱);但维护/容错成本更高。
  • 动态结构/键不固定:反射默认或“SG + JsonExtensionData/自定义转换器”更务实;多态优先用 .NET 7+JsonPolymorphic/JsonDerivedType

8. 选型建议表 🧭

场景首选备注
常规 API / 中等吞吐Source Generator维护成本低、AOT/Trim 友好;冷/热路径均优于反射
大批量埋点 / 网关编解码手写 Span搭配 IBufferWriter/管道/池化,做好单测与 Fuzz
动态结构 / 字段不确定反射默认(热点局部 SG)灵活;识别热点后迁移到 SG
AOT/NativeAOT/严苛 TrimSource Generator 或 TypeInfoResolver反射路径可能被禁用,需要 SG 或显式配置
安全对外输出需最严转义默认编码器谨慎使用 UnsafeRelaxedJsonEscaping(仅非 HTML/JS 注入语境)

9. 反模式清单 🚫✅

  • Utf8JsonReader 读数值经 GetString()Parse
    ✅ 直接 GetInt32()/GetDecimal()/GetBoolean()
  • ❌ 使用 ValueSpan 直接比较属性名
    ✅ 用 ValueTextEquals("name")(自动反转义)
  • MemoryStream 中转请求体
    ✅ 用 BodyReader + Utf8JsonReader(ReadOnlySequence<byte>, …)(本文默认“收齐再解析”更稳)
  • ❌ 每次 new JsonSerializerOptions()
    缓存/复用同一实例(线程安全)
  • ❌ 在不安全语境全局开启 UnsafeRelaxedJsonEscaping
    ✅ 保持默认编码器或仅在确定安全的纯 JSON 语境启用

10. 复现步骤 🧪➡️🏁

  1. 新建解决方案与三个项目(ClassLib/Console/Web),按 §5 代码分别放入 JsonPerf.CodecsJsonPerf.BenchJsonPerf.Api;添加 NuGet 包 BenchmarkDotNet

  2. 启用生成器:在 JsonPerf.Codecs 中定义 PerfJsonContext[JsonSerializable] 标注;在 API 项(如需全局)将 JsonOptions.SerializerOptions.TypeInfoResolver = PerfJsonContext.Default

  3. 微基准:dotnet run -c Release -p src/JsonPerf.Bench(观察吞吐与 B/op;如需固定 Job,取消注释 [SimpleJob(...)])。

  4. 启动 API:dotnet run -c Release -p src/JsonPerf.Api

  5. 压测:

    • ./scripts/wrk.sh http://127.0.0.1:5000/order/sg(或 /ref/span)取吞吐/延迟分布;
    • ./scripts/hey.sh http://127.0.0.1:5000/order/sg 取 P50/P95/P99(可导 CSV)。
  6. 扰动实验:切换编码器/忽略条件/数字处理等,记录变化。

  7. AOT/Trim 验证(可选):在 csproj 添加:

    <PropertyGroup><PublishTrimmed>true</PublishTrimmed><TrimMode>full</TrimMode><PublishAot>true</PublishAot>
    </PropertyGroup>
    

    若出现“反射序列化被禁用”类错误,切换到 SG 或配置 TypeInfoResolver


11. FAQ(工程化小贴士)📝

  • SG 一定比反射快吗? 多数场景下冷启动与热路径都更优,但收益与模型复杂度相关;务必用你的数据基准化。
  • 手写 Span 如何保证健壮性? 增强 Token 验证、越界/类型不符检查;对不可信输入做 Fuzz;异常统一转 ProblemDetails
  • Minimal API 全局 JSON 设置builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>;控制器项目用 AddControllers().AddJsonOptions(...)
  • 进一步极限:可在可控环境试验 new JsonWriterOptions { SkipValidation = true }JsonReaderOptionsMaxDepth、尾逗号等)——性能与风险需权衡。⚖️

http://www.dtcms.com/a/357179.html

相关文章:

  • 并发编程——06 JUC并发同步工具类的应用实战
  • 如何高效批量完成修改文件名的工作?
  • NullPointerException 空指针异常,为什么老是遇到?
  • 嵌入式Ubuntu22.04安装过程详解实现
  • Oracle SQL性能调优之魂:深入理解索引原理与优化实践
  • 智能接听,破局高峰占线:云蝠AI客服重塑企业服务新范式
  • 【Spring底层分析】Spring AOP补充以及@Transactional注解的底层原理分析
  • 球型摄像机实现360°无死角
  • 【前端教程】从基础到专业:诗哩诗哩网HTML视频页面重构解析
  • 技术干货|Prometheus告警及告警规则
  • APM32芯得 EP.31 | APM32F402 HC-SR04超声测距经典操作:波形输出与滤波
  • 微算法科技(NASDAQ:MLGO)一种基于FPGA的Grover搜索优化算法技术引领量子计算
  • PCIe 6.0配置与地址空间架构:深入解析设备初始化的核心机制
  • C#实现OPC客户端
  • 《Password Guessing Using Random Forest》论文解读
  • system论文阅读--HPCA25
  • Excel Word Pdf 格式转换
  • ubuntu 安装 vllm
  • 电平移位器的原理
  • 群核科技--SpatialGen
  • pytest使用allure测试报告
  • Pytest 插件方法:pytest_runtest_makereport
  • 多方调研赋能AI+智慧消防 豪越科技人工智能创新获认可
  • 【网络安全领域】边界安全是什么?目前的发展及应用场景
  • java基本类型关键字
  • EasyExcel处理大数据量导出
  • 新手法务合同审查,有什么建议?
  • 单点登录(SSO)前端(Vue2.X)改造
  • 关于锁相放大器(LIA)的系统论文研究(重点于FPGA部分)
  • 设计模式:装饰模式(Decorator Pattern)