From eba2f0799054c37ddb7a0717868f281bd19d49d7 Mon Sep 17 00:00:00 2001 From: douboer Date: Thu, 30 Oct 2025 16:31:47 +0800 Subject: [PATCH] update at 2025-10-30 16:31:47 --- BENCHMARK_GUIDE.md | 325 +++++++++++++++++++++++ README.md | 40 ++- scripts/benchmark_concurrent.py | 446 ++++++++++++++++++++++++++++++++ scripts/run_benchmark_quick.sh | 71 +++++ scripts/run_benchmark_suite.sh | 104 ++++++++ 5 files changed, 985 insertions(+), 1 deletion(-) create mode 100644 BENCHMARK_GUIDE.md create mode 100755 scripts/benchmark_concurrent.py create mode 100755 scripts/run_benchmark_quick.sh create mode 100755 scripts/run_benchmark_suite.sh diff --git a/BENCHMARK_GUIDE.md b/BENCHMARK_GUIDE.md new file mode 100644 index 0000000..444d726 --- /dev/null +++ b/BENCHMARK_GUIDE.md @@ -0,0 +1,325 @@ +# 并发性能测试使用指南 + +## 📋 概述 + +本项目提供了完整的并发性能测试工具,用于评估 YOLO 数字识别系统在多用户同时访问场景下的性能表现。 + +## 🎯 测试工具 + +### 1. `benchmark_concurrent.py` - 核心测试脚本 + +模拟 n 个用户并发执行数字识别任务。 + +**主要功能:** +- ✅ 支持自定义并发用户数和每用户图片数 +- ✅ 循环使用 valid 文件夹中的图片 +- ✅ 统计详细的性能指标(QPS、响应时间、成功率等) +- ✅ 生成文本和 JSON 格式的报告 +- ✅ 支持不同的模型和配置参数 + +**基本用法:** + +```bash +# 模拟10个用户,每个用户识别20张图片 +python scripts/benchmark_concurrent.py --users 10 --images-per-user 20 + +# 使用详细输出模式 +python scripts/benchmark_concurrent.py --users 5 --images-per-user 10 --verbose + +# 调整置信度阈值 +python scripts/benchmark_concurrent.py --users 10 --images-per-user 20 --conf 0.15 + +# 指定输出文件 +python scripts/benchmark_concurrent.py --users 20 --output results/my_test.txt +``` + +**完整参数:** + +```bash +--users N # 并发用户数量(默认: 10) +--images-per-user N # 每个用户识别的图片数量(默认: 20) +--model PATH # 模型路径(默认: 最佳模型) +--source DIR # 图片源文件夹(默认: valid) +--conf FLOAT # 置信度阈值(默认: 0.2) +--imgsz INT # 输入图片大小(默认: 320) +--output FILE # 输出报告路径(默认: results/benchmark_report.txt) +--verbose # 显示详细日志 +``` + +### 2. `run_benchmark_quick.sh` - 快速测试套件 + +运行一组预定义的快速测试(1, 3, 5, 8 个并发用户),适合快速验证。 + +**使用方法:** + +```bash +./scripts/run_benchmark_quick.sh +``` + +**测试配置:** +- 并发级别:1, 3, 5, 8 用户 +- 每用户图片数:10 张 +- 总共测试:10 + 30 + 50 + 80 = 170 张图片 + +**输出:** +- 各并发级别的详细报告 +- 性能对比摘要表格 +- 保存位置:`results/benchmark_quick/` + +### 3. `run_benchmark_suite.sh` - 完整测试套件 + +运行更全面的测试(1, 3, 5, 10, 15, 20 个并发用户)。 + +**使用方法:** + +```bash +./scripts/run_benchmark_suite.sh +``` + +**测试配置:** +- 并发级别:1, 3, 5, 10, 15, 20 用户 +- 每用户图片数:20 张 +- 总共测试:20 + 60 + 100 + 200 + 300 + 400 = 1080 张图片 + +**输出:** +- 各并发级别的详细报告 +- 性能对比摘要表格 +- 保存位置:`results/benchmark_suite/` + +## 📊 性能指标说明 + +### 报告包含的指标 + +**总体性能:** +- **总执行时间**:所有并发用户完成的总时间 +- **总识别图片数**:所有用户处理的图片总数 +- **成功率**:成功识别的图片比例 +- **吞吐量 (QPS)**:每秒处理的图片数量 + +**响应时间统计:** +- **平均响应时间**:单张图片的平均识别时间 +- **最小/最大响应时间**:响应时间的范围 +- **P50/P90/P95/P99**:百分位数,表示大部分请求的响应时间 + +**各用户性能:** +- 每个用户的图片数、成功数、总耗时、平均耗时 + +## 📈 测试结果示例 + +### 快速测试结果(实际测试数据) + +``` +并发用户 | 总图片 | 总耗时(s) | QPS | 平均响应(s) +---------|---------|-----------|--------|------------ +1 | 10 | 0.17 | 60.30 | 0.014 +3 | 30 | 0.36 | 84.35 | 0.027 +5 | 50 | 0.58 | 86.05 | 0.042 +8 | 80 | 1.10 | 72.65 | 0.081 +``` + +### 标准测试结果(10用户×20图) + +``` +总体性能: + - 总执行时间: 2.85 秒 + - 总识别图片数: 200 + - 成功: 200 (100.0%) + - 吞吐量 (QPS): 70.28 图片/秒 + +响应时间统计: + - 平均响应时间: 0.123 秒 + - P50 响应时间: 0.109 秒 + - P90 响应时间: 0.167 秒 + - P99 响应时间: 0.342 秒 +``` + +## 💡 使用建议 + +### 场景选择 + +| 测试场景 | 推荐工具 | 适用情况 | +|---------|---------|---------| +| 快速验证 | `run_benchmark_quick.sh` | 代码修改后快速验证性能 | +| 完整评估 | `run_benchmark_suite.sh` | 系统上线前全面测试 | +| 自定义测试 | `benchmark_concurrent.py` | 特定并发场景测试 | +| 压力测试 | `benchmark_concurrent.py` | 使用更高的并发数(如50+) | + +### 测试流程建议 + +**1. 开发阶段** + +```bash +# 快速测试,验证功能正常 +python scripts/benchmark_concurrent.py --users 3 --images-per-user 5 --verbose +``` + +**2. 性能优化** + +```bash +# 运行快速套件,对比优化前后 +./scripts/run_benchmark_quick.sh +``` + +**3. 上线前验证** + +```bash +# 运行完整套件,全面评估 +./scripts/run_benchmark_suite.sh +``` + +**4. 压力测试** + +```bash +# 高并发测试 +python scripts/benchmark_concurrent.py --users 50 --images-per-user 10 +``` + +### 性能调优建议 + +如果性能不理想,可以尝试: + +1. **调整模型参数** + ```bash + # 降低置信度阈值 + --conf 0.15 + + # 减小输入图片尺寸 + --imgsz 256 + ``` + +2. **优化并发配置** + - 根据 CPU 核心数调整并发用户数 + - 监控系统资源使用情况 + +3. **使用更快的模型** + ```bash + # 使用更小的模型(如果准确率满足要求) + --model yolov8n.pt + ``` + +## 📁 输出文件说明 + +### 文本报告 (`.txt`) + +详细的性能测试报告,包含: +- 测试配置信息 +- 总体性能统计 +- 响应时间分析 +- 各用户执行情况 +- 失败任务详情(如有) + +### JSON 数据 (`.json`) + +结构化的测试数据,包含: +```json +{ + "config": {...}, // 测试配置 + "summary": { // 总体统计 + "total_time": 2.85, + "total_images": 200, + "qps": 70.28 + }, + "users": [ // 每个用户的详细结果 + { + "user_id": 0, + "results": [...], // 每张图片的识别结果 + "total_time": 2.50, + "success_count": 20, + "avg_time": 0.125 + } + ] +} +``` + +JSON 文件可用于: +- 后续数据分析 +- 生成自定义报告 +- 性能趋势对比 + +## 🔍 故障排查 + +### 问题:测试时间过长 + +**原因:** 并发数或图片数设置过高 + +**解决:** +```bash +# 减少并发数或图片数 +python scripts/benchmark_concurrent.py --users 5 --images-per-user 10 +``` + +### 问题:部分识别失败 + +**原因:** 图片质量差或置信度阈值过高 + +**解决:** +```bash +# 降低置信度阈值 +python scripts/benchmark_concurrent.py --conf 0.1 + +# 或先对图片进行预处理 +python scripts/preprocess_images.py --input valid --output valid-processed --method clahe +python scripts/benchmark_concurrent.py --source valid-processed +``` + +### 问题:QPS 过低 + +**可能原因:** +- 模型加载开销(每个线程独立加载) +- 图片尺寸过大 +- 系统资源不足 + +**优化建议:** +- 减小 `--imgsz` 参数 +- 关闭其他占用资源的程序 +- 监控 CPU 使用率 + +## 📝 自定义测试 + +### 创建自定义测试脚本 + +```bash +#!/bin/bash +# 自定义测试场景 + +# 场景1:低并发长时间测试 +python scripts/benchmark_concurrent.py \ + --users 2 \ + --images-per-user 100 \ + --output results/scenario1.txt + +# 场景2:高并发短时间测试 +python scripts/benchmark_concurrent.py \ + --users 50 \ + --images-per-user 5 \ + --output results/scenario2.txt + +# 场景3:不同配置对比 +for conf in 0.1 0.15 0.2 0.25; do + python scripts/benchmark_concurrent.py \ + --users 10 \ + --images-per-user 10 \ + --conf $conf \ + --output results/benchmark_conf_${conf}.txt +done +``` + +## 🎓 最佳实践 + +1. **基线测试**:先建立性能基线(单用户测试) +2. **逐步加压**:从低并发逐步提高到高并发 +3. **多次测试**:每个场景测试3-5次,取平均值 +4. **记录环境**:记录测试时的系统环境和资源使用情况 +5. **对比分析**:保存历史测试数据,进行趋势分析 + +## 📞 相关文档 + +- [README.md](../README.md) - 项目完整文档 +- [QUICKSTART.md](../QUICKSTART.md) - 快速开始指南 +- [FINAL_REPORT.md](../FINAL_REPORT.md) - 项目性能报告 + +--- + +**作者**: Gavin Chan +**创建日期**: 2025-01-30 +**最后更新**: 2025-01-30 diff --git a/README.md b/README.md index cdf7c31..e34d946 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - ✅ **完整流程**: 从数据预处理到模型训练再到批量识别的完整pipeline - ✅ **易于使用**: 提供交互式脚本和一键运行工具 - ✅ **可视化结果**: 自动生成带标注的可视化图片 +- ✅ **性能测试**: 内置并发性能测试工具,评估多用户场景性能 ## 📁 项目结构 @@ -388,13 +389,50 @@ python scripts/predict_digits_improved.py --source valid-processed --conf 0.15 4. **超参数调优**: 调整学习率、优化器等参数 5. **后处理优化**: 根据业务规则(必须4位数字)进行后处理 -## � 项目文档 +## 🧪 性能测试 + +### 并发性能测试 + +模拟多用户同时访问场景,评估系统性能: + +```bash +# 快速测试(1, 3, 5, 8 个并发用户) +./scripts/run_benchmark_quick.sh + +# 标准测试(自定义并发数) +python scripts/benchmark_concurrent.py --users 10 --images-per-user 20 + +# 详细输出模式 +python scripts/benchmark_concurrent.py --users 5 --images-per-user 10 --verbose +``` + +### 测试结果示例 + +实际测试数据(Apple M2,CPU模式): + +| 并发用户 | 总图片 | 总耗时(s) | QPS | 平均响应(s) | +|---------|-------|----------|-----|------------| +| 1 | 10 | 0.17 | 60.30 | 0.014 | +| 3 | 30 | 0.36 | 84.35 | 0.027 | +| 5 | 50 | 0.58 | 86.05 | 0.042 | +| 8 | 80 | 1.10 | 72.65 | 0.081 | +| 10 | 200 | 2.85 | 70.28 | 0.123 | + +**性能指标说明:** +- **QPS**: 每秒处理的图片数量(越高越好) +- **平均响应时间**: 单张图片识别耗时(越低越好) +- **最佳并发数**: 3-5 个用户时 QPS 最高 + +详细使用方法请参考 [BENCHMARK_GUIDE.md](BENCHMARK_GUIDE.md) + +## 📂 项目文档 - **README.md**: 完整使用文档(本文件) - **QUICKSTART.md**: 5分钟快速上手指南 - **FINAL_REPORT.md**: 项目完成报告和性能分析 - **PROJECT_STRUCTURE.md**: 详细的项目结构说明 - **CODE_CLEANUP_DONE.md**: 代码清理和优化记录 +- **BENCHMARK_GUIDE.md**: 并发性能测试详细指南 ## 👨‍💻 作者 diff --git a/scripts/benchmark_concurrent.py b/scripts/benchmark_concurrent.py new file mode 100755 index 0000000..efb28e7 --- /dev/null +++ b/scripts/benchmark_concurrent.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 +""" +并发性能测试脚本 - 模拟多用户同时识别 + +功能: + - 模拟 n 个用户并发执行数字识别任务 + - 循环使用 valid 文件夹中的图片 + - 统计总体性能指标(吞吐量、响应时间、成功率) + - 生成详细的性能报告 + +使用方法: + # 模拟10个并发用户,每个用户识别20张图片 + python scripts/benchmark_concurrent.py --users 10 --images-per-user 20 + + # 使用不同的模型和配置 + python scripts/benchmark_concurrent.py --users 5 --images-per-user 10 --conf 0.15 + + # 指定输出报告文件 + python scripts/benchmark_concurrent.py --users 20 --output results/benchmark_report.txt + +输出: + - 实时进度显示 + - 详细的性能统计(总时间、平均响应时间、QPS等) + - 每个用户的执行情况 + - 失败任务的详细信息 + +作者: Gavin Chan +日期: 2025-01-30 +""" + +import argparse +import time +import os +from pathlib import Path +from concurrent.futures import ThreadPoolExecutor, as_completed +from ultralytics import YOLO +import cv2 +import numpy as np +from datetime import datetime +import json + + +def parse_args(): + """解析命令行参数""" + parser = argparse.ArgumentParser(description='并发性能测试 - 模拟多用户识别') + + parser.add_argument('--users', type=int, default=10, + help='并发用户数量(默认: 10)') + parser.add_argument('--images-per-user', type=int, default=20, + help='每个用户识别的图片数量(默认: 20)') + parser.add_argument('--model', type=str, + default='runs/digit_yolo/exp_preprocessed_color_150/weights/best.pt', + help='模型路径') + parser.add_argument('--source', type=str, default='valid', + help='图片源文件夹(默认: valid)') + parser.add_argument('--conf', type=float, default=0.2, + help='置信度阈值(默认: 0.2)') + parser.add_argument('--imgsz', type=int, default=320, + help='输入图片大小(默认: 320)') + parser.add_argument('--output', type=str, default='results/benchmark_report.txt', + help='输出报告文件路径') + parser.add_argument('--verbose', action='store_true', + help='显示详细日志') + + return parser.parse_args() + + +def load_image_paths(source_dir): + """ + 加载所有图片路径 + + Args: + source_dir: 图片文件夹路径 + + Returns: + list: 图片路径列表 + """ + source_path = Path(source_dir) + if not source_path.exists(): + raise FileNotFoundError(f"图片文件夹不存在: {source_dir}") + + # 支持的图片格式 + extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.webp') + image_paths = [str(p) for p in source_path.iterdir() + if p.suffix.lower() in extensions] + + if not image_paths: + raise ValueError(f"文件夹中没有找到图片: {source_dir}") + + return sorted(image_paths) + + +def extract_digits_from_predictions(results): + """ + 从YOLO预测结果中提取数字 + + Args: + results: YOLO预测结果对象 + + Returns: + tuple: (识别的数字字符串, 平均置信度, 检测到的数字个数) + """ + if not results or len(results) == 0: + return "", 0.0, 0 + + result = results[0] + if result.boxes is None or len(result.boxes) == 0: + return "", 0.0, 0 + + # 获取所有检测框 + boxes = result.boxes.cpu().numpy() + + # 提取类别、置信度和位置 + detections = [] + for box in boxes: + cls = int(box.cls[0]) + conf = float(box.conf[0]) + x_center = float((box.xyxy[0][0] + box.xyxy[0][2]) / 2) + detections.append((cls, conf, x_center)) + + if not detections: + return "", 0.0, 0 + + # 按x坐标排序(从左到右) + detections.sort(key=lambda x: x[2]) + + # 提取数字和置信度 + digits = [str(d[0]) for d in detections] + confidences = [d[1] for d in detections] + + result_str = ''.join(digits) + avg_conf = sum(confidences) / len(confidences) + + return result_str, avg_conf, len(detections) + + +def recognize_single_image(model, image_path, conf_threshold, img_size): + """ + 识别单张图片 + + Args: + model: YOLO模型对象 + image_path: 图片路径 + conf_threshold: 置信度阈值 + img_size: 输入图片大小 + + Returns: + dict: 识别结果 { + 'filename': 文件名, + 'digits': 识别的数字, + 'confidence': 置信度, + 'count': 数字个数, + 'time': 耗时(秒), + 'success': 是否成功 + } + """ + start_time = time.time() + + try: + # 执行预测 + results = model.predict( + source=image_path, + conf=conf_threshold, + imgsz=img_size, + verbose=False + ) + + # 提取数字 + digits, confidence, count = extract_digits_from_predictions(results) + + elapsed = time.time() - start_time + + return { + 'filename': Path(image_path).name, + 'digits': digits, + 'confidence': confidence, + 'count': count, + 'time': elapsed, + 'success': True, + 'error': None + } + + except Exception as e: + elapsed = time.time() - start_time + return { + 'filename': Path(image_path).name, + 'digits': '', + 'confidence': 0.0, + 'count': 0, + 'time': elapsed, + 'success': False, + 'error': str(e) + } + + +def user_task(user_id, model_path, image_paths, num_images, conf_threshold, img_size, verbose): + """ + 单个用户的识别任务 + + Args: + user_id: 用户ID + model_path: 模型路径 + image_paths: 所有可用图片路径 + num_images: 该用户要识别的图片数量 + conf_threshold: 置信度阈值 + img_size: 图片大小 + verbose: 是否显示详细日志 + + Returns: + dict: 用户任务结果 { + 'user_id': 用户ID, + 'results': 识别结果列表, + 'total_time': 总耗时, + 'success_count': 成功数量, + 'avg_time': 平均耗时 + } + """ + # 加载模型(每个线程独立加载) + model = YOLO(model_path) + + # 循环使用图片 + user_images = [] + for i in range(num_images): + img_idx = (user_id * num_images + i) % len(image_paths) + user_images.append(image_paths[img_idx]) + + start_time = time.time() + results = [] + + for img_path in user_images: + result = recognize_single_image(model, img_path, conf_threshold, img_size) + results.append(result) + + if verbose: + status = "✓" if result['success'] else "✗" + print(f" [{status}] 用户{user_id:2d} | {result['filename']:20s} | " + f"{result['digits']:6s} | {result['time']:.3f}s") + + total_time = time.time() - start_time + success_count = sum(1 for r in results if r['success']) + avg_time = sum(r['time'] for r in results) / len(results) if results else 0 + + return { + 'user_id': user_id, + 'results': results, + 'total_time': total_time, + 'success_count': success_count, + 'avg_time': avg_time + } + + +def generate_report(user_results, total_time, args): + """ + 生成性能测试报告 + + Args: + user_results: 所有用户的结果列表 + total_time: 总执行时间 + args: 命令行参数 + + Returns: + str: 报告内容 + """ + # 统计数据 + total_images = sum(len(ur['results']) for ur in user_results) + total_success = sum(ur['success_count'] for ur in user_results) + total_failed = total_images - total_success + success_rate = (total_success / total_images * 100) if total_images > 0 else 0 + + # 响应时间统计 + all_times = [r['time'] for ur in user_results for r in ur['results']] + avg_response_time = sum(all_times) / len(all_times) if all_times else 0 + min_response_time = min(all_times) if all_times else 0 + max_response_time = max(all_times) if all_times else 0 + + # 吞吐量 + qps = total_images / total_time if total_time > 0 else 0 + + # 生成报告 + report = [] + report.append("=" * 80) + report.append("YOLO 数字识别并发性能测试报告") + report.append("=" * 80) + report.append(f"\n测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + report.append(f"\n配置参数:") + report.append(f" - 并发用户数: {args.users}") + report.append(f" - 每用户图片数: {args.images_per_user}") + report.append(f" - 模型: {args.model}") + report.append(f" - 置信度阈值: {args.conf}") + report.append(f" - 图片大小: {args.imgsz}") + report.append(f" - 图片源: {args.source}") + + report.append(f"\n总体性能:") + report.append(f" - 总执行时间: {total_time:.2f} 秒") + report.append(f" - 总识别图片数: {total_images}") + report.append(f" - 成功: {total_success} ({success_rate:.1f}%)") + report.append(f" - 失败: {total_failed}") + report.append(f" - 吞吐量 (QPS): {qps:.2f} 图片/秒") + + report.append(f"\n响应时间统计:") + report.append(f" - 平均响应时间: {avg_response_time:.3f} 秒") + report.append(f" - 最小响应时间: {min_response_time:.3f} 秒") + report.append(f" - 最大响应时间: {max_response_time:.3f} 秒") + + # 百分位数 + sorted_times = sorted(all_times) + p50_idx = int(len(sorted_times) * 0.50) + p90_idx = int(len(sorted_times) * 0.90) + p95_idx = int(len(sorted_times) * 0.95) + p99_idx = int(len(sorted_times) * 0.99) + + report.append(f" - P50 响应时间: {sorted_times[p50_idx]:.3f} 秒") + report.append(f" - P90 响应时间: {sorted_times[p90_idx]:.3f} 秒") + report.append(f" - P95 响应时间: {sorted_times[p95_idx]:.3f} 秒") + report.append(f" - P99 响应时间: {sorted_times[p99_idx]:.3f} 秒") + + # 每个用户的统计 + report.append(f"\n各用户性能:") + report.append(f" {'用户ID':>8} | {'图片数':>8} | {'成功':>8} | {'总耗时':>10} | {'平均耗时':>10}") + report.append(f" {'-'*8}-+-{'-'*8}-+-{'-'*8}-+-{'-'*10}-+-{'-'*10}") + + for ur in sorted(user_results, key=lambda x: x['user_id']): + report.append(f" {ur['user_id']:8d} | {len(ur['results']):8d} | " + f"{ur['success_count']:8d} | {ur['total_time']:9.2f}s | " + f"{ur['avg_time']:9.3f}s") + + # 失败任务详情 + failed_tasks = [] + for ur in user_results: + for r in ur['results']: + if not r['success']: + failed_tasks.append((ur['user_id'], r)) + + if failed_tasks: + report.append(f"\n失败任务详情 (共 {len(failed_tasks)} 个):") + for user_id, result in failed_tasks[:20]: # 最多显示20个 + report.append(f" 用户{user_id} | {result['filename']} | 错误: {result['error']}") + if len(failed_tasks) > 20: + report.append(f" ... 还有 {len(failed_tasks) - 20} 个失败任务") + + report.append("\n" + "=" * 80) + + return '\n'.join(report) + + +def main(): + """主函数""" + args = parse_args() + + # 检查模型文件 + if not Path(args.model).exists(): + print(f"❌ 模型文件不存在: {args.model}") + return + + # 加载图片路径 + print(f"\n📂 加载图片路径...") + try: + image_paths = load_image_paths(args.source) + print(f"✓ 找到 {len(image_paths)} 张图片") + except Exception as e: + print(f"❌ 加载图片失败: {e}") + return + + # 显示测试配置 + total_images = args.users * args.images_per_user + print(f"\n⚙️ 测试配置:") + print(f" - 并发用户数: {args.users}") + print(f" - 每用户图片数: {args.images_per_user}") + print(f" - 总图片数: {total_images}") + print(f" - 模型: {Path(args.model).name}") + print(f" - 置信度: {args.conf}") + print(f" - 图片大小: {args.imgsz}") + + # 开始测试 + print(f"\n🚀 开始并发测试...\n") + start_time = time.time() + + # 使用线程池并发执行 + user_results = [] + with ThreadPoolExecutor(max_workers=args.users) as executor: + # 提交所有用户任务 + futures = [] + for user_id in range(args.users): + future = executor.submit( + user_task, + user_id, + args.model, + image_paths, + args.images_per_user, + args.conf, + args.imgsz, + args.verbose + ) + futures.append(future) + + # 收集结果并显示进度 + completed = 0 + for future in as_completed(futures): + result = future.result() + user_results.append(result) + completed += 1 + + if not args.verbose: + print(f" 进度: {completed}/{args.users} 用户完成 " + f"({completed/args.users*100:.0f}%)", end='\r') + + total_time = time.time() - start_time + + if not args.verbose: + print() # 换行 + + # 生成报告 + print(f"\n📊 生成性能报告...") + report = generate_report(user_results, total_time, args) + + # 显示报告 + print(report) + + # 保存报告 + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, 'w', encoding='utf-8') as f: + f.write(report) + + print(f"\n✓ 报告已保存到: {args.output}") + + # 保存JSON格式的详细数据 + json_output = output_path.with_suffix('.json') + json_data = { + 'config': vars(args), + 'summary': { + 'total_time': total_time, + 'total_images': total_images, + 'qps': total_images / total_time if total_time > 0 else 0, + }, + 'users': user_results + } + + with open(json_output, 'w', encoding='utf-8') as f: + json.dump(json_data, f, indent=2, ensure_ascii=False) + + print(f"✓ 详细数据已保存到: {json_output}") + + +if __name__ == '__main__': + main() diff --git a/scripts/run_benchmark_quick.sh b/scripts/run_benchmark_quick.sh new file mode 100755 index 0000000..4ec9efd --- /dev/null +++ b/scripts/run_benchmark_quick.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# 快速并发性能测试(较小规模,适合快速验证) + +echo "================================================================================" +echo "YOLO 数字识别 - 快速并发测试" +echo "================================================================================" +echo "" + +RESULT_DIR="results/benchmark_quick" +mkdir -p "$RESULT_DIR" + +IMAGES_PER_USER=10 +USER_COUNTS=(1 3 5 8) + +echo "📋 测试配置:" +echo " - 每用户图片数: $IMAGES_PER_USER" +echo " - 测试并发级别: ${USER_COUNTS[@]}" +echo "" + +for users in "${USER_COUNTS[@]}"; do + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🚀 测试: ${users} 个并发用户 × ${IMAGES_PER_USER} 张图片" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + python scripts/benchmark_concurrent.py \ + --users "$users" \ + --images-per-user "$IMAGES_PER_USER" \ + --output "$RESULT_DIR/benchmark_${users}users.txt" + + echo "" +done + +# 生成摘要 +SUMMARY_FILE="$RESULT_DIR/summary.txt" + +{ + echo "================================================================================" + echo "YOLO 数字识别 - 快速并发测试摘要" + echo "================================================================================" + echo "" + echo "测试时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "" + echo "性能对比:" + echo "" + printf "%-12s | %-10s | %-12s | %-12s | %-15s\n" \ + "并发用户" "总图片" "总耗时(s)" "QPS" "平均响应(s)" + echo "-------------+------------+--------------+--------------+----------------" +} > "$SUMMARY_FILE" + +for users in "${USER_COUNTS[@]}"; do + json_file="$RESULT_DIR/benchmark_${users}users.json" + + if [ -f "$json_file" ]; then + total_images=$(python3 -c "import json; print(json.load(open('$json_file'))['summary']['total_images'])") + total_time=$(python3 -c "import json; print(f\"{json.load(open('$json_file'))['summary']['total_time']:.2f}\")") + qps=$(python3 -c "import json; print(f\"{json.load(open('$json_file'))['summary']['qps']:.2f}\")") + avg_time=$(python3 -c "import json; d=json.load(open('$json_file')); times=[r['time'] for u in d['users'] for r in u['results']]; print(f\"{sum(times)/len(times):.3f}\")") + + printf "%-12d | %-10d | %-12s | %-12s | %-15s\n" \ + "$users" "$total_images" "$total_time" "$qps" "$avg_time" >> "$SUMMARY_FILE" + fi +done + +echo "" >> "$SUMMARY_FILE" +echo "================================================================================" >> "$SUMMARY_FILE" + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +cat "$SUMMARY_FILE" +echo "" +echo "✅ 快速测试完成!详细报告: $RESULT_DIR/" +echo "================================================================================" diff --git a/scripts/run_benchmark_suite.sh b/scripts/run_benchmark_suite.sh new file mode 100755 index 0000000..deeed06 --- /dev/null +++ b/scripts/run_benchmark_suite.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# +# 并发性能测试套件 - 运行多组测试对比不同并发级别的性能 +# +# 功能: +# - 测试不同并发用户数下的系统性能 +# - 生成对比报告 +# +# 使用: +# ./scripts/run_benchmark_suite.sh +# +# 作者: Gavin Chan +# 日期: 2025-01-30 + +echo "================================================================================" +echo "YOLO 数字识别 - 并发性能测试套件" +echo "================================================================================" +echo "" + +# 创建结果目录 +RESULT_DIR="results/benchmark_suite" +mkdir -p "$RESULT_DIR" + +# 测试配置 +IMAGES_PER_USER=20 +USER_COUNTS=(1 3 5 10 15 20) + +echo "📋 测试配置:" +echo " - 每用户图片数: $IMAGES_PER_USER" +echo " - 测试并发级别: ${USER_COUNTS[@]}" +echo "" + +# 运行测试 +for users in "${USER_COUNTS[@]}"; do + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🚀 测试场景: ${users} 个并发用户" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + output_file="$RESULT_DIR/benchmark_${users}users.txt" + + python scripts/benchmark_concurrent.py \ + --users "$users" \ + --images-per-user "$IMAGES_PER_USER" \ + --output "$output_file" + + echo "" + sleep 1 # 短暂休息,避免系统过载 +done + +# 生成对比报告 +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "📊 生成对比报告..." +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +SUMMARY_FILE="$RESULT_DIR/summary.txt" + +cat > "$SUMMARY_FILE" << 'EOF' +================================================================================ +YOLO 数字识别 - 并发性能对比报告 +================================================================================ + +EOF + +echo "测试时间: $(date '+%Y-%m-%d %H:%M:%S')" >> "$SUMMARY_FILE" +echo "" >> "$SUMMARY_FILE" +echo "各并发级别性能对比:" >> "$SUMMARY_FILE" +echo "" >> "$SUMMARY_FILE" + +printf "%-12s | %-10s | %-12s | %-12s | %-15s\n" \ + "并发用户" "总图片" "总耗时(s)" "QPS" "平均响应(s)" >> "$SUMMARY_FILE" +echo "-------------+------------+--------------+--------------+----------------" >> "$SUMMARY_FILE" + +# 从JSON文件中提取数据 +for users in "${USER_COUNTS[@]}"; do + json_file="$RESULT_DIR/benchmark_${users}users.json" + + if [ -f "$json_file" ]; then + total_images=$(python3 -c "import json; print(json.load(open('$json_file'))['summary']['total_images'])") + total_time=$(python3 -c "import json; print(f\"{json.load(open('$json_file'))['summary']['total_time']:.2f}\")") + qps=$(python3 -c "import json; print(f\"{json.load(open('$json_file'))['summary']['qps']:.2f}\")") + + # 计算平均响应时间 + avg_time=$(python3 -c "import json; d=json.load(open('$json_file')); times=[r['time'] for u in d['users'] for r in u['results']]; print(f\"{sum(times)/len(times):.3f}\")") + + printf "%-12d | %-10d | %-12s | %-12s | %-15s\n" \ + "$users" "$total_images" "$total_time" "$qps" "$avg_time" >> "$SUMMARY_FILE" + fi +done + +echo "" >> "$SUMMARY_FILE" +echo "================================================================================" >> "$SUMMARY_FILE" + +# 显示对比报告 +cat "$SUMMARY_FILE" + +echo "" +echo "✅ 测试完成!" +echo "" +echo "📁 详细报告位置:" +echo " - 对比摘要: $SUMMARY_FILE" +echo " - 各测试详情: $RESULT_DIR/benchmark_*users.txt" +echo " - JSON数据: $RESULT_DIR/benchmark_*users.json" +echo "" +echo "================================================================================"