为什么这个基于Lwt且看似并发的代码如此不一致

Edg*_*ian 5 ocaml lwt

我正在尝试创建Lwt的并发示例,并想出了这个小样本

let () =
  Lwt_main.run (
      let start = Unix.time () in
      Lwt_io.open_file Lwt_io.Input "/dev/urandom" >>= fun data_source ->
      Lwt_unix.mkdir "serial" 0o777 >>= fun () ->
      Lwt_list.iter_p
        (fun count ->
         let count = string_of_int count in
         Lwt_io.open_file
           ~flags:[Unix.O_RDWR; Unix.O_CREAT]
           ~perm:0o777
           ~mode:Lwt_io.Output ("serial/file"^ count ^ ".txt") >>= fun h ->
         Lwt_io.read ~count:52428800
                     data_source >>= Lwt_io.write_line h)
        [0;1;2;3;4;5;6;7;8;9] >>= fun () ->
      let finished = Unix.time () in
      Lwt_io.printlf "Execution time took %f seconds" (finished -. start))
Run Code Online (Sandbox Code Playgroud)

编辑:要求50GB它是:"然而这是非常缓慢,基本无用.内部绑定是否需要以某种方式强制?"

编辑:我最初写的要求50 GB,它从未完成,现在我有一个不同的问题,要求50MB,执行几乎是瞬间和du -sh报告只有80k的目录大小.

编辑:我也尝试使用相同的错误结果显式关闭文件句柄的代码.

我是OS X最新版本并编译

ocamlfind ocamlopt -package lwt.unix main.ml -linkpkg -o Test

(我也尝试过/dev/random,是的,我正在使用挂钟时间.)

ivg*_*ivg 17

所以,你的代码有一些问题.

问题1

主要问题是你Lwt_io.read错误地理解了这个功能(没有人可以怪你!).

val read : ?count : int -> input_channel -> string Lwt.t
  (** [read ?count ic] reads at most [len] characters from [ic]. It
      returns [""] if the end of input is reached. If [count] is not
      specified, it reads all bytes until the end of input. *)
Run Code Online (Sandbox Code Playgroud)

~count:len指定,它会读取最多 len字符.至多意味着它可以减少阅读量.但如果count省略该选项,则它将读取所有数据.我个人认为这种行为不直观,即使不是很奇怪.因此,这最多意味着len或多或少,即,不能保证它将准确读取len字节.事实上,如果你在你的程序中添加一个检查:

 Lwt_io.read ~count:52428800 data_source >>= fun data ->
 Lwt_io.printlf "Read %d bytes" (String.length data) >>= fun () ->
 Lwt_io.write h data >>= fun () ->
Run Code Online (Sandbox Code Playgroud)

您将看到4096,每次尝试它只读取字节:

Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Read 4096 bytes
Run Code Online (Sandbox Code Playgroud)

为什么4096?因为这是默认的缓冲区大小.但它实际上并不重要.

问题2

Lwt_io模块实现缓冲IO.这意味着您的所有写入和读取都不会直接进入文件,而是缓存在内存中.这意味着,你应该记住flushclose.您的代码不会在完成时关闭描述符,因此您可能会遇到某个缓冲区在程序终止后保持未刷新状态的情况.Lwt_io特别是,在程序退出之前刷新所有缓冲区.但是你不应该依赖这个没有文档的功能(将来你可能会尝试任何其他缓冲的io,比如来自标准C库的fstream).所以,总是关闭你的文件(另一个问题是今天的文件描述符是最宝贵的资源,他们的泄漏很难找到).

问题3

不要使用/dev/urandom/dev/random测量io.对于前者,您将测量随机数生成器的性能,对于后者,您将测量机器中的熵流.两者都很慢.根据CPU的速度,您很少会获得超过16 Mb/s的速度,而且它的Lwt吞吐量会低得多.读取/dev/zero和写入/dev/null将实际执行内存中的所有传输,并显示可由程序实现的实际速度.一个编写良好的程序仍将受到内核速度的限制.在下面提供的示例程序中,这将显示700 MB/s的平均速度.

问题4

如果您真的在努力表现,请不要使用缓冲的io.你永远不会得到最大化.例如,Lwt_io.read将首先在缓冲区中读取,然后它将创建一个string并将数据复制到该字符串.如果你真的需要一个性能,那么你需要提供自己的缓冲.在大多数情况下,没有必要这样做,因为Lwt_io它非常有效.但是如果你需要每秒处理几十兆字节,或者需要一些特殊的缓冲策略(非线性),你可能需要考虑提供自己的缓冲.好消息是Lwt_io允许你这样做.您可以查看一个示例程序,它将测量Lwt输入/输出的性能.它模仿着名的pv节目.

问题5

您希望通过并行运行线程来获得一些性能.问题是在你的测试中没有并发的地方./dev/random(以及/dev/zero)是一个仅由CPU限制的设备.这与调用random函数一样.它永远是available,所以没有系统调用会阻止它.写入常规文件也不是并发的好地方.首先,通常只有一个硬盘驱动器,其中有一个写入头.即使系统调用将阻塞并对其他线程产生控制,这也会导致性能偏离,因为两个线程现在将竞争标头位置.如果你有SSD,那么标题就不会有任何竞争,但性能会更差,因为你会破坏你的缓存.但幸运的是,通常写常规文件不会阻止.因此,您的线程将运行,即它们将被序列化.