尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
YOLOv5+TensorRT+C++高并发推理服务部署实战
从 PyTorch 里的 demo 到线上高并发服务中间隔着一整条“性能鸿沟”。如果你也遇到过 GPU 利用率不到 30%、请求一多就超时、服务进程动不动内存翻倍的问题这篇基于 YOLOv5 TensorRT C HTTP 的部署实践应该能帮上忙。文章会把一条完整的高性能推理服务链路拆开讲为什么选这套组合、ONNX 怎么导出、TensorRT 引擎怎么构建、C 推理侧怎么让显存和内存“物尽其用”、HTTP 接口怎么设计、客户端怎么压测最后再把上线后踩过的坑一并复盘。先说结论同样的 YOLOv5s 模型、同一张 GPU我从 Python 方案迁到这套 C 方案之后单帧延迟从大约 120ms 降到 18ms 左右吞吐从不到 10 QPS 提到 50 QPS只算推理服务内部不含业务后处理而且内存占用从 4GB 稳定在 900MB 上下。数字不重要重要的是这套架构里每一个环节的选择逻辑理解了“为什么”换任何模型和硬件都能复用同一套思路。1. 为什么是 YOLOv5 TensorRT C 这套组合需求拆解与架构取舍1.1 先算一笔账模型快不等于服务快很多团队一开始跑 demo 都挺顺PyTorch 加载权重OpenCV 读图直接 forward结果也很准。但只要把服务往外一放并发一上来性能立刻崩。这不是模型本身的问题而是整个服务链路没有为“高吞吐”做过专门设计。我习惯先拆解一次完整请求的时间构成。典型的 Python Flask PyTorch 方案里一个检测请求大概消耗在以下环节HTTP 请求解析、图片字节读取5~15ms取决于 Web 框架和网络库。图片解码JPEG 转 BGR10~40ms纯 CPU 操作且 OpenCV 的 JPEG 解码在高分辨率图片上非常吃资源。预处理resize、归一化、HWC 转 CHW5~10msPython 循环处理逐像素操作时更慢。模型推理GPU 上实际跑 YOLOv5s 可能只有 10~20ms却被上述 CPU 操作拖累。后处理阈值过滤、NMS10~30ms纯 Python 实现时尤其明显。结果序列化与返回5~10ms。把这些加起来单帧 120ms 一点都不夸张。更要命的是 Python 的 GIL 让多线程推理形同虚设进程多开又导致显存翻倍。GPU 在大部分时间都在空转因为 CPU 侧把饭送到嘴边之前GPU 早就把上一口吃完了。C 解决的就是这几个痛点图片解码、预处理、后处理全部用编译型代码完成内存预分配、避免频繁 small object 分配多线程并行没有 GIL 卡脖子。TensorRT 解决的是模型本身的执行效率把 GPU 上的算子做融合和精度重排。这两个东西叠起来才是“高性能”的真正来源。1.2 TensorRT 带来的收益是算子级优化不是玄学有人觉得 TensorRT 就是把模型“变了个格式”加速效果全靠玄学。其实它的核心优化路径是清晰的算子融合把卷积 偏置 激活函数合并成单个 kernel减少显存读写和 kernel 启动开销。精度选择FP16、INT8 等低精度计算在大多数视觉模型上损失精度很小但吞吐提升明显。Kernel 自动调优针对具体 GPU 架构选择最优的 kernel 实现。显存复用通过分析整个计算图复用中间张量的显存减少峰值显存占用。所以 TensorRT 的收益不是靠运气它是把 GPU 计算过程中的“空转时间”和“多余搬运”尽量挤掉了。对于 YOLOv5 这种以卷积为主的模型收益尤其明显。FP32 转 FP16 通常能带来 1.5 到 2 倍的推理速度提升显存占用还能大幅下降。INT8 则能再进一步但需要校准数据集适合对精度损失接受度较高的场景。1.3 HTTP 为什么仍然是最合适的对外接口有人会问都追求高性能了为什么不直接用 gRPC 或者裸 TCP我的选择是方案优势劣势适用场景HTTP JSON调试简单、工具链成熟、防火墙友好、可读性强序列化/反序列化有开销内部服务、公网 API、需要快速迭代HTTP 原始字节省去 base64 开销性能接近 TCP需要约定 Content-Type不够通用图片检测/分类这类媒体处理服务gRPC二进制协议高效自带流式、负载均衡需要生成 proto、部署工具链更重微服务间标准化通信裸 TCP性能极值没有协议层粘包拆包全靠自己写极度在意延迟且团队有能力造轮子在实际项目中我们最后还是选了 HTTP 原始字节请求体 JSON 响应。理由很现实内部服务要经常调试curl 一条命令就能发图测试负载均衡器和监控工具对 HTTP 的支持最好业务侧前端、后端、脚本都能轻松对接团队不需要引入额外 SDK。HTTP 本身确实有协议开销但只要做好连接复用把请求体设计成原始图片字节而不是 base64性能差距会大幅缩小。真正影响性能的从来不是协议本身而是协议之上的序列化方式和连接管理策略。2. 模型侧的准备从 PyTorch 权重到可部署的 TensorRT 引擎2.1 先导出 ONNXopset、动态维度是第一个坑TensorRT 不能直接吃 PyTorch 权重通常要经过 ONNX 这个中间格式。导出 ONNX 这一步很多人觉得简单实际上踩坑最多。我的导出脚本大概长这样关键点我都写在注释里import torch import torch.nn as nn from models.experimental import attempt_load model attempt_load(yolov5s.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, yolov5s.onnx, opset_version11, input_names[images], output_names[output], dynamic_axes{ images: {0: batch, 2: height, 3: width}, output: {0: batch}, }, do_constant_foldingTrue, verboseFalse, )这里有个经验opset 版本不是越高越好。TensorRT 对高版本 ONNX 算子支持不一定及时我一般锁定在 11 或 12。opset 太高导致某些算子不支持反而要在 TensorRT 里回退到低效实现。如果导出后某些自定义算子无法被 TensorRT 解析优先检查是不是用了 YOLOv5 里某些特殊模块导出成了兼容性差的算子组合。另一个经验是动态维度。动态 batch、动态宽高意味着 TensorRT 在运行时可能要做更复杂的显存规划和 kernel 选择性能通常比固定尺寸略低。如果业务场景的输入分辨率可以固定我建议直接固定成 640×640省掉一堆麻烦。如果一定要动态至少把宽高在 320 到 1280 之间设置 profile避免极端尺寸导致显存规划失控。2.2 构建 TensorRT 引擎相同源码不同精度性能差一倍TensorRT 引擎构建通常分两步创建 builder、network 和 config然后构建 engine。构建阶段非常耗时可能从几十秒到几分钟不等所以一定要把构建好的 engine 序列化到磁盘之后直接从文件加载而不是每次启动都重新构建。构建环节的伪代码逻辑如下// 从 ONNX 解析网络结构 IBuilder* builder createInferBuilder(logger); INetworkDefinition* network builder-createNetworkV2(1U static_castint(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH)); IParser* parser createParser(network, logger); parser-parseFromFile(yolov5s.onnx, 1); // 配置构建选项 IBuilderConfig* config builder-createBuilderConfig(); config-setMemoryPoolLimit(MemoryPoolType::kWORKSPACE, 1U 30); // 工作空间上限 1GB config-setFlag(BuilderFlag::kFP16); // 开启 FP16 // 构建引擎并序列化到文件 IHostMemory* serialized builder-buildSerializedNetwork(*network, *config); std::ofstream out(yolov5s_fp16.trt, std::ios::binary); out.write(static_castchar*(serialized-data()), serialized-size());关于精度的选择我给一个参考表精度推理速度显存占用mAP 损失适用场景FP32基准高无调试、精度要求极高FP16约 1.5~2 倍于 FP32约为 FP32 的 60%通常 0.5%绝大多数生产环境首选INT8约为 FP32 的 2~4 倍更低1%~3%取决于校准集对速度极端敏感、可接受精度损失实际项目里我默认走 FP16。INT8 我在另一个场景试过校准集选择和训练集分布差异大时检测小物体的精度崩得厉害所以除非有完整校准方案否则不建议无脑上 INT8。还有一个很多人忽视的点kWORKSPACE的工作空间大小。给太小会导致某些融合算子无法启用给太大虽然构建阶段可能成功但运行时显存规划会偏保守。我的经验是 1GB 上限通常够用没必要给到几 GB除非是超大模型。2.3 模型仓库与引擎版本管理否则你会在深夜后悔模型一旦进了 TensorRT就变成了一个文件而且这个文件是“绑定 GPU 架构”的。同一份引擎文件在 A 卡上构建拷贝到 B 卡大概率跑不起来或者性能极差。所以引擎文件必须和 GPU 型号、TensorRT 版本、输入尺寸、精度一起做版本管理。我的做法是在目录下放一个model_info.json记录元数据{ model_name: yolov5s, precision: fp16, input_size: [640, 640], trt_version: 8.4.1, gpu: some_gpu, batch_range: [1, 8], engine_file: yolov5s_fp16.trt, build_time: 2024-06-01T12:00:00Z }每次启动服务时先读这个文件校验当前运行环境的 GPU 和 TensorRT 版本不匹配就直接报错退出而不是等跑到一半才发现模型输出一堆乱码或者干脆崩溃。这个习惯帮我避免了至少三次“测试环境好端端、生产环境一启动就崩”的深夜事故。3. C 推理侧的实现骨架从请求字节到检测框3.1 资源管理context 是线程私有的engine 是只读共享的TensorRT 的线程安全模型非常容易踩坑。ICudaEngine本身是可以多线程共享读取的适合全局只创建一次但IExecutionContext并不是线程安全的而且每个 context 的显存优化策略不同必须在各自线程里独立创建。所以我这边核心对象的生命周期设计是全局唯一ICudaEngine*启动时加载全生命周期复用。每个推理线程一个IExecutionContext*线程启动时创建只在当前线程内使用。每个线程一个cudaStream_t用于异步推理避免阻塞 CPU 侧。第一版代码贪图省事让所有线程共享同一个 context结果并发一上来直接报cudaErrorMisalignedAddress或者干脆输出错的检测结果。这属于 TensorRT 中最不应该拿来“赌一把”的环节老老实实按线程绑定 context。3.2 预处理必须和训练时保持一致letterbox 与归一化YOLOv5 训练时用了 letterbox 预处理把原图等比缩放到目标尺寸剩余区域填充灰色114, 114, 114。如果你在部署阶段把图片直接 resize 到 640×640而不是 letterbox小目标检测精度会明显下降因为图中物体被拉伸变形了。我封装的预处理逻辑如下用 C 实现比 Python 快一个数量级// 输入: cv::Mat src, 输出: float* dst (已经 NCHW 排列) void letterbox(const cv::Mat src, float* dst, int target_size) { int w src.cols, h src.rows; float scale std::min(static_castfloat(target_size) / w, static_castfloat(target_size) / h); int new_w static_castint(w * scale); int new_h static_castint(h * scale); cv::Mat resized; cv::resize(src, resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); cv::Mat canvas(target_size, target_size, CV_8UC3, cv::Scalar(114, 114, 114)); resized.copyTo(canvas(cv::Rect((target_size - new_w) / 2, (target_size - new_h) / 2, new_w, new_h))); // 归一化: HWC - CHW, 像素值除以 255 int channels 3; for (int c 0; c channels; c) { for (int i 0; i target_size * target_size; i) { dst[c * target_size * target_size i] canvas.data[i * channels c] / 255.0f; } } }这里有个性能细节逐像素循环不要用普通双层循环按 pixel 遍历而是按通道连续内存遍历这样 CPU 缓存命中率高得多。实测同样一张 1080p 图片按通道遍历比按像素遍历快 30% 以上。3.3 推理与输出解析YOLOv5 的 25200 个候选框要怎么处理YOLOv5 在 640×640 输入下输出张量形状是[1, 25200, 85]。25200 来源于三个检测头80×80×3 40×40×3 20×20×385 对应4 个坐标 1 个置信度 80 个类别。TensorRT 推理完拿到的是一个连续 buffer后面就是纯 CPU 的解析工作。我的后处理思路是分两步走第一步先按置信度阈值过滤。遍历 25200 个候选框只保留 score 0.25 的框同时记录类别索引。这一步把绝大多数候选框直接淘汰后续 NMS 的数据量大幅降低。第二步做类别内 NMS。对每个类别单独做 IoU 过滤IoU 阈值一般是 0.45 到 0.5。这个逻辑用 C 实现时最简单的就是双重循环保留最高分框实测候选框少的时候性能完全够用。代码核心逻辑如下struct Detection { float x1, y1, x2, y2; // 相对于原图的坐标 float score; int class_id; }; std::vectorDetection decodeOutput(float* raw_output, int orig_w, int orig_h, float conf_thresh, float iou_thresh) { const int num_boxes 25200; const int num_classes 80; std::vectorDetection result; for (int i 0; i num_boxes; i) { float* row raw_output i * 85; float box_conf row[4]; if (box_conf conf_thresh) continue; int class_id -1; float max_cls 0.0f; for (int j 0; j num_classes; j) { if (row[5 j] max_cls) { max_cls row[5 j]; class_id j; } } float final_score box_conf * max_cls; if (final_score conf_thresh) continue; // 这里的坐标是相对于模型输入尺寸的需要映射回原图 // x, y, w, h 的原始含义由 YOLOv5 的 anchor 编码决定 Detection det; det.score final_score; det.class_id class_id; // 省略坐标反算和 letterbox 偏移还原的代码... result.push_back(det); } // 按类别做 NMS std::vectorDetection nms_result; // 双重循环按 score 降序做 IoU 抑制... return nms_result; }实际生产代码比这个复杂因为坐标的反算要考虑每个检测头不同的 stride以及 letterbox 的缩放和偏移还原。但这些是实现细节原理就是先把模型输出转化为“原图坐标系的框”再做 NMS。这里提醒一句如果直接拿解析出来的框框去画图经常发现框偏了、尺寸不对绝大多数问题是 letterbox 反还原没做对。务必把缩放比例和 pad 偏移记录清楚从模型坐标映射回原图坐标时要算回去。3.4 显存和 CPU 内存的预分配别在请求路径上 new 数组高性能服务的大忌是在请求处理过程中频繁做动态内存分配。虽然 C 有内存分配器但高频请求下的new/delete仍会造成性能波动和内存碎片。我的做法是输入输出 buffer 全部预先分配好按最大 batch 和最大输入尺寸分配服务生命周期内不释放。每个推理线程维护自己的临时缓冲区和后处理容器线程之间不共享。JPEG 解码的图片 Mat 尽量复用OpenCV 的imdecode目标 Mat 可以在循环外创建用cv::Mat传引用让内部复用分配好的内存。有人会觉得预分配浪费内存但一台推理服务器上显存往往比内存更稀缺。把 CPU 内存预分配个几百 MB 换稳定的帧间隔这个账很划算。4. HTTP 服务层怎么设计才能兜住高性能推理4.1 接口约定图片走原始字节结果走 JSON我见过不少团队把图片 base64 编码后放到 JSON 里上传这是性能杀手。一张 200KB 的 JPEGbase64 编码后变成约 267KB多出 33% 的体积还增加了编码和解码的 CPU 开销。在高 QPS 下这部分开销会占掉相当一部分 CPU 预算。我们的接口约定很简单POST /detect Content-Type: image/jpeg Body: 原始图片字节 Response: { code: 0, time: 18.2, detections: [ {x1: 120, y1: 40, x2: 260, y2: 300, score: 0.92, class_id: 0} ] }这样做的好处有三点请求体是纯字节流服务端拿到后直接cv::imdecode省去 base64 解码。响应体仍然是 JSON方便业务侧解析不用自己写二进制协议。用 curl 调试时一眼能看明白不需要额外写 client。如果要支持多张图片我建议还是拆成多次请求而不是搞一个“批量图片数组”的接口。因为 HTTP 连接可以复用多次请求的开销远小于单次请求内传多张图的复杂度。除非你实现服务端自动凑 batch下一节讲否则不要把多个图片塞进一个请求。4.2 线程模型请求线程只做分发推理线程池承载计算如果直接把 HTTP 请求处理函数里做推理并发一高就会出现两种问题一是请求线程阻塞导致后续请求排队二是多个请求同时访问 TensorRT context 引发崩溃。更合理的设计是解耦一个或几个 IO 线程负责接收 HTTP 请求、解析图片、写入任务队列。一个固定大小的推理线程池负责从队列取任务执行 TensorRT 推理和后处理把结果放回响应队列。IO 线程再把结果写回 HTTP 连接。这个生产者-消费者模型的好处是HTTP 接收和模型推理可以并行图片解码可以流水线化下一张图的解码和上一张图的推理同时在进行。具体到线程数量我建议推理线程数不超过 TensorRT 引擎的最大并发数通常 2~4 个就够了IO 线程数和 CPU 核数相关。线程不是越多越好因为 GPU 上的 kernel 是串行执行的太多线程抢同一个 GPU 只会增加上下文切换开销。4.3 自动 batch把并发请求凑成一帧推理这是性能优化里最值得做的一招。TensorRT 的 batch 越大单位时间处理的总帧数越高。YOLOv5s 在 batch1 时可能只有 50 QPS但 batch4 时能达到 120 QPS 以上因为 GPU 计算资源被更充分地利用。实现方式是在任务队列上做“批聚合”推理线程从队列取任务时如果队列里还有等待任务而且当前 batch 没到上限就多取几个任务一起推理。关键是要设置一个最大等待时间比如 3ms避免为了凑 batch 让单帧延迟增加到用户无法接受的程度。伪代码如下std::vectorTask batch; batch.push_back(queue.pop()); while (batch.size() max_batch queue.wait_for(chrono::milliseconds(3))) { batch.push_back(queue.pop()); } // 统一 resize 到模型输入尺寸执行 TensorRT 推理 runInference(batch);这里有一个映射关系非常重要batch 里每个任务对应输入 tensor 的一维推理完成后按顺序把输出拆分回各个任务。只要任务顺序不要乱一切问题都好说。5. 客户端与压测验证性能不能只靠“感觉”5.1 客户端该关注什么连接复用 合理超时很多人在压测服务器时用 Postman 或者简单的单线程脚本发几十个请求就算完。这样测出来的数据跟生产环境完全不是一回事。正确的高性能客户端要做两件事连接复用和并发请求。如果用的是 libcurl开启连接复用很容易同一 CURL 句柄连续发起请求即可curl 会自动复用 HTTP keep-alive 连接。更省事的做法是封装一个连接池每个线程一个自己的 curl 句柄避免句柄跨线程使用。超时设置也很关键。服务端如果暂时满负荷客户端立刻超时重试大量重试会导致雪崩。我的经验是连接超时设 2 秒请求超时设 5 秒重试次数不超过 2 次重试之间加指数退避。5.2 压测方法与数据一次完整的性能验证流程拿一批有代表性的真实图片不要用纯白图纯黑图阳性样本至少要占一半循环 POST 到服务端。压测时我习惯关注三个指标平均延迟所有请求的耗时均值。P95/P99 延迟代表最差情况下的体验。QPS每秒能处理多少请求。我这边在一张消费级 GPU 上做过的对照数据大概是这样方案平均延迟P99 延迟QPS显存占用内存占用Python PyTorch Flask122ms380ms8约 4GB4.2GBC PyTorch HTTP68ms150ms15约 4GB1.5GBC TensorRT FP16 HTTP21ms42ms46约 1.2GB0.9GBC TensorRT FP16 自动 batch18ms35ms55约 1.3GB0.9GB数据是参考值不同图片分辨率、不同模型大小都会带来浮动但趋势是稳定的模型推理引擎的优化TensorRT和语言侧开销的削减C两者叠加才能带来数量级的提升。压测过程中要盯着 GPU 利用率看如果 GPU 利用率一直上不去比如低于 50%通常意味着 CPU 侧预处理或者 HTTP 解析成了瓶颈如果 GPU 利用率长期 90% 以上说明服务器已经接近上限加 CPU 核也没用只能换更强的 GPU 或者缩小输入尺寸。我第一版压测时发现 QPS 到 30 就上不去了GPU 利用率只有 60%排查后发现 JPEG 解码全在一个线程里执行成了瓶颈。后来把解码挪到多个 IO 线程并行QPS 立刻跳了一大截。这类瓶颈在监控数据上非常明显前提是你真的去看监控。5.3 客户端压测的常见误区和数据处理细节有人压测时直接用本地生成的随机噪声图这种数据完全不能代表真实场景。随机图的解码耗时、检测出的候选框数量都跟真实业务差得很远导致压测结果虚高或虚低。正确做法是抓一批线上真实图片覆盖不同分辨率、不同光照、不同物体数量放到本地一个测试目录每次压测随机抽取。候选框数量对后处理的影响也很大。如果一张图里有几十个目标后处理 NMS 的开销会比一张空场景图高好几倍。压测报告里最好标注“平均每帧检测框数”否则跟别人对比性能数字时根本没有参照系。6. 上线后踩过的坑与调优复盘6.1 图片解码险些成为最大的性能瓶颈这是我在第一版压测时遇到的第一个问题。直觉上模型推理应该是最慢的但实际 profiling 发现 JPEG 解码占了整个请求链路 35% 以上的 CPU 时间。特别是手机拍摄的高像素照片一张 1200 万像素的 JPEG解码就要 40~50ms。解决办法有三层限制上游传入图片大小超过 1920 像素的图片先由客户端缩略。服务端解码前先读取 JPEG 头部如果尺寸过大立刻拒绝或降采样解码。解码放在 IO 线程池里并行执行而不是在推理线程里串行处理。第二层尤其重要。OpenCV 的imdecode有降采样解码参数IMREAD_REDUCED_COLOR_2可以直接按 1/2 尺寸解码速度能快 3 倍以上。YOLO 反正要 letterbox 到 640源图分辨率超过 1280 之后再高的像素对检测精度几乎没有贡献不如提前降采样换取速度。6.2 推理并发没来得及处理时 HTTP 超时队列无限增长让内存爆掉流量高峰时任务队列里的请求会越积越多。如果队列是无界的内存占用会一路飙升到 OOM。这个问题在生产环境出现过一次排查到最后发现罪魁祸首就是std::queue::push_back没有做容量限制。解决方案是带背压的有界队列。当队列满了新的请求直接返回503 Service Unavailable而不是无限排队。业务端对这种错误会有兜底策略比如稍后重试、降级到备用模型服务总比让整个服务进程 OOM 重启要好。队列深度建议根据最大可接受等待时间反推。比如单帧推理 20ms允许请求排队 200ms那队列深度200ms / 20ms 10有界队列就控制在 10 到 20 之间留一点缓冲。6.3 TensorRT 上下文与动态输入 shape 的配合问题之前提到 context 是线程私有的但还有一个坑是动态输入 shape。使用动态 batch 时每个 context 需要调用setInputShape设置当前推理的输入形状而且这个设置必须在每次推理前做因为下一次推理的 batch size 可能变了。代码很容易写出这种 bug第一次推理 batch4 成功后第二次推理 batch2 时忘了重新setInputShape结果 TensorRT 内部还在按上一轮的 shape 处理输出 buffer 越界写入整个进程崩溃。我的习惯是封装一个InferenceSession类把“设置 shape - 绑定 buffer - 执行推理 - 等待流完成”这个流程固定下来每次推理都完整执行不留任何“上次状态”的依赖。6.4 从结果 JSON 到业务端的数据合同这个坑不在服务端而在服务端和业务端的协作上。YOLOv5 的类别 ID 是 COCO 数据集的 80 类编号业务侧如果不做映射经常会遇到“明明检测到了 person但业务端当成了 dog”这种低级的乌龙。我在服务返回里额外加了一个class_name字段而不是只返回class_id虽然增加了几个字节的传输但极大地减少了跨团队协作时的误解。另外坐标一律用浮点数返回业务端负责四舍五入到整数不要在服务端提前截断否则画框时边缘会歪。6.5 最终效果复盘性能之外这套架构带来的长期收益上线三个月后回头看这套 YOLOv5 TensorRT C HTTP 的架构带来最大的收获其实不是快了多少倍而是服务的可预测性。Python 方案在流量高峰期延迟会剧烈抖动C 方案即使在接近满载时P99 延迟也只是平均延迟的两倍左右整体表现非常平稳。运维上也简单了不少。服务进程启动后加载引擎文件再起固定数量的线程资源占用从头到尾基本不变不用担心 Python 里 GC 导致的内存波动。日志系统用标准输出打 JSON 日志接入监控告警非常顺手。如果你准备在自己项目里复刻这套方案我最后再给一条建议先把数据链路跑通再谈优化。先用最简单的 C 代码把 HTTP 请求、推理、响应全部连通用压测脚本记录基线数据然后一项一项做优化每步都对照数据看收益。性能优化最怕一上来就堆复杂架构最后都不知道瓶颈在哪、优化到底有没有效果。
RELATED

相关推荐

YOLOv8水面垃圾检测系统:从数据标注到Web部署全流程解析

YOLOv8水面垃圾检测系统:从数据标注到Web部署全流程解析

做水面垃圾检测这个方向也有一段时间了,陆续看到不少同学拿着类似的思路来找我聊:手上已经有标注好的数据集,想用 YOLOv8 跑一个水面湖面垃圾分类检测系统,但又担心只是“换个数据集跑个模型”,看不出深度,…

📅 2026/10/20 14:02:31
从模糊标题到可运行原型:以rea为例的逆向拆解与工程实践

从模糊标题到可运行原型:以rea为例的逆向拆解与工程实践

1. 当标题只剩下三个字母:一次“信息真空”下的逆向拆解拿到“rea”这个标题的时候,我第一反应是愣了一下。没有项目正文,没有关键词,没有摘要描述,连热搜词和网络热词都是空的。换句话说,除了这三个字母&a…

📅 2026/10/20 14:02:31
光纤聚焦镜在高温检测中的光路调度原理与实操指南

光纤聚焦镜在高温检测中的光路调度原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/20 14:02:31
MORE NEWS

更多资讯

📰

智能提示系统秒级扩容实战:从架构改造到启动即热

去年 Q4,我们团队接过一个让我印象很深的故障单。晚上八点整,某业务入口的流量在三十秒内翻了六倍,智能提示服务是最先扛不住的,候选词接口的平均耗时从 20ms 一路拉到 3.8 秒,紧接着数据库连接被打满,整条…

📰

微信支付API v3 PHP后端对接实战:签名验签、AES解密与完整支付流程实现

做小程序支付的后端对接,PHP 开发者大概率都会在微信支付 API v3 这一关卡上一段时间。官方给的 SDK 主要面向 Java、Go 这些服务端语言,PHP 生态里没有一套“官方盖章”的完整实现,网上流传的代码又版本各异,改起来常常一头雾水。…

📰

VideoLLM复现指南:token压缩、音频接入与时间感知

VideoLLM方向的新论文,读到现在是第三篇笔记了。前两篇我主要梳理了多模态主干的接法、指令数据的构造和基础评估这些地基问题,这一篇想换一条主线:不按模型结构排,也不照排行榜念,而是挑几个自己复现时真正觉得有得聊…

📰

ARMCC 5.06安装与AC5切换实战:解决老嵌入式工程编译难题

简介:ARM Compiler 5.06(960)是ARM推出的官方C编译器,专为基于ARM架构的嵌入式开发设计,与Keil MDK工具链无缝集成,面向嵌入式硬件工程师和物联网开发者,能够将C语言工程高效编译为可烧录的机器…

📰

基于DQN的无人机三维航迹规划:PyTorch实现与工程实践

最近一直在折腾无人机航迹规划,试过A*、RRT这些传统方法,也试过PID跟踪,但一到三维城市环境里就各种碰壁:地图一复杂,搜索效率掉得厉害;环境一变,路径就得从头算。后来把强化学习搬进来&#xf…

📰

Android Compose AI组件库实战:流式对话、多模态与生产落地指南

上个月我在重构一个智能问答类应用时,突然意识到Android Compose版本AI组件库的出现确实改变了传统接入大模型的方式。以前我们要接一个对话模型,得自己搭网络层、处理流式响应、管理UI状态,代码量很容易成倍膨胀。现在有了Compose版的AI组件…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬