65.9K
CodeProject 正在变化。 阅读更多。
Home

小部件上的大事:探索“少即是多”

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2022年3月25日

CPOL

10分钟阅读

viewsIcon

7936

在 PC 平台上,能完成任务就是王道。而在物联网领域,诀窍在于“能否完成任务”。

ESP32 WROVER IoT device

引言

当你想在物联网设备上做一些超出基本功能的事情时,你很快就会遇到瓶颈。通常,如果你试图用传统的方式来解决编程问题,那个瓶颈会是内存 (RAM)——也就是说,你没有足够的内存。内存可能是最重要的因素,因为物联网设备没有虚拟内存。你只有多少内存,就用多少,如果不够,你的代码根本无法运行。

接下来是 I/O 的使用,如果你需要它来更新显示屏,你的代码就会依赖于显示屏总线的速度。在物联网设备上,这意味着执行时间会因为延迟而显著增加。

最后,你还需要处理 CPU 的使用率,虽然受限,但它可能为你的代码性能提供更多的“余地”。毕竟,运行缓慢的代码至少还在运行,而且 CPU 的速度仍然比 I/O 快得多,所以这一点有所弥补。

此外还有一个程序空间的问题,这也是一个主要因素——如果空间不够,你的代码就无法在该设备上运行。不过,本文不会深入探讨这个问题。一般来说,避免使用模板,并为重复代码使用例程,应该就没问题了。

规划您的项目

了解你的环境

内存:你有什么?

在你开始设计之前,你应该能告诉我,在你的入口点被触发时,有多少可用内存——不是总内存,而是可用内存。我之所以问你,只是因为我比较挑剔,但更重要的是,你需要这个数字。一旦你有了这个数字,就把它记在心里,因为它将帮助你做出设计决策。没有它,你就等于蒙着眼睛写代码。至少,这样做会让我们其他人看得津津有味。

更难的是知道你有多少栈空间。我没有试图提前找出答案,而是简单地采用了一种试错的方法,这因平台而异,就像总内存一样,因为专业人士就是这么做的,或者至少我被告知是这样。(我希望没有专业人士在阅读这篇文章。)

I/O:存储、外设和驱动程序,我的天!

首先也是最重要的,你是否有可用的存储空间,它有什么特性?它是非易失性的吗?它是可移除的还是可靠可用的?如果是后者,它有多少可用工作空间?它的速度有多快?它有粉色的吗?所有这些都很重要,因为有可能利用存储来分担那些原本会占用内存的工作。如果满足某些条件——例如存储是可靠可用的——那么在规划软件时,考虑这一点至关重要,或者至少是时髦的。

其次,你的 I/O 或外设是否需要内存才能运行?例如,一些显示驱动程序可能需要帧缓冲来存储显示数据。如果是这样,就从你可用的内存中减去它。当你使用对性能至关重要的代码路径上的外设时,你还需要考虑总线延迟。如果不考虑,你的代码会慢到让你想下车去推。

CPU:用尽它值得吗?

你的平台是否有多个核心,你计划使用它们吗?如果是这样,你需要考虑多线程编程的开销,包括运行时性能以及开发工作量和复杂性。如果没有一个像样的调试器,如果你打算走这条路,这也是让你发疯的好方法。记住要为每个额外的线程考虑栈空间,并从可用内存中减去。好玩吗?

与其处理所有这些麻烦,不如考虑使用单个核心和线程进行编码,从而节省功耗、复杂性和额外的内存使用。只要你为此规划好代码,你总是可以使用“协作式多线程”。我们将在后面进一步介绍。

效率与功耗:慢而稳才能赢

最有效的代码行是永远不会执行的代码行。它是最快的代码,也许更重要的是,它也是使用电量最少的那段代码。你的设备可能靠电池供电。你以为已经够烦恼了,现在你还得考虑你多久会让你的最终用户因为需要给你的小工具充电而感到烦躁。

至少有一半的时间,速度不是你的首要任务——功耗才是。幸运的是,正如我上面提到的,这两个目标通常是相辅相成的。然而,在某些领域,你必须做出权衡速度以换取电池使用寿命的设计决策,例如避免使用第二个核心。

但同时,请记住在不使用蓝牙收音机时将其关闭。

陷阱

数组:它们不是你的朋友

说真的,数组,或者任何内存支持的缓冲区都会占用宝贵的内存。囤积内存是一种健康的思维方式,只要这种习惯不蔓延到现实生活中。你应该付出相当大的努力来避免使用较大的数组。

你可以使用流式处理和协程等技术来进行增量处理,而不是一次性加载整个缓冲区然后进行处理。随着我们继续深入,我们将介绍这些和其他技术。总的来说,避免设计接受数组(或缓冲区指针)的接口,否则你会后悔的。

堆:避免将其粉碎成碎片

你认为自己很厉害,迄今为止还有 150kB 的可用内存?我敢打赌你是这么想的,而且你会一直这么想,直到你尝试执行 void* foo = malloc(150*1024); 并且它失败了。

为什么会有那么多内存还会失败?

答案是,因为在生活中,没有什么值得拥有的东西是容易得到的。更具体地说,是因为你没有 150kB 的连续可用空间。你可能只有 100kB 的一块和 50kB 的一块可用,或者两块 75kB 的可用,甚至两块 100kB 的可用,但你没有一块 150kB 的。这是由于堆碎片化,它就像硬盘碎片化一样,但你无法像进行脑移植一样对堆进行碎片整理。

通常在 PC 平台上,你可能注意不到碎片化,因为你的 32GB 机器有大量的堆空间。只有当你开始耗尽堆空间时,你才会注意到堆碎片化。而在物联网设备上,你几乎总是在“耗尽”状态,因为你在内存极低的条件下运行。堆碎片化现在永远是你的敌人。你必须防御性地编码来避免它。

你会想避免大量的、长时间存在的、小的 malloc 调用。如果你确实使用了堆,最好是短暂使用然后丢弃,除非是大型分配。尽量相应地进行编码。

STL:用还是不用 STL?

某些平台可能没有 STL,或者至少不完整或不符合标准。在这种情况下,你将不得不将就,并且可能需要一些基本的样板代码和自己的抽象基类来填补其缺失的部分。享受学习 STL 中一些基本功能是如何实现的乐趣吧,因为你将不得不自己重写一部分。

我个人更倾向于在物联网平台上避免使用 STL,原因有很多,其中最不重要的是,它很难让它负责任地使用堆。它容易导致堆碎片化。

资源管理:一切都不会永存(但愿如此)

资源(内存、CPU 时间、总线、信号量等)是宝贵的。

还记得那个小孩玩过的“烫手山芋”游戏吗?把你的资源当成烫手山芋——使用它们,然后尽快丢掉它们。你代码的任何一个部分在任何给定时间持有的资源越少,你的代码的其他部分在同一时间就能做的事情就越多。你使用的每个缓冲区和每个句柄都有一个隐喻性的时钟在滴答作响,所以使用它,然后就用完它。

降低 CPU 负载:快速缓存并离开

在物联网领域,缓存至关重要。一方面,你的程序不是直接从程序闪存空间运行的。它们必须在执行时一点一点地复制到内存中。

此外,你可以通过使用哈希表在关键代码路径上缓存复杂的计算来节省电力和/或提高性能。谨慎使用,这可以使你的效率提高几个数量级。

技术

流式传输一点点

几个月前,我野心勃勃,决定在 Arduino 上实现 TrueType 字体。问题是,一个普通的 TTF 文件大约是 250kB。在大多数物联网设备上,你无法将它加载到内存中。

除非要求将其加载到程序闪存空间,否则你可以从其他来源流式传输它,例如 SD 卡。我采用了一些公共领域的 TrueType 渲染代码,并将其全部转换为使用流而不是内存缓冲区。这花了一些功夫,但回报丰厚。缺点是它速度至少会稍慢一些,即使是从内存中使用,因为流式传输需要你将数据复制到临时内存中才能使用它。尽管如此,流式传输可以化不可能为可能。这是一个在你工具箱里应该有的好工具。

htcw_io 可以在你的项目中提供基本的流功能,而无需依赖 STL。

与协程协作

手动实现它们可能有点困难,因为它们通常需要将 `while` 循环转换为 `goto`,并构建状态机。其思想是,你的例程不应该一次性完成大量工作,而是每次被调用时处理一部分工作。显然,这需要保留一些状态,并在大多数情况下,如前所述,构建一个状态机。

这样做可以避免代码长时间阻塞。

同样,你会想避免在代码中使用 `delay()` 这样的调用,而更倾向于使用 Arduino 的 `millis()` 函数等来检查时间,并在 `if` 语句中查看时间是否已过。这样,你就可以在等待的同时继续做有用的工作,而不是干等着。

总的来说,思路是不要阻塞,或者说,在任何给定点上尽可能少地阻塞。

用缓存解决问题

在内部,你的小型 MCU 会缓存代码。为了最大化这种效果,你需要保持你的例程简短,并保持高引用局部性。换句话说,如果你希望代码都保留在缓存中,尽量避免跳跃,除非是短距离的跳跃,并且尽量不要创建大型例程或循环。显然,你需要做什么就做什么,但是将关键代码保存在内存中可以显著加快速度。在 ESP32 上,可以将 `IRAM_ATTR` 属性应用于函数,将其保留在内存中。要谨慎使用,因为这是拆东墙补西墙。这会占用宝贵的 SRAM。

除此之外,你可能想缓存复杂的计算,以节省电力和/或提高性能。使用哈希表是实现这一目标的首选技术。

htcw_data 包含一个简单的、不依赖 STL 的哈希表,适合缓存。

保持你的线程不被破坏

如果你必须使用抢占式多线程,请记住你没有 PC 的调试能力,因此调试竞态条件会从非常困难变成几乎不可能。

因此,你的首要任务应该是保持事情的简单、可预测和健壮。我借鉴了 .NET 的一个想法,使用“同步上下文”和线程池来实现我的多线程。

FreeRTOS Thread Pack 可以为你提供这些工具。它仅适用于 ESP32,尽管可以相对容易地将其改编到运行 FreeRTOS 的其他平台上。

结论

为物联网编码有时需要与为 PC 和服务器开发截然不同的优先级和编码技术。希望这些概念和技术能对你未来的项目有所帮助。

历史

  • 2022年3月25日 - 初始提交
© . All rights reserved.