评论
编译器应该已经将 switch 语句转换为跳转表,如果是这样,那么我相信每个案例中的代码已经是一个内联函数,因此优化可能已经发生。 您可以构建一个宏来填充跳转表(它只是一个函数指针数组)来手动执行此操作。理想情况下,我们会使用 G- 或 M- 编号作为索引,但由于 M- 代码高达 999,您将为静态数组多使用大约 1k 的程序空间。然后枚举它们将非常容易,只打印那些指向 0 以外的地方。 |
是的,我想到了。我的下一个想法是“关联数组”,但这在 C 中并不是真正的东西。有结构数据类型 (struct),但它们更加严格,并且名称不是可以匹配或输出的字符串 (他们甚至没有从编译器中提取出来)。您可以将一个代码名称数组作为字符串,另一个带有函数指针,但这有点难看,并且可能会大大增加处理时间和内存,所以我开始寻找其他形式的键/值对,这让我想到哈希/查找表。 我想面向对象的方法是为命令创建一个类,其中一个变量用于名称,一个用于函数指针指向代码的作用,然后创建一个该类型的数组,但我不知道有什么有效的方法在该数组中搜索其变量之一的值的方法。这是我会在 javascript 中不假思索地做的事情,但是…… |
这可能有效:http: //playground.arduino.cc/Code/HashMap#.Uyt1MfldV8E 假设它写得很好并且复杂度与 C++ 的 hash_map 相同。查找 hash_map 的最坏情况应该是 O(n),但通常是 O(1)。 不过,根据将其添加到 Marlin 占用多少程序空间,简单的跳转表+宏可能仍会领先。 |
使用内联函数,您不再有函数指针。内联意味着该函数永远不会作为单独的函数存在,而是会被复制到使用它的任何地方。所以函数指针没有地方可以指向。 具有 1000 个条目的数组在内存中也会有超过 1000 个字节。函数指针不仅仅是一个字节。理论上你也可以检查链表和二叉树,.. 但是在 100 字节很多的平台上,我看不到实现所有所需功能的机会。最好的机会是依靠内联预处理器。 满足您所有观点并且是干净的解决方案的解决方案在 AVR 的有限资源上是不可行的。因此,要么等待 ARM 处理器(Marlin 2),要么等待像我这样的解决方案:https ://github.com/JustAnother1/Pacemaker |
Switch 会编译成一个 jmp,因此将该代码转换为内联几乎没有什么好处(每个代码一个时钟周期?),而且不值得恕我直言。此外,由于您会创建很多函数,因此您会有效地做同样的事情,但在函数中而不是在“案例”中。没有清晰度。 返回 M 代码列表会很好,但也许可以使用预编译器以某种方式完成…… 马尔钦 |
@nothinman我没想过将已实现代码列表存储为布尔数组,然后在请求列表时使用循环将其扩展为代码列表。聪明的。如果我们想将它扩展为输出代码可以采用哪些参数,它就不会工作,但这对于基本情况来说已经足够了。我想这将允许我们只为实现的代码创建一个包含该数据的单独数组,并使用第一个数组中 1 的总和作为第二个数组的索引…… 任何人,我想说接下来要做的是弄清楚如何在预处理器中生成案例…… |
你好@nothinman我想我不同意你的说法“因为你会创建很多功能,你会有效地做同样的事情,但在功能而不是在’案例’中。没有获得清晰度。” 这样做不会实现您的 1、2 和 3 个目标,但会创造很多清晰度,并开始将此固件移动到一段代码的漫长过程,新手容易理解,更具可读性,模块化,能够聚集关联功能在一起,因此减少了全局变量等的使用。所有这些都已在以前的问题中进行了报告和评论。 我想我们都可以看到 100 条 switch 语句的优势,它不再是 2100 行代码,而是 300 行代码。我会做这项工作只是为了开始使代码更具可读性和自我记录 |
@ljones-adaptive说得好,这几乎就是我在第一条中想说的。 |
我不同意。将 M- G- 值与其作用分开并不更具可读性。您要么滚动浏览另一个包含 100 个函数的文件,要么必须打开 100 个文件。无论哪种方式,您现在都在主程序循环和您感兴趣的代码之间来回跳转。仅仅因为 switch 语句很长并不意味着它很难理解,至少不超过多个函数或文件,这也许更容易。 |
谢谢@tmilker,这其实也是我的想法。您要有效更改的只是同一代码块在文件中的位置,否则它将被移动到一个新文件/多个文件中。 |
当前的 switch 是一个巨大的代码块,需要大量注释才能保持清晰,即使这样也做得不好。还有很多代码乱码,比较麻烦。有了命名函数,可以搜索合适的函数名,不知道名字的可以在switch中查找。这也将使按正确顺序排列案例成为一项工作,不太可能由于抓取错误的行数或通过重新定位大块代码而产生合并冲突而导致引入错误(这就是我一直推迟的原因到目前为止固定顺序。当然,以正确的顺序排列代码编号也应该使进一步的速度优化成为可能(尽管我不知道编译器是否足够聪明来做到这一点)。 我宁愿阅读一个整齐排列的函数名称列表,然后跳转到相应的命名函数,也不愿经历试图扫描当前 switch 语句的噩梦。 没有提到的是,一些代码执行的操作需要由其他代码或 LCD 交互等触发。例如,当我开始处理 fwretract 代码时,G10/G11 实现和自动撤回代码(从 G1 触发)具有相同代码的两个独立的、不同的损坏版本。我修复了 G10/G11,但起初忽略了 G1 中的那个。我将重写的 G10/G11 代码移到了一个函数中,这样自动收缩代码也可以调用它,从而以更少的代码实现更好的功能,并减少未来引入错误或不一致行为的可能性。 |
@tmilker将 G-/M- 代码从主程序循环中分离出来只是使代码更易于理解的第一步。将功能分组到块(单个文件)中,以便它在一个地方停止来回跳转的需要,因为一个功能的所有内容都位于一起,比如一个文件中的 SD 代码,另一个文件中的床等。 |
在仔细考虑之后,也许在单独的文件中有类似 米。 |
我们知道当前的解决方案不是最优的。但它很简单,不会占用很多资源。 我们正在试验其他不会使用更多闪存和内存的系统。 另一种选择是使用像 ragel 这样的词法分析器。词法分析器代码很难用 arduino IDE 更改。 |
TBC @ #1456 _ 许多内联函数_ |
几天来我一直在集思广益,试图想出一种更好的方法来枚举固件已知的 G 代码和 M 代码并将它们链接到它们的功能,但看起来答案将在编程领域我不是很熟悉(因为我的大部分经验都是使用高级语言,这会使我想做的事情变得容易,并且该死的速度和内存使用的后果)。我正在寻找满足以下条件的东西:
我的研究表明,当前的 switch 语句可能被编译成哈希表或查找表(我从来没有直接使用过,而且我并不完全清楚其中的区别)以使其比 if… else if… else 施工。后一种构造可以构建为遍历数组并检查每个元素是否与接收到的代码匹配,这将使 1 和 2 变得容易,但违反了 3。
因为编译器从当前的 switch 语句创建了这些表之一,所以应该可以显式地创建这个表,以便它可以用来更好地组织 1 的代码,并允许以其他方式引用该表以满足2,而代码识别在编译代码中发生的方式将与现在相同,满足 3。
我想要的是其他人关于这些是否值得追求的目标的意见,以及关于如何实施这些目标的任何更好的想法(显然,这样做有任何帮助)。