最近接手了一个智能体脂秤的项目,用户反馈称体重的时候响应太慢,要等好几秒才出结果。我花了两周时间,对体脂秤的固件和APP做了性能调优,把响应时间从5秒降到了1秒以内,降了80%。本文分享这次性能调优的过程和经验,包括性能瓶颈分析、固件优化、蓝牙通信优化、APP优化、以及嵌入式设备性能调优的通用方法,希望对做IoT和嵌入式开发的同学有参考价值。
一、项目背景
这个智能体脂秤,是公司的一款智能家居产品。它的功能很简单:用户站上去,测量体重和体脂率,然后通过蓝牙把数据传到手机APP上,APP显示结果并记录历史数据。
产品上线之后,用户反馈最多的问题就是:响应太慢。用户站上去之后,要等好几秒,APP上才显示体重和体脂率。有些用户甚至以为秤坏了,站了几秒没反应就下来了。
这个问题,影响了用户体验,也影响了产品的口碑。领导让我负责优化这个问题,目标是把响应时间降到2秒以内。
我先测了一下,从用户站上去,到APP显示结果,平均需要5秒左右,最慢的时候甚至要7-8秒。这个速度,确实太慢了。
于是,我开始了这次性能调优。
二、性能瓶颈分析
调优的第一步,不是上来就改代码,而是先分析性能瓶颈,找到慢的原因。
我把整个测量流程,拆分成了几个阶段,然后分别测量每个阶段的耗时:
- 传感器采样阶段:用户站上去之后,体重传感器和体脂传感器采集数据,需要多长时间?
- 数据计算阶段:传感器采集到原始数据之后,固件计算体重和体脂率,需要多长时间?
- 蓝牙连接阶段:秤和手机APP建立蓝牙连接,需要多长时间?
- 数据传输阶段:秤把计算结果通过蓝牙传到APP,需要多长时间?
- APP处理阶段:APP收到数据之后,处理和显示,需要多长时间?
我在固件和APP里都加了时间戳,把每个阶段的开始和结束时间都记录下来,然后测了几十次,取平均值。
结果发现:
| 阶段 | 平均耗时 | 占比 |
|---|---|---|
| 传感器采样 | 1.5秒 | 30% |
| 数据计算 | 0.2秒 | 4% |
| 蓝牙连接 | 2.5秒 | 50% |
| 数据传输 | 0.3秒 | 6% |
| APP处理 | 0.5秒 | 10% |
| 总计 | 5.0秒 | 100% |
一目了然,最大的瓶颈是蓝牙连接,占了50%的时间,平均2.5秒。其次是传感器采样,占了30%,平均1.5秒。这两个阶段加起来,占了80%的时间。
数据计算、数据传输、APP处理,这三个阶段加起来才1秒,不是主要瓶颈。
找到了瓶颈,接下来就是针对性地优化。
三、蓝牙连接优化
蓝牙连接是最大的瓶颈,平均2.5秒。我先分析了一下,为什么蓝牙连接这么慢。
问题分析:
原来的蓝牙连接流程是这样的:
- 用户站上去,秤检测到重量,开始广播蓝牙信号
- APP扫描到秤的广播信号,发起连接
- 连接建立之后,进行服务发现(Service Discovery),查找秤提供的服务和特征值
- 然后进行特征值的读取和通知订阅
- 最后传输数据
这个流程,每一步都需要时间。尤其是服务发现,要遍历所有的服务和特征值,比较慢。
而且,原来的代码里,APP是在用户站上去之后,才开始扫描和连接的。也就是说,用户站上去之后,才开始整个蓝牙连接流程,这2.5秒的连接时间,用户都在等。
优化方案:
我做了几个优化:
优化1:预连接(Pre-connect)
最大的优化,是把蓝牙连接提前。不要等用户站上去之后才开始连接,而是在APP打开的时候,就尝试和秤建立连接。
具体做法:
- APP打开的时候,自动扫描并连接附近的秤
- 连接成功之后,保持连接(或者保持一个低功耗的连接状态)
- 用户站上去的时候,秤直接通过已经建立的连接传输数据,不需要再重新连接
这样,蓝牙连接的时间,就从用户等待的时间里,移到了APP打开的时候。用户站上去的时候,连接已经建立好了,直接传数据就行。
当然,这个优化有一些细节要处理:
- 连接保持会增加功耗,需要在功耗和响应速度之间权衡。可以用低功耗的连接参数(更长的连接间隔),保持连接但功耗不高
- 如果APP打开的时候,秤不在附近(比如用户不在家),连接会失败,这时候要处理连接失败的情况,用户站上去的时候再重新连接
- 如果有多个秤,要处理连接哪个的问题,可以让用户选择默认的秤
即使有这些细节问题,预连接还是带来了巨大的提升。大部分情况下,用户打开APP之后,秤已经连接好了,站上去直接出结果。
优化2:优化服务发现
服务发现(Service Discovery)是蓝牙连接中比较慢的一步。原来的代码里,连接建立之后,会遍历所有的服务和特征值,这比较慢。
优化方法:
- 我们的秤,只提供一个自定义服务,服务里只有几个特征值(体重、体脂率、电量等)
- 不需要遍历所有服务,直接用我们知道的服务UUID和特征值UUID,直接访问
- 跳过通用的服务发现流程,直接用已知的UUID操作
这样,服务发现的时间,从原来的1秒左右,降到了几乎可以忽略。
当然,这种优化只适用于我们自己的设备,因为我们知道服务和特征值的UUID。如果是通用的蓝牙工具,还是需要服务发现。
优化3:优化连接参数
蓝牙连接的参数(连接间隔、从设备延迟、超时时间),会影响连接的响应速度和功耗。
原来的连接参数,是比较保守的,连接间隔比较长(100ms),响应比较慢,但功耗低。
我调整了连接参数:
- 连接间隔从100ms降到了30ms,响应更快
- 从设备延迟设为0,确保数据能及时传输
- 超时时间保持合理的值,避免连接断了之后长时间不重连
当然,连接间隔缩短会增加功耗。我做了一个动态调整:在传输数据的时候,用短的连接间隔(30ms),响应快;传输完成之后,切换到长的连接间隔(100ms),降低功耗。
这样,既保证了响应速度,又控制了功耗。
优化4:优化重连机制
如果蓝牙连接断了(比如用户离开了一会儿,或者蓝牙干扰),重连的速度也很重要。
原来的重连机制,是连接断了之后,先扫描,再连接,比较慢。
优化之后:
- 连接断了之后,直接用之前的设备地址发起连接,不需要重新扫描
- 用自动重连参数(autoConnect=true),让系统自动重连
- 重连的时候,直接用已知的服务和特征值,跳过服务发现
这样,重连的时间,从原来的3-4秒,降到了1秒以内。
蓝牙优化效果:
经过这几个优化,蓝牙连接的时间,从原来的2.5秒,降到了:
- 预连接成功的情况下:几乎为0(连接已经建立好了)
- 需要重新连接的情况下:0.5-1秒
平均下来,蓝牙连接的时间,从2.5秒降到了0.5秒左右,降了80%。
四、传感器采样优化
传感器采样是第二大瓶颈,平均1.5秒。我分析了一下,为什么采样需要这么长时间。
问题分析:
体脂秤的传感器采样,包括两部分:
- 体重采样:体重传感器(压力传感器)采集体重数据,需要一段时间稳定
- 体脂采样:体脂传感器(生物电阻抗)采集体脂数据,需要通入微弱电流,测量电阻抗
原来的采样流程:
- 用户站上去,检测到重量
- 等待体重传感器稳定(0.5秒)
- 采集体重数据,采样10次,取平均值(0.5秒)
- 启动体脂测量,通入电流,等待稳定(0.3秒)
- 采集体脂数据,采样5次,取平均值(0.2秒)
- 总共1.5秒
这个流程,是串行的,体重采样完成之后,才开始体脂采样。而且,采样次数比较多,等待稳定的时间也比较长。
优化方案:
我做了几个优化:
优化1:体重和体脂并行采样
最大的优化,是把体重采样和体脂采样并行化。
原来的流程是串行的:先采体重,再采体脂。但实际上,体重传感器和体脂传感器是独立的,可以同时工作。
优化之后:
- 用户站上去,检测到重量
- 同时启动体重采样和体脂采样
- 体重传感器稳定后,开始采集体重数据
- 体脂传感器稳定后,开始采集体脂数据
- 两个都完成之后,进行计算
这样,体重采样和体脂采样并行进行,总时间就是两者中较长的那个,而不是两者之和。
体重采样需要1.0秒,体脂采样需要0.5秒,并行之后,总时间就是1.0秒,而不是1.5秒。
优化2:减少采样次数,用算法优化
原来的体重采样,采样10次取平均值;体脂采样,采样5次取平均值。采样次数多,是为了让数据更稳定、更准确。
但实际上,传感器的数据,在稳定之后,波动是很小的。不需要采样那么多次,用更少的采样次数,加上合适的滤波算法,就能达到同样的精度。
我做了优化:
- 体重采样:从10次降到5次,用滑动平均滤波
- 体脂采样:从5次降到3次,用中值滤波
采样次数减少了,采样时间自然就短了。而且,用了滤波算法之后,数据的稳定性和精度,和原来差不多,甚至更好。
当然,减少采样次数,要验证精度。我做了对比测试,用标准砝码测试体重精度,用标准体脂模体测试体脂精度,确认优化之后的精度,仍然在产品要求的范围内。
优化3:优化稳定检测算法
原来的稳定检测,是固定等待0.5秒,不管传感器有没有稳定,都等0.5秒。但实际上,有时候传感器0.2秒就稳定了,有时候0.8秒才稳定。固定等待0.5秒,快的时候浪费时间,慢的时候可能还没稳定。
我优化了稳定检测算法:
- 实时监测传感器数据的波动
- 当连续N个数据点的波动小于阈值时,认为已经稳定
- 稳定了就立刻开始采样,不需要固定等待
这样,稳定快的时候,立刻开始采样,节省时间;稳定慢的时候,多等一会儿,确保数据准确。
平均下来,稳定检测的时间,从固定的0.5秒,降到了平均0.3秒。
优化4:提前启动体脂测量
体脂测量,需要通入微弱电流,等待电路稳定。原来的流程,是体重采样完成之后,才启动体脂测量。
优化之后,在检测到用户站上去的时候,就立刻启动体脂测量的电路,让电路提前稳定。等体重采样完成(或者并行采样到一定阶段),体脂电路已经稳定了,可以直接采集数据。
这样,体脂测量的启动时间,就和体重采样的时间重叠了,不需要额外等待。
传感器优化效果:
经过这几个优化,传感器采样的时间,从原来的1.5秒,降到了0.7秒左右,降了50%以上。
而且,我做了精度验证,优化之后的体重和体脂精度,和原来差不多,都在产品要求的范围内。也就是说,我们在不牺牲精度的前提下,把采样时间降了一半。
五、其他优化
除了蓝牙连接和传感器采样这两个主要瓶颈,我还对其他几个阶段做了一些小优化。
数据计算优化:
原来的体脂率计算,用的是一个比较复杂的公式,涉及到很多浮点运算。在8位单片机上,浮点运算比较慢。
我做了优化:
- 把浮点运算改成定点运算,用整数代替浮点数,速度快很多
- 把一些常数提前计算好,存在Flash里,不需要每次都算
- 优化计算公式,去掉了一些不必要的计算步骤
数据计算的时间,从原来的0.2秒,降到了0.05秒。虽然这个阶段本来就不是瓶颈,但优化之后,总时间又少了一点。
数据传输优化:
原来的数据传输,是分多个包传输的,体重一个包,体脂率一个包,电量一个包,每个包都要等确认。
优化之后,把所有数据打包成一个包,一次传输完成。而且,用了蓝牙的Write Without Response(不需要确认的写入),传输更快。
数据传输的时间,从原来的0.3秒,降到了0.1秒。
APP处理优化:
APP收到数据之后,原来的处理流程比较复杂:
- 解析蓝牙数据
- 转换数据格式
- 写入本地数据库
- 上传到服务器
- 更新UI显示
其中,写入数据库和上传服务器,是同步执行的,会阻塞UI线程,导致显示慢。
优化之后:
- 解析和转换数据,在主线程做(很快)
- 写入数据库和上传服务器,放到后台线程异步执行
- UI显示,在数据解析完成之后立刻更新,不需要等数据库和服务器
这样,APP显示结果的时间,从原来的0.5秒,降到了0.1秒。用户感觉就是,数据一到,立刻就显示出来了。
六、优化效果
经过两周的调优,各个阶段的耗时,变成了这样:
| 阶段 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 传感器采样 | 1.5秒 | 0.7秒 | 53% |
| 数据计算 | 0.2秒 | 0.05秒 | 75% |
| 蓝牙连接 | 2.5秒 | 0.5秒 | 80% |
| 数据传输 | 0.3秒 | 0.1秒 | 67% |
| APP处理 | 0.5秒 | 0.1秒 | 80% |
| 总计 | 5.0秒 | 1.45秒 | 71% |
平均响应时间,从5秒降到了1.45秒,降了71%。在预连接成功的情况下(大部分情况),响应时间甚至能降到1秒以内,降了80%。
这个结果,达到了领导要求的"2秒以内"的目标,甚至超出了预期。
用户体验的提升也很明显。用户站上去,几乎立刻就能看到体重和体脂率,不需要再等好几秒了。用户反馈里,关于"响应慢"的投诉,几乎消失了。
而且,这些优化,都是在不牺牲精度和功耗的前提下完成的。体重和体脂的精度,和原来一样;秤的电池续航,也没有明显下降。
七、性能调优的通用方法
这次体脂秤的性能调优,让我总结了一些嵌入式设备和IoT产品性能调优的通用方法。
1. 先测量,再优化
这是最重要的一条。不要上来就改代码,先测量,找到性能瓶颈,再有针对性地优化。
测量的方法:
- 把整个流程拆分成多个阶段
- 在每个阶段的开始和结束,加时间戳
- 多次测量,取平均值
- 找到耗时最长的阶段,就是主要瓶颈
找到了瓶颈,再优化,效率最高。如果盲目优化,可能花了很多时间,优化了一个不是瓶颈的地方,整体效果不大。
2. 并行化,不要串行
很多嵌入式设备的性能问题,是因为流程是串行的。能并行的地方,尽量并行。
比如,这次体脂秤的优化,体重采样和体脂采样并行,就节省了很多时间。
在嵌入式开发中,常见的并行化方法:
- 用DMA(直接内存访问),让数据传输和CPU计算并行
- 用中断,让外设工作和CPU处理并行
- 用多任务/多线程,让不同的任务并行
- 提前启动后续步骤,让后续步骤的准备工作和当前步骤并行
当然,并行化要注意同步和竞态条件,确保数据的正确性。
3. 减少不必要的工作
很多性能问题,是因为做了不必要的工作。把不必要的工作去掉,性能自然就提升了。
比如,这次优化中:
- 减少了采样次数,因为不需要那么多次
- 跳过了通用的服务发现,因为我们知道服务和特征值的UUID
- 去掉了计算公式中不必要的步骤
减少不必要的工作,是最简单、最有效的优化方法。在优化之前,先问问自己:这一步是必须的吗?能不能去掉?能不能简化?
4. 用空间换时间,用时间换空间
性能优化中,经常需要在时间和空间之间权衡。
用空间换时间的例子:
- 把计算结果缓存起来,下次直接用,不需要重新计算
- 把常数提前计算好,存在Flash里,不需要每次都算
- 用查表法代替计算,用空间换时间
用时间换空间的例子:
- 数据压缩,用计算时间换存储空间
- 延迟计算,需要的时候才算,用时间换内存
在嵌入式设备中,内存和Flash空间通常比较紧张,所以要特别注意权衡。但如果空间够用,用空间换时间,通常是很有效的优化方法。
5. 优化算法,而不只是优化代码
很多性能问题,根本原因是算法不好。这时候,再怎么优化代码,效果也有限。要从算法层面优化。
比如,这次体脂秤的优化中,稳定检测算法的优化,就是算法层面的优化。从固定等待,变成自适应检测,既提升了速度,又保证了精度。
在嵌入式开发中,常见的算法优化:
- 用更高效的滤波算法(如卡尔曼滤波),减少采样次数
- 用更高效的数值计算方法(如快速傅里叶变换),减少计算量
- 用更高效的数据结构(如哈希表),减少查找时间
- 用近似算法,在可接受的精度范围内,大幅提升速度
算法优化,通常能带来数量级的性能提升,比单纯优化代码有效得多。
6. 关注用户感知的性能,而不只是客观指标
性能优化,最终目的是提升用户体验。所以,要关注用户感知的性能,而不只是客观的技术指标。
比如,这次优化中,预连接把蓝牙连接时间,从用户等待的时间里,移到了APP打开的时候。客观上,蓝牙连接的时间没有减少(还是需要那么多时间),但用户感知的响应时间,大大减少了。因为用户不需要等了。
类似的方法:
- 提前加载,让用户感觉更快
- 先显示主要内容,再加载次要内容
- 加loading动画,让等待不那么难熬
- 乐观更新,先显示结果,后台再同步
用户感知的性能,有时候比客观的性能更重要。优化的时候,要多站在用户的角度想:用户感觉快了吗?用户体验好了吗?
八、调优过程中的坑
在这次调优过程中,我也踩了一些坑,分享给大家。
坑1:优化了精度,结果精度下降了
最开始,我减少了采样次数,结果发现体重和体脂的精度下降了,尤其是在用户站得不稳的时候,数据波动比较大。
后来,我加了滤波算法(滑动平均和中值滤波),才把精度拉回来。
教训:性能优化,不能以牺牲功能和精度为代价。优化之后,一定要做充分的测试,验证功能和精度没有下降。
坑2:预连接导致功耗增加
预连接确实提升了响应速度,但也导致了功耗增加。APP一直保持蓝牙连接,手机的蓝牙一直工作,耗电增加了。
后来,我做了动态连接参数管理,传输数据的时候用短连接间隔,空闲的时候用长连接间隔,才把功耗降下来。
教训:性能优化,要权衡各个方面。响应速度、功耗、精度、稳定性,这些都要兼顾,不能顾此失彼。
坑3:并行采样导致数据冲突
体重采样和体脂采样并行的时候,最开始出现了数据冲突的问题。两个传感器同时工作,互相干扰,导致数据不准确。
后来,我仔细看了硬件设计,发现体重传感器和体脂传感器用的是不同的ADC(模数转换器),理论上不会互相干扰。问题出在软件上,两个采样任务共用了一个缓冲区,导致数据覆盖。
我给两个采样任务分配了独立的缓冲区,问题就解决了。
教训:并行化的时候,要特别注意数据同步和竞态条件。确保并行的任务之间,不会互相干扰,数据不会冲突。
坑4:Write Without Response导致数据丢失
优化蓝牙数据传输的时候,我用了Write Without Response(不需要确认的写入),速度确实快了。但测试发现,偶尔会出现数据丢失的情况,APP收不到数据。
原因是,Write Without Response不需要确认,如果蓝牙连接不稳定,数据可能会丢。
后来,我做了一个折中:关键数据(体重和体脂率)用需要确认的写入,确保不丢;非关键数据(比如电量)用不需要确认的写入,提升速度。
教训:性能优化,不能牺牲可靠性。速度和可靠性之间,要找到平衡。关键数据,一定要确保可靠;非关键数据,可以追求速度。
九、写在最后
这次体脂秤的性能调优,是一次很有意义的经历。它让我从一个纯软件工程师,深入到了嵌入式和IoT领域,了解了硬件、固件、蓝牙、APP的整个链路,也学到了很多性能调优的方法和技巧。
性能调优,是一件很有挑战性,也很有成就感的事情。当你把一个5秒的响应,优化到1秒以内,当你看到用户体验的提升,那种成就感,是写普通代码体会不到的。
但性能调优,也是一件需要耐心和细心的事情。不能盲目优化,要先测量,找到瓶颈;不能急于求成,要一步一步来,每一步都验证;不能顾此失彼,要在速度、精度、功耗、可靠性之间找到平衡。
这次调优,我最大的感悟是:性能优化的本质,是理解系统。只有深入理解了整个系统的工作原理,理解了每个环节的耗时和瓶颈,才能找到最有效的优化方法。如果只是表面地改改代码,效果是有限的。
嵌入式设备和IoT产品的性能调优,和纯软件的性能调优,有很多相似之处,也有很多不同。相似的是,都要先测量、找瓶颈、再优化;不同的是,嵌入式设备受限于硬件资源(CPU、内存、功耗),需要更多地在资源和性能之间权衡。
最后,用一句话结束本文:"性能调优没有银弹,只有深入理解系统,找到瓶颈,针对性地优化,才能取得最好的效果。"希望这次体脂秤性能调优的经验,能对做嵌入式和IoT开发的同学,有一些参考和启发。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录