更快的处理器/时钟可以执行更多的代码吗?

电器工程 Arduino avr 中央处理器 时钟速度
2022-01-21 22:39:26

我正在编写一个程序以在运行频率为 16Mhz 的 ATmega 328 上运行(如果您了解它们,它是一个 Arduino Duemilanove,它是一个 AVR 芯片)。

我有一个每 100 微秒运行一次的中断进程。我想说,要计算出在一个 100 微秒的循环中可以执行多少“代码”是不可能的(我正在用 C 语言编写,它可能被转换为汇编然后转换为二进制图像?)。

这也取决于代码的复杂性(例如,一个巨大的单行代码可能比几行短代码运行得慢)。

我的理解是否正确,因为我的时钟频率或 16Mhz 的处理器每秒执行 1600 万个周期(这意味着每微秒 16 个周期 16,000,000/1,000/1,000);因此,如果我想在 100 微秒的循环中做更多的事情,购买像 72Mhz 版本这样更快的模型会给我每微秒 72 个循环(72,000,000/1,000/1,000)?

目前它的运行速度有点太慢了,即它需要比 100 微秒多一点的时间来完成循环(到底多长时间很难说,但它逐渐落后了),我希望它多做一点,是这是获得更快芯片的理智方法还是我疯了?

3个回答

通常,设备每秒可以执行的汇编指令数量取决于指令组合以及每种指令类型执行所需的周期数(CPI)。理论上,您可以通过查看反汇编的 asm 文件并查看您关注的功能,计算其中所有不同类型的指令,并从数据表中查找目标处理器的循环计数,从而对您的代码进行循环计数。

在更复杂的处理器中,确定每秒有效指令数的问题由于它们是流水线的并且具有高速缓存等事实而加剧。对于像 ATMega328 这样的简单设备,情况并非如此,它是飞行处理器中的单条指令。

至于实际问题,对于像 AVR 这样的简单设备,我的回答或多或少是“是”。将时钟速度加倍应该使任何给定函数的执行时间减半。然而,对于 AVR,它们的运行速度不会超过 20MHz,因此您只能将 Arduino “超频”另外 4MHz。

此建议不适用于具有更高级功能的处理器。将英特尔处理器的时钟速度加倍实际上不会使其每秒执行的指令数加倍(因为分支错误预测、缓存未命中等)。

@vicatcu 的回答非常全面。另外需要注意的一点是,在访问 I/O(包括程序和数据存储器)时,CPU 可能会进入等待状态(停滞的 CPU 周期)。

例如,我们使用的是 TI F28335 DSP;RAM 的某些区域是程序和数据存储器的 0 等待状态,因此当您在 RAM 中执行代码时,它以每条指令 1 个周期运行(除了那些需要超过 1 个周期的指令)。但是,当您从 FLASH 存储器(内置 EEPROM,或多或少)执行代码时,它无法以全速 150MHz 运行,而且速度要慢几倍。


关于高速中断代码,你必须学习很多东西。

首先,要非常熟悉你的编译器。如果编译器做得很好,对于大多数事情来说,它不应该比手工编码的汇编慢很多。(其中“慢得多”:2 倍对我来说是可以的;10 倍是不可接受的)你需要学习如何(以及何时)使用编译器优化标志,并且每隔一段时间你应该看看在编译器的输出中查看它是如何工作的。

你可以让编译器做一些其他的事情来加速代码:

  • 使用内联函数(不记得 C 是否支持这一点,或者它是否只是 C++ 主义),用于小函数和只执行一次或两次的函数。缺点是内联函数很难调试,尤其是在打开编译器优化的情况下。但是它们为您节省了不必要的调用/返回序列,特别是如果“函数”抽象是用于概念设计目的而不是代码实现。

  • 查看你的编译器手册,看看它是否有内在函数——这些是依赖于编译器的内置函数,它们直接映射到处理器的汇编指令;一些处理器有汇编指令,可以做一些有用的事情,比如最小/最大/位反转,你可以节省时间。

  • 如果您正在进行数值计算,请确保您没有不必要地调用数学库函数。我们有一种情况,代码类似于y = (y+1) % 4一个周期为 4 的计数器,期望编译器将模 4 实现为按位与。相反,它调用了数学库。所以我们替换y = (y+1) & 3为做我们想做的事。

  • 熟悉bit-twiddling hacks 页面我保证你至少会经常使用其中的一种。

您还应该使用 CPU 的定时器外设来测量代码执行时间——它们中的大多数都有一个定时器/计数器,可以设置为在 CPU 时钟频率下运行。在关键代码的开头和结尾处捕获计数器的副本,您可以看到需要多长时间。如果你不能这样做,另一种选择是在代码开始时降低输出引脚,并在结束时升高它,并在示波器上查看此输出以计时执行。每种方法都需要权衡取舍:内部定时器/计数器更灵活(您可以对几件事进行计时)但更难获取信息,而设置/清除输出引脚在示波器上立即可见并且您可以捕获统计信息,但是很难区分多个事件。

最后,经验带来了一项非常重要的技能——无论是通用的还是特定的处理器/编译器组合:知道何时以及何时不进行优化一般来说,答案是不要优化。Donald Knuth 的名言经常在 StackOverflow 上发布(通常只是最后一部分):

我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源

但是您处于知道必须进行某种优化的情况下,因此是时候硬着头皮进行优化(或获得更快的处理器,或两者兼而有之)。不要在汇编中编写整个 ISR。这几乎是一场灾难——如果你这样做了,在几个月甚至几周内你会忘记你所做的部分以及为什么这样做,并且代码可能非常脆弱且难以更改。但是,您的代码中可能有一些部分非常适合汇编。

部分代码非常适合汇编编码的迹象:

  • 功能完善,定义明确的小例程,不太可能改变
  • 可以使用特定汇编指令的功能(最小/最大/右移/等)
  • 被调用多次的函数(给你一个乘数:如果你在每次调用时节省 0.5usec,它被调用 10 次,这会节省你 5usec,这对你的情况很重要)

了解编译器的函数调用约定(例如,它将参数放在寄存器中的位置,以及它保存/恢复的寄存器),以便您可以编写 C 可调用汇编例程。

在我当前的项目中,我们有一个相当大的代码库,其中的关键代码必须在 10kHz 中断(100usec - 听起来很熟悉?)中运行,并且没有那么多用汇编编写的函数。那些是诸如 CRC 计算、软件队列、ADC 增益/偏移补偿之类的东西。

祝你好运!

另一件需要注意的事情 - 您可能可以执行一些优化以使您的代码更高效。

例如 - 我有一个从定时器中断中运行的例程。该例程必须在 52µS 内完成,并且在执行过程中必须逐步通过大量内存。

我通过将主计数器变量锁定到一个寄存器来管理速度的大幅提升(在我的 µC 和编译器上 - 与您的不同):

register unsigned int pointer asm("W9");

我不知道你的编译器的格式——RTFM,但是你可以做一些事情来让你的例程更快,而不必切换到汇编。

话虽如此,您在优化例程方面可能比编译器做得更好,因此切换到汇编可能会给您带来一些巨大的速度提升。