MDK编译优化笔记

在一次使用MDk的编译优化等级比较高的时候发现编译不优化时功能正常,开了优化等级02就出现异常,调试中看了很多博客总结一下。

  • 一个变量,如果你的主程序要用到,同时中断还要用到,要加volatile修饰。告诉编译器这个变量是可能随时发生变化的,使得编译器编译程序的时候,每次都从RAM里面读取数据,而不是使用之前缓存到寄存器里面的值。
  • 对于多任务的程序,如果一个公共变量被多个任务用到也要加volatile修饰。
  • 同时变量定义的时候用了关键字volatile修饰,但是在其他文件引用时不加volatile变量修饰一样会被编译器优化掉。现则反过来想想,原因还是很简单的,MDK编译多个文件时是分别编译,最后再用链接器链接,当编译的时候一个模块引用另外一个模块的变量,完全是靠的变量声明,如果声明都不加volatile,那么引用的模块肯定会把变量当成普通变量的,再反推一下,如果原变量没有加volatile,但是声明的时候加了volatile,是不是引用的模块会将这个变量当成volatile型变量呢

  C编译器是以每个C文件作为基本编译单元的,称为模块,被编译为obj;而模块之间的函数或变量访问都是通过标号来实现的,标号本身没有任何属性,只是提供给链接器使用的一个符号名称而已,标号的属性完全就靠调用的地方的原型声明来决定的!因此,你在一个.C模块中定义为volatile,仅仅是在.C模块中告诉编译器不要优化而已,在另外的模块内使用了这个变量,而它们是不知道该变量是什么属性的,所以只有靠原型声明来告诉编译器这些信息了。

最后简单的给总结一下:别小看原型声明,它是联系各个模块的桥梁,是各种输出符号的属性信息所在。编译原理不懂就是比较亏啊。。。

最后补充一下MDK中关于编译的一些选项说明:

USE Cross-Module Optimization

 跨模块优化从先前的构建中获取信息,并使用它将UNUSED函数放入其中在相应的目标文件中拥有自己的ELF部分。 此选项也称为链接器反馈,需要构建程序两次以利用它来减少代码大小。

USE MicroLIBMicroLIB

是一个针对嵌入式平台优化过的C库可以减少应用程序的大小。他是标准C库的一个子集,以提供功能和代码大小之间的权衡,比如一些标准C库的memcpy函数应用到微控制器上就运行速度来说比较慢。原文(Some of the standard C library functions such as memcpy() are slower, while some features of the default library are not supported.)这里还说默认的库还不支持一些特性:

包括:

  1. 操作系统功能,例如 abort(),exit(),time(),system(),getenv()
  2. 宽字符和多字节支持,例如 mbtowc(),wctomb()
  3. stdio文件I/O函数,stdin,stdout和stderr除外
  4. 位置无关且线程安全的代码

Link-Time Code Generation(这个选项新版好像没有了)
链接时代码生成指示编译器以中间格式创建对象,以便链接器可以执行进一步的代码优化。 这使代码生成器可以同时查看所有对象的跨文件依赖性,从而允许它应用更高级别的优化。 链接时代码生成可以减少代码大小,并使您的应用程序运行得更快。
Optimization Levels

不同的优化级别允许您在级别之间进行权衡已编译代码中可用的调试信息以及代码的性能。以下优化级别可用:
-O0应用最小优化。
大多数优化都被关闭,生成的代码具有最佳的调试视图。
-O1应用受限优化。
例如,删除未使用的内联函数和未使用的静态函数。在这个优化级别,编译器还应用自动优化,例如删除冗余代码和重新排序指令以避免互锁情况。生成的代码经过合理优化,具有良好的调试视图。
-O2应用高优化(这是默认设置)。
在此级别应用的优化利用了ARM对处理器体系结构的深入了解,利用给定目标的特定于处理器的行为。它生成优化良好的代码,但有限调试视图。
-O3应用最积极的优化。
优化符合用户的-Ospace / -Otime选择。默认情况下,多文件编译是启用,这会导致更长的编译时间,但会提供最高级别的优化。

Optimize for Time
“优化时间”复选框使编译器进行优化,更加注重实现最佳效果检查时的性能(-Otime)或未选中时的最小代码大小(-Ospace)。
取消选中Optimize for Time就意味着选择-Ospace选项,该选项指示编译器执行优化以可能增加的执行时间为代价来减小Image文件大小。例如,使用非内联函数调用而不是大型结构副本的内联代码。 这是默认选项。从中运行编译器时命令行,使用'-Ospace'调用此选项选中Optimize for -Otime选项,该选项指示编译器以最快的速度优化代码执行时间,有可能增加image文件大小。建议编译时间关键部分您的代码使用-Otime,其余使用-Ospace指令。

Split Load and Store Multiples
指示编译器将涉及大量寄存器的LDM和STM指令拆分为一系列较少多个寄存器的加载/存储。这意味着16个寄存器的LDM可以分成4个独立的LDM,每个LDM由4个寄存器组成。此选项有助于减少没有缓存或写缓冲区的ARM系统上的中断延迟,以及使用零等待状态32位内存的系统。
例如,ARM7和ARM9处理器只能在指令边界上执行异常。如果在无缓存的ARM7和ARM9系统中的16个寄存器的LDM开始时发生异常,则系统将在获取异常之前完成对存储器的16次访问。根据存储器仲裁系统,这可能导致非常高的中断延迟。将LDM拆分为4个寄存器的4个独立LDM意味着处理器在加载最多4个寄存器后将采用异常,从而大大减少中断延迟。选择此选项可提高系统的整体性能。
One ELF Section per Function
选项告诉编译器将所有函数放入它们各自的ELF部分。 这允许链接器删除未使用的函数.ELF代码部分通常包含许多函数的代码。 链接器通常只能删除未使用的ELF部分,而不是未使用的函数。 只有当所有内容都未使用时,才能删除ELF部分。因此,将每个函数拆分为自己的ELF部分允许编译器轻松识别哪些未使用,并将其删除。选择此选项会增加编译代码所需的时间,但可以提高性能。

常见优化目标选项

最小的目标代码:

选中
• The MicroLIB C library
• Cross-module optimization
• Optimization level 2 (-O2)

最好的代码表现性能:

选中
• Cross-module optimization
• Optimization level 3 (-O3)
• Optimize for time

最后总结一句话,只要代码逻辑够严密无论是多高的编译优化等级都是不会出问题的,所以在资源不是出现了必须要优化的时候不建议优化代码,自己增加实现难度而已,反之如果你追求功能的稳定可以在优化等级下发现0级优化下存在的问题进行完善在改回0级优化编译此时代码的逻辑漏洞会减少。

  • 一个变量,如果你的主程序要用到,同时中断还要用到,要加volatile修饰。告诉编译器这个变量是可能随时发生变化的,使得编译器编译程序的时候,每次都从RAM里面读取数据,而不是使用之前缓存到寄存器里面的值。
  • 对于多任务的程序,如果一个公共变量被多个任务用到也要加volatile修饰。
  • 同时变量定义的时候用了关键字volatile修饰,但是在其他文件引用时不加volatile变量修饰一样会被编译器优化掉。现则反过来想想,原因还是很简单的,MDK编译多个文件时是分别编译,最后再用链接器链接,当编译的时候一个模块引用另外一个模块的变量,完全是靠的变量声明,如果声明都不加volatile,那么引用的模块肯定会把变量当成普通变量的,再反推一下,如果原变量没有加volatile,但是声明的时候加了volatile,是不是引用的模块会将这个变量当成volatile型变量呢

  C编译器是以每个C文件作为基本编译单元的,称为模块,被编译为obj;而模块之间的函数或变量访问都是通过标号来实现的,标号本身没有任何属性,只是提供给链接器使用的一个符号名称而已,标号的属性完全就靠调用的地方的原型声明来决定的!因此,你在一个.C模块中定义为volatile,仅仅是在.C模块中告诉编译器不要优化而已,在另外的模块内使用了这个变量,而它们是不知道该变量是什么属性的,所以只有靠原型声明来告诉编译器这些信息了。

最后简单的给总结一下:别小看原型声明,它是联系各个模块的桥梁,是各种输出符号的属性信息所在。编译原理不懂就是比较亏啊。。。

最后补充一下MDK中关于编译的一些选项说明:

USE Cross-Module Optimization

 跨模块优化从先前的构建中获取信息,并使用它将UNUSED函数放入其中在相应的目标文件中拥有自己的ELF部分。 此选项也称为链接器反馈,需要构建程序两次以利用它来减少代码大小。

USE MicroLIBMicroLIB

是一个针对嵌入式平台优化过的C库可以减少应用程序的大小。他是标准C库的一个子集,以提供功能和代码大小之间的权衡,比如一些标准C库的memcpy函数应用到微控制器上就运行速度来说比较慢。原文(Some of the standard C library functions such as memcpy() are slower, while some features of the default library are not supported.)这里还说默认的库还不支持一些特性:

包括:

  1. 操作系统功能,例如 abort(),exit(),time(),system(),getenv()
  2. 宽字符和多字节支持,例如 mbtowc(),wctomb()
  3. stdio文件I/O函数,stdin,stdout和stderr除外
  4. 位置无关且线程安全的代码

Link-Time Code Generation(这个选项新版好像没有了)
链接时代码生成指示编译器以中间格式创建对象,以便链接器可以执行进一步的代码优化。 这使代码生成器可以同时查看所有对象的跨文件依赖性,从而允许它应用更高级别的优化。 链接时代码生成可以减少代码大小,并使您的应用程序运行得更快。
Optimization Levels

不同的优化级别允许您在级别之间进行权衡已编译代码中可用的调试信息以及代码的性能。以下优化级别可用:
-O0应用最小优化。
大多数优化都被关闭,生成的代码具有最佳的调试视图。
-O1应用受限优化。
例如,删除未使用的内联函数和未使用的静态函数。在这个优化级别,编译器还应用自动优化,例如删除冗余代码和重新排序指令以避免互锁情况。生成的代码经过合理优化,具有良好的调试视图。
-O2应用高优化(这是默认设置)。
在此级别应用的优化利用了ARM对处理器体系结构的深入了解,利用给定目标的特定于处理器的行为。它生成优化良好的代码,但有限调试视图。
-O3应用最积极的优化。
优化符合用户的-Ospace / -Otime选择。默认情况下,多文件编译是启用,这会导致更长的编译时间,但会提供最高级别的优化。

Optimize for Time
“优化时间”复选框使编译器进行优化,更加注重实现最佳效果检查时的性能(-Otime)或未选中时的最小代码大小(-Ospace)。
取消选中Optimize for Time就意味着选择-Ospace选项,该选项指示编译器执行优化以可能增加的执行时间为代价来减小Image文件大小。例如,使用非内联函数调用而不是大型结构副本的内联代码。 这是默认选项。从中运行编译器时命令行,使用'-Ospace'调用此选项选中Optimize for -Otime选项,该选项指示编译器以最快的速度优化代码执行时间,有可能增加image文件大小。建议编译时间关键部分您的代码使用-Otime,其余使用-Ospace指令。

Split Load and Store Multiples
指示编译器将涉及大量寄存器的LDM和STM指令拆分为一系列较少多个寄存器的加载/存储。这意味着16个寄存器的LDM可以分成4个独立的LDM,每个LDM由4个寄存器组成。此选项有助于减少没有缓存或写缓冲区的ARM系统上的中断延迟,以及使用零等待状态32位内存的系统。
例如,ARM7和ARM9处理器只能在指令边界上执行异常。如果在无缓存的ARM7和ARM9系统中的16个寄存器的LDM开始时发生异常,则系统将在获取异常之前完成对存储器的16次访问。根据存储器仲裁系统,这可能导致非常高的中断延迟。将LDM拆分为4个寄存器的4个独立LDM意味着处理器在加载最多4个寄存器后将采用异常,从而大大减少中断延迟。选择此选项可提高系统的整体性能。
One ELF Section per Function
选项告诉编译器将所有函数放入它们各自的ELF部分。 这允许链接器删除未使用的函数.ELF代码部分通常包含许多函数的代码。 链接器通常只能删除未使用的ELF部分,而不是未使用的函数。 只有当所有内容都未使用时,才能删除ELF部分。因此,将每个函数拆分为自己的ELF部分允许编译器轻松识别哪些未使用,并将其删除。选择此选项会增加编译代码所需的时间,但可以提高性能。

常见优化目标选项

最小的目标代码:

选中
• The MicroLIB C library
• Cross-module optimization
• Optimization level 2 (-O2)

最好的代码表现性能:

选中
• Cross-module optimization
• Optimization level 3 (-O3)
• Optimize for time

最后总结一句话,只要代码逻辑够严密无论是多高的编译优化等级都是不会出问题的,所以在资源不是出现了必须要优化的时候不建议优化代码,自己增加实现难度而已,反之如果你追求功能的稳定可以在优化等级下发现0级优化下存在的问题进行完善在改回0级优化编译此时代码的逻辑漏洞会减少。

猜你喜欢

转载自www.cnblogs.com/CodeWorkerLiMing/p/12079963.html