假设我精心设计了一组类来抽象一些东西,现在我担心我的C ++编译器是否能够to peel off those wrappings and emit really clean, concise and fast code。我如何找出编译器决定做什么?
我知道的唯一方法是to inspect the disassembly。这适用于简单的代码,但有两个缺点 - the compiler might do it different when it compiles the same code again并且机器代码分析也不容易,所以需要付出努力。
我怎样才能找到编译器如何决定实现我用C ++编写的代码?
答案 0 :(得分:7)
我担心你在这个问题上运气不好。你试图找出“编译器做了什么”。编译器所做的是生成机器代码。反汇编只是一种更易读的机器代码形式,但它无法添加不存在的信息。你无法通过查看汉堡包来弄清楚绞肉机的工作原理。
答案 1 :(得分:4)
我其实很想知道这件事。
过去几个月,我对Clang项目非常感兴趣。
Clang特别感兴趣的一个优点是,您可以发出优化的LLVM IR代码而不是机器代码。 IR是一种高级汇编语言,具有结构和类型的概念。
Clang编译器套件中的大多数优化传递确实在IR上执行(最后一轮当然是特定于体系结构并由后端根据可用操作执行),这意味着您实际上可以看到,就在IR,如果对象创建(如在您的链接问题中)已经优化了。
我知道它仍然是汇编(虽然是更高级别),但它对我来说似乎更具可读性:
这适合你:)?
答案 2 :(得分:3)
定时代码将直接测量其速度,并且可以避免完全查看反汇编。这将检测编译器,代码修改或细微配置更改何时影响性能(无论好坏)。以这种方式,它比拆卸更好,这只是间接措施。
像代码大小这样的东西也可以作为问题的可能指标。他们至少建议改变一些事情。当编译器将一堆模板(或其他任何内容)归结为一系列简明的指令时,它还可以指出意外的代码膨胀。
当然,查看反汇编是一种很好的技术,用于开发代码并帮助确定编译器是否正在进行足够好的转换。你可以看看你是否真的得到了你的钱。
换句话说,衡量你的期望然后潜入,如果你认为编译器“欺骗”你。
答案 3 :(得分:2)
您可能会发现一个编译器有一个转储后优化AST /表示的选项 - 它的可读性是另一回事。如果你正在使用GCC,那么它有可能不会太难,并且有人可能已经做过了 - GCCXML做了一些模糊的事情。如果您想要构建生产代码的编译器无法做到这一点,那就没什么用了。
之后,某些编译器(例如带有-S的gcc)可以输出汇编语言,这可能比读取反汇编更有用:例如,某些编译器将高级源替换为注释,然后是相应的汇编。
至于你提到的缺点:
编译器在再次编译相同代码时可能会有所不同
绝对,只有编译器文档和/或源代码可以告诉你这个机会,尽管你可以对夜间测试运行进行一些性能检查,这样如果性能突然改变你就会收到警报
并且机器代码分析也不重要,因此需要付出努力。
提出了一个问题:什么会更好。我可以对您在代码上运行编译器的过程进行映像,它记录变量何时缓存在使用点的寄存器中,哪些函数调用内联,甚至是指令可能占用的最大CPU周期数(在编译时可知)等等并产生一些记录,然后是源查看器/编辑器,它相应地对源进行颜色编码和注释。这是你想到的那种事吗?它会有用吗?也许比其他人更多 - 例如寄存器使用的黑白信息忽略了各种级别的CPU缓存(以及运行时的利用率)的效用;编译器可能甚至都没有试图建模。知道内嵌的实际位置会给我一种温暖的模糊感觉。但是,分析似乎更有前途和有用。我担心这些好处比实际更直观,并且编译器编写者最好不要追求C ++ 0x特性,运行时检测,内省,或者在旁边写D“; - )。
答案 4 :(得分:2)
您想知道编译器是否生成了“干净,简洁和快速的代码”。
“清洁”在这里没什么意义。清洁代码是一种促进人类可读性和可维护性的代码。因此,该属性涉及程序员看到的内容,即源代码。编译器生成的二进制代码没有清晰的概念,只能由CPU查看。如果您编写了一组很好的类来抽象问题,那么您的代码就像一样干净。
“简明代码”有两个含义。对于源代码,这是关于保存稀缺的程序员眼睛和大脑资源,但是,正如我上面指出的,这不适用于编译器输出,因为在那一点上没有人参与。另一个含义是关于紧凑的代码,因此具有较低的存储成本。这会对执行速度产生影响,因为RAM很慢,因此您确实希望代码的最内层循环适合CPU级别1缓存。可以使用一些开发人员工具获得编译器生成的函数的大小;在使用GNU binutils的系统上,您可以使用size
命令获取目标文件(编译的.o
)和objdump
中的总代码和数据大小,以获取更多信息。特别是,objdump -x
将给出每个函数的大小。
“快速”是需要衡量的。如果您想知道您的代码是否快速,那么请对其进行基准测试。 如果代码对你手头的问题来说太慢了(这种情况不常发生)和你有一些令人信服的理论理由认为硬件可以做很多事情更好(例如,因为您估计了所涉及的操作数量,深入研究了CPU手册,并掌握了所有内存带宽和缓存问题),然后(并且只有这样)是时候看看编译器对你的代码做了什么。除非这些条件,源代码的清洁是一个更重要的问题。
所有这一切,如果你有先验关于编译器可以做什么的概念,它可以帮助很多。这需要一些培训。我建议你看一下经典dragon book;但否则你将不得不花一些时间编译一些示例代码并查看程序集输出。 C ++不是最简单的语言,你可能想要从简单的C开始。理想情况下,一旦你知道能够编写自己的编译器,那么你就知道编译器可以做什么了,你可以猜到它是什么>将对给定的代码执行。
答案 5 :(得分:1)
你的问题的答案几乎被卡尔钉死了。如果你想看看编译器做了什么,你必须开始检查它产生的汇编代码 - 需要使用elbow润滑脂。至于发现它如何实现你的代码“如何”背后的“原因”...正如你所提到的,每个编译器(以及每个构建,可能)都是不同的。有不同的方法,不同的优化等。但是,我不担心它是否发出干净,简洁的机器代码 - 清洁和简洁应留给源代码。另一方面,速度几乎是程序员的责任(剖析ftw)。更有趣的问题是正确性,可维护性,可读性等。如果您想查看它是否进行了特定的优化,编译器文档可能会有所帮助(如果它们可用于您的编译器)。您也可以尝试搜索以查看编译器是否实现了用于优化任何内容的已知技术。但是,如果这些方法失败了,那么你就回去阅读汇编代码了。请记住,您检查的代码可能对性能或可执行文件大小几乎没有影响 - 在深入研究任何这些内容之前,先获取一些硬数据。
答案 6 :(得分:1)
实际上,如果你能得到你的编译器,有办法得到你想要的东西 生成DWARF调试信息。每个都有一个DWARF描述 外线功能,在该描述中,(希望)是条目 对于每个内联函数。阅读DWARF,有时是编译器,这并非易事 不产生完整或准确的DWARF,但它可以是一个有用的信息来源 关于编译器实际做了什么,它不依赖于任何一个编译器或CPU。 拥有DWARF阅读库后,您可以使用各种有用的工具 围绕它建立。
不要期望将它与Visual C ++一起使用,因为它使用不同的调试格式。 (但您可以通过调试助手库进行类似的查询 附带它。)
答案 7 :(得分:1)
如果你的编译器设法翻译你的“包装并发出真正干净,简洁和快速的代码”,那么跟进发出的代码的努力应该是合理的。
与另一个答案相反,我觉得如果它(相对)可以轻松地映射到原始源代码,那么发出的汇编代码可能很“干净”,如果它不包含遍布整个地方的调用而且系统是跳跃不是太复杂。通过代码调度和重新排序,优化的机器代码也是可读的,唉,已成为过去。