Enr*_*lis 2 c assembly gcc haskell ghc
有一次我问为什么简单的“times 2”程序的 Haskell 二进制文件这么大(例如 C++)?,但这被欺骗了用 GHC 编译成巨大的二进制文件的小型 Haskell 程序。
\n我决定问一个类似的问题,但重点是代码的实际作用,因此是它的汇编形式,而不是二进制文件。我还没有找到我的问题的现有答案。
\n所以不,这不能回答我的问题。
\n这是 C++(实际上是 C)的哑程序,它假设给出了 1 个表示数字的命令行参数,将其转换为 anint并将return其乘以 2 作为可执行文件的退出代码(是的,我可以\不超过 255)
int main(int argc, char * argv[]) {\n int r{0};\n int c{0};\n while (argv[1][c] != \'\\0\') {\n r *= 10;\n r += argv[1][c++] - \'0\';\n }\n return r*2;\n}\nRun Code Online (Sandbox Code Playgroud)\n\nmain: # @main\n mov rcx, qword ptr [rsi + 8]\n movzx edx, byte ptr [rcx]\n test dl, dl\n je .LBB0_1\n inc rcx\n xor eax, eax\n.LBB0_3: # =>This Inner Loop Header: Depth=1\n movsx edx, dl\n lea eax, [rax + 4*rax]\n lea eax, [rdx + 2*rax]\n add eax, -48\n movzx edx, byte ptr [rcx]\n inc rcx\n test dl, dl\n jne .LBB0_3\n add eax, eax\n ret\n.LBB0_1:\n xor eax, eax\n ret\nRun Code Online (Sandbox Code Playgroud)\n据我所知,这是一个“类似的”Haskell 程序,它完成了完全相同的任务,但它仍然生成了一个更大的程序集(257 行,因此长了 12 倍以上\xc2\xb9:
\nmain: # @main\n mov rcx, qword ptr [rsi + 8]\n movzx edx, byte ptr [rcx]\n test dl, dl\n je .LBB0_1\n inc rcx\n xor eax, eax\n.LBB0_3: # =>This Inner Loop Header: Depth=1\n movsx edx, dl\n lea eax, [rax + 4*rax]\n lea eax, [rdx + 2*rax]\n add eax, -48\n movzx edx, byte ptr [rcx]\n inc rcx\n test dl, dl\n jne .LBB0_3\n add eax, eax\n ret\n.LBB0_1:\n xor eax, eax\n ret\nRun Code Online (Sandbox Code Playgroud)\n是的,它以不同的方式做到这一点,特别是因为我无法真正用 Haskell 吐出程序代码,但我的观点是,当我编译上面的代码时,编译器应该知道那是main那为计算退出值而创建的任何中间结果都不需要在其他地方使用。为什么不能证明所有这些代码所做的事情都可以通过几行汇编来完成(正如上面的 C/C++ 程序所演示的那样?)
或者,如果情况并非如此,那么为什么装配体如此之大?我只是忘记了适当的编译器标志吗?或者是什么?
\n难道只是开发GHC的功夫没有投入到优化这么小的代码上吗?(在这种情况下,我会想知道为什么,考虑到组件化意味着将一个大程序分成越来越小的程序:如果我不优化它的小部分,我怎么能有一个最佳程序?)或者是优化这么小的程序由于我仍然不明白的根本原因,程序根本不可能实现?
\n我想到的另一个想法是:也许我编写代码的方式并不容易被优化?也许如果我以另一种形式编写它,编译器可以做得更好?
\n至于优化是一个难题,是的,我相信,但我对此一无所知。但我认为给编译器的保证越多,它就越能优化。在这方面我认为 Haskell 更好。
\n当然,我可以花一些时间来编写 Haskell 代码,它表达的过程与我在 C 中表达的过程完全相同。但是考虑到我目前拥有的程序集的大小......它真的会产生很大的差异吗?
\n如果您认为是这样,请告诉我,我将删除这个问题并花时间尝试一下。
\n有些评论提到了类似的事情
\n但只是为什么呢?我是说,
\n作为比较,当我读到(并验证)Clang 计算出这段 C 代码时,我感到很惊讶
\nimport Data.List (foldl\')\nimport System.Environment (getArgs)\nimport System.Exit (exitWith, ExitCode (ExitFailure))\nmain :: IO Int\nmain = do\n [str] <- getArgs\n exitWith $ ExitFailure $ (*) 2 $ foldl\' step 0 str\n where\n step i c = 10 * i + fromEnum c - fromEnum \'0\'\nRun Code Online (Sandbox Code Playgroud)\n归结为设定的位数,因此它将其编译为
\nfoo(int): # @foo(int)\n popcntl %edi, %eax\n retq\nRun Code Online (Sandbox Code Playgroud)\n如果架构有popcntl.
(\xc2\xb9) 事实证明,@Cubic\ 的注释提供了一种通过删除以下假设来获得稍短的程序集的方法(182 行,因此仍然比上面的 C 程序长几乎 9 倍)只有一个命令行参数,通过更改此
\nint foo(int a) {\n int count = 0;\n while (a) {\n ++count;\n a &= a - 1;\n }\n return count;\n}\nRun Code Online (Sandbox Code Playgroud)\n对此,
\nfoo(int): # @foo(int)\n popcntl %edi, %eax\n retq\nRun Code Online (Sandbox Code Playgroud)\n至于 @Cubic 提出的另一点,即 Haskell 程序需要迭代(惰性)链接列表来解析字符串,我认为不能删除。删除它,即使它是(是吗?) 可能的,在我看来意味着我们正在放弃 Haskell 的本质,放弃惯用的做事方式,并拥抱另一种语言的模拟,而这种语言与 Haskell 无关。与哈斯克尔一起做。
\ntha*_*guy 11
C++ 编译器并未编写这种高度优化的代码。你是。
如果您开始在 C++ 中使用一些与 Haskell 中相同类型的库,您会发现它不再生成整洁且简短的汇编:
#include <gmpxx.h>
#include <string>
int main(int argc, char* argv[]) {
mpz_class r{0};
mpz_class c{0};
for(char c : std::string(argv[1])) {
r *= 10;
r += c - '0';
}
r *= 2;
return r.get_ui();
}
Run Code Online (Sandbox Code Playgroud)
同时,如果你开始像编写 C++ 一样编写 Haskell(这确实不是我的强项,但这是我的第一次尝试):
foo :: Ptr CChar -> Int
foo !ptr = unsafePerformIO $ do
n <- f 0 ptr
return $! n*2
where
f :: Int -> Ptr CChar -> IO Int
f !n !ptr = do
c <- peek ptr
if c == 0 then return n else f (n*10+(fromEnum c - 0x30)) (plusPtr ptr 1)
Run Code Online (Sandbox Code Playgroud)
然后你会发现 GHC 还生成更紧凑的代码:
406c48: add r12,0x10
406c4c: cmp r12,QWORD PTR [r13+0x358]
406c53: ja 406c8a <Main_zdwf_info+0x42>
406c55: movsx rax,BYTE PTR [rsi] # c = *s
406c59: test rax,rax # if c == 0
406c5c: je 406c75 <Main_zdwf_info+0x2d>
406c5e: add r12,0xfffffffffffffff0
406c62: inc rsi # s++
406c65: imul r14,r14,0xa # r *= 10
406c69: add r14,rax # r += c
406c6c: lea rax,[r14-0x30] # tmp = r - '0'
406c70: mov r14,rax # r = tmp
406c73: jmp 406c48 <Main_zdwf_info>
Run Code Online (Sandbox Code Playgroud)