我一直在尝试从Haskell中的acm.timus.ru解决problem 1330。基本上,归结为:1)从stdin读取长度为N(N <10 ^ 4)的数组A和M对整数(M <10 ^ 5); 2)对于每个(从,到)对,打印子数组A [from..to]到stdout的总和。
由于SO不会让我发布超过2个网址作为此问题的一部分,我将在下面的Github repository中引用文件。
我想出了两个共享大部分代码的解决方案。第一个(1330_slow.hs)使用Prelude函数(getLine / read / words)并且有点慢:
$ ./bench.sh slow_hs
slow_hs
Time inside the program: 2.18
MD5 (output.slow_hs.txt) = 89bcf8fd69a7fce953595d329c8f033a
另一个解决方案(1330.hs)抛弃这些函数,将它们替换为Data.ByteString.Char8等价物(B.getLine / B.readInt / B.words),并且表现得不错:
$ ./bench.sh hs
hs
Time inside the program: 0.27
MD5 (output.hs.txt) = 89bcf8fd69a7fce953595d329c8f033a
此问题的时间限制为500毫秒,因此虽然270毫秒足够快(与我在其他语言中的解决方案相当,例如C ++和Go),但2180毫秒并没有削减它。那么为什么我的第一个解决方案如此可笑呢?即使按照Real World Haskell的分析提示,我仍然无法理解这一点(我只能想到大部分时间花在了readIntPair函数上,这对我来说没什么用。)
如果你想对自己做一些测试,我有一个Python输入生成器(gen_test.py)和一个预先生成的输入文件(input.txt),以防你没有安装Python。和两个解决方案之间的差异(slow_fast_diff.txt)。
答案 0 :(得分:8)
Bytestring
IO涉及将数据读入打包缓冲区,就像您在C中所习惯的那样。另一方面,String
是链接的字符列表,不仅使IO复杂化,而且处理这可能意味着更高的内存,处理,缓存,分支和GC的使用。
另一种表达方式的方法:ByteString
因同样的原因而快速unsigned char *
在C
中很快。
答案 1 :(得分:8)
正如其他人所说,ByteString
并不快,String
非常非常慢。
ByteString
每个字符存储一个字节,加上一些簿记开销。 String
存储每个字符12个字节(取决于您是以32位还是64位模式运行)。它还将每个字符存储在非连续内存中,因此每个字符必须单独分配给它,由垃圾收集器单独扫描,最后再单独解除分配。这意味着缓存局部性差,分配器时间很多,垃圾收集时间也很多。简而言之,这是非常低效的。
基本上,ByteString
执行C所做的事情,Java做什么,C ++做什么,C#做什么,VB做什么,以及其他所有编程语言对字符串做什么。我所知道的其他语言没有像Haskell那样低效的默认字符串类型。 (即使是Frege,这是一种Haskell方言,使用更有效的字符串类型。)
我应该指出ByteString.Char8
只处理Latin-1字符。它根本不能处理随机的Unicode字符。对于像这样的编程挑战来说,这可能不是问题,但对于“真实系统”来说可能就是这样。 ByteString
并不真正涉及异国情调的角色或不同的角色编码或任何东西;它只是假设你想要纯ASCII。这曾经是一个安全的假设;今天,不是那么多。