【问题标题】:Performance analysis性能分析
【发布时间】:2019-10-14 21:33:11
【问题描述】:

看一个很简单的程序,如何弄清楚CPU用在了哪里:

use mmap::*;
use crc::{crc32, Hasher32};

use std::cmp::min;
use std::env::args;
use std::fs::File;
use std::fs::metadata;
use std::os::unix::io::AsRawFd;
use std::slice::from_raw_parts;
use std::time::Instant;

fn main() -> std::io::Result<()> {
   let filename = args().nth(1).unwrap();
   let t0 = Instant::now();
   let file = File::open(String::from(&filename[..]))?;
   let fd = file.as_raw_fd();
   let fs = metadata(filename)?;
   let sz = fs.len() as usize;

   let mut offset: usize = 0;
   let mut c32 = crc32::Digest::new(crc32::IEEE);
   while offset < sz {
      let rem = sz - offset;
      let rem = min(rem, 1024 * 1024);

      let map = MemoryMap::new(rem, &[MapOption::MapFd(fd),
                                      MapOption::MapReadable,
                                      MapOption::MapOffset(offset)]).
                map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
      let buf = map.data();
      c32.write(unsafe { from_raw_parts(buf, rem) });
      offset += rem;
   }
   println!("{:08x} in {:.3}", c32.sum32(), t0.elapsed().as_secs());
   Ok(())
}

这是为了对命令行上提供的文件进行内存映射,并计算它的 CRC32。我不是在寻找其他可能实现这一目标的实现,因为我的目的是练习与 libc 函数的交互。

该程序似乎运行正常,但比我编写的同等 Go 或 Java 程序消耗更多的时间和 CPU,即使我认为它是经过优化编译的:

Cargo.toml:

[profile.dev]
opt-level = 3

[package]
name = "mmap"
version = "0.1.0"
authors = ["Gee"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

mmap = "~0.1.1"
crc = "^1.0.0"

Mac OSX、SSD、以各种顺序执行 Go 和 Rust 程序,以消除“冷”文件缓冲区和“热”缓冲区的影响 - 为 Go 产生大约 4 秒的冷运行,为 Go 产生不到一秒的热运行,但 Rust 总是 30 多秒。也试过cargo build --release。 Rust 程序的 CPU 使用率也明显更高(例如,Go 为 25%,Rust 为 80+%)。我希望 CPU 利用率主要来自 CRC-32 计算和在某些缓冲区之间复制文件内容。不同之处在于导致 Rust 在这里做额外工作的东西。

Go 程序使用相同的方法扫描一个 1GB 的 XML 文件,syscall.Mmap,一次映射 1MB:

$ ../a ~/demo/dest.xml 
a772d8c4 in 3.978

货物运行:

$ cargo run ~/demo/dest.xml 
    Finished dev [optimized + debuginfo] target(s) in 0.13s
     Running `target/debug/mmap /Users/ololo/demo/dest.xml`
a772d8c4 in 33

在 cmets 中有一个显示 Java 程序的请求。这是可以看到在 3.9 秒内读取该文件的 Go 程序:

package main

import(
   "fmt"
   "hash/crc32"
   "os"
   "syscall"
   "time"
)

func main() {
   t0 := time.Now()
   fn := os.Args[1]
   fd, err := os.Open(fn)
   if err != nil {
      fmt.Printf("Couldn't open %s", err)
      return
   }
   defer fd.Close()

   fi, err := fd.Stat()
   if err != nil {
      fmt.Printf("Couldn't stat: %s", err)
      return
   }

   sz := fi.Size()

   cksum := crc32.NewIEEE()
   var off int64

   off = 0
   for sz > off {
      rem := 1024 * 1024
      if sz - off < int64(rem) {
         rem = int(sz - off)
      }
      data, err := syscall.Mmap(int(fd.Fd()), off, rem, syscall.PROT_READ, syscall.MAP_SHARED)
      if err != nil {
         fmt.Printf("Couldn't mmap at %d", off)
         return
      }
      off += int64(rem)
      n, err := cksum.Write(data)
      if err != nil || n != len(data) {
         fmt.Printf("Somehow could not cksum %d", len(data))
         return
      }

      syscall.Munmap(data)
   }

   fmt.Printf("%x in %.3f\n", cksum.Sum32(), time.Now().Sub(t0).Seconds())
}

【问题讨论】:

  • 1. cargo run --release; 2. 你滥用unsafe,即使使用--release,你也可以不剥夺编译器的一些有价值的优化; 3. 你实际上并没有向我们展示 java 代码,所以我们甚至无法告诉你你是否对同样的东西进行了基准测试。
  • @SébastienRenauld 1. 我试过了; 2.这不是滥用,似乎有必要将ptr转换为slice——但更重要的是,如何确认,或者如何绕过不使用unsafe?; 3. 我想保持问题简短,并专注于如何理解 Rust 花费时间的地方。我用 Go 程序更新了问题,可以看到它在 4 秒内运行,消耗 25% 的 CPU。
  • 对于#1,您为什么在问题中报告未优化的运行时间?并不是说我将结果称为阴影或其他任何东西,但这有点误导。
  • 如果在我回家之前没有人接触过这个问题,我会深入研究它;我没有工具可以在这台笔记本电脑上正确解决这个问题。分析应该会出现一些情况,因为 25% 的 CPU 使用率会非常明显。
  • 如果有人在看,这里是更新。添加函数调用的时间测量结果表明 mmap 板条箱毕竟有问题:```a772d8c4 in 37.119 时间细分:mmap:32.859 .data():0.000 from_raw_parts:0.000 crc32.write:4.233 ```跨度>

标签: rust


【解决方案1】:

原来这里有操作系统细节。

let map = MemoryMap::new(
    rem,
    &[
        MapOption::MapFd(fd),
        MapOption::MapNonStandardFlags(libc::MAP_SHARED),
        MapOption::MapReadable,
        MapOption::MapOffset(offset),
    ],
)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;

由于某种原因,我正在使用的 crate 没有显式公开对 MAP_SHARED 的访问权限,并且考虑到 cmets 的问题,它可能不会在 Linux 上产生影响。 Mac OSX 似乎对待MAP_SHARED 与未指定该标志时不同,从而使mmap 系统调用显着变慢。当指定MAP_SHARED 时,Mac 上的“暖”运行将返回到大约 3 秒。

【讨论】:

    猜你喜欢
    • 2016-12-13
    • 2019-08-16
    • 2016-07-28
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多