[Libguestfs] Poor write performance with golang binding

Csaba Henk csaba at redhat.com
Wed Feb 19 14:00:11 UTC 2020


Hi,

I scribbled a simple guestfs based program called guestfs-xfer with
following synopsis:

Usage: guest-xfer [options] [op] [diskimage]

  op = [ls|cat|write]

  options:
    -d, --guestdevice DEV            guest device
        --blocksize BS               blocksize [default: 1048576]
    -o, --offset OFF                 offset [default: 0]

So eg. `cat /dev/urandom | guest-xfer -d /dev/sda write mydisk.img` will fill
mydisk.img with pseudorandom content.

I implemented this both with Ruby and Go. The 'write' op relies on
pwrite_device.
I have pasted the codes to the end of this mail.

I'm creating mydisk.img as a qcow2 file with a raw sparse file bakcend

$ truncate -s 100g myimg.raw
$ qemu-img create -f qcow2 -b myimg.{raw,img}

Then I do

# pv /dev/sda2 | guest-xfer -d /dev/sda write mydisk.img

I find that the ruby implementation produces a 24 MiB/s throughput, while
the go one only 2 MiB/s.

Note that the 'cat' operation (that writes device content to stdout)
is reasonably fast with both language implementaitions (doing around
70 MiB/s).

Why is this, how the Go binding could be improved?

Regards,
Csaba


<code lang="ruby">
#!/usr/bin/env ruby

require 'optparse'
require 'ostruct'
require 'guestfs'

module BaseOp
extend self

  def included mod
    mod.module_eval { extend self }
  end

  def perform gu, opts, gudev
  end

  attr_reader :readonly
end

module OPS
extend self

  def find name
    m = self.constants.find { |c| c.to_s.downcase == name }
    m ? const_get(m) : nil
  end

  def names
    constants.map &:downcase
  end

  module Cat
    include BaseOp

    @readonly = 1

    def perform gu, opts
      off = opts.offset
      while true
        buf = gu.pread_device opts.dev, opts.bs, off
        break if buf.empty?
        print buf
        off += buf.size
      end
    end
  end

  module Write
    include BaseOp

    @readonly = 0

    def perform gu, opts
      off = opts.offset
      while true
        buf = STDIN.read opts.bs
        break if (buf||"").empty?
        siz = gu.pwrite_device opts.dev, buf, off
        if siz != buf.size
          raise "short write at offset #{off} (wanted #{buf.size}, done #{siz})"
        end
        off += buf.size
      end
    end
  end

  module Ls
    include BaseOp

    @readonly = 1

    def perform gu, opts
      puts gu.list_devices
    end
  end

end

def main
  opts = OpenStruct.new offset: 0, bs: 1<<20
  optp = OptionParser.new
  optp.banner << " [op] [diskimage]

  op = [#{OPS.names.join ?|}]

  options:"

  optp.on("-d", "--guestdevice DEV", "guest device") { |c| opts.dev = c }
  optp.on("--blocksize BS", Integer,
          "blocksize [default: #{opts.bs}]") { |n| opts.bs = n }
  optp.on("-o OFF", "--offset", Integer,
          "offset [default: #{opts.offset}]") { |n| opts.offset = n }
  optp.parse!

  unless $*.size == 2
    STDERR.puts optp
    exit 1
  end
  opname,image = $*

  op = OPS.find opname
  op or raise "unkown op #{opname} (should be one of #{OPS.names.join ?,})"

  gu=Guestfs::Guestfs.new
  begin
    gu.add_drive_opts image, readonly: op.readonly
    gu.launch

    op.perform gu, opts

    gu.shutdown
  ensure
    gu.close
  end
end

if __FILE__ == $0
  main
end
</code>

<code lang="go">
package main

import (
    "flag"
    "fmt"
    "libguestfs.org/guestfs"
    "log"
    "os"
    "path/filepath"
    "strings"
)

type Op int

const (
    OpUndef Op = iota
    OpList
    OpCat
    OpWrite
)

var OpNames = map[Op]string{
    OpList:  "ls",
    OpCat:   "cat",
    OpWrite: "write",
}

const usage = `%s [options] [op] [diskimage]

op = [%s]

options:
`

func main() {
    var devname string
    var bs int
    var offset int64

    log.SetFlags(log.LstdFlags | log.Lshortfile)
    flag.Usage = func() {
        var ops []string

        for _, on := range OpNames {
            ops = append(ops, on)
        }
        fmt.Fprintf(flag.CommandLine.Output(), usage,
                filepath.Base(os.Args[0]), strings.Join(ops, "|"))
        flag.PrintDefaults()
    }
    flag.StringVar(&devname, "guestdevice", "", "guestfs device name")
    flag.IntVar(&bs, "blocksize", 1<<20, "blocksize")
    flag.Int64Var(&offset, "offset", 0, "offset")
    flag.Parse()

    var opname string
    var disk string
    switch flag.NArg() {
    case 2:
        opname = flag.Arg(0)
        disk = flag.Arg(1)
    default:
        flag.Usage()
        os.Exit(1)
    }

    op := OpUndef
    for o, on := range OpNames {
        if opname == on {
            op = o
            break
        }
    }
    if op == OpUndef {
        log.Fatalf("unkown op %s\n", opname)

    }

    g, err := guestfs.Create()
    if err != nil {
        log.Fatalf("could not create guestfs handle: %s\n", err)
    }
    defer g.Close()

    /* Attach the disk image to libguestfs. */
    isReadonly := map[Op]bool{
        OpList:  true,
        OpCat:   true,
        OpWrite: false,
    }
    optargs := guestfs.OptargsAdd_drive{
        Readonly_is_set: true,
        Readonly:        isReadonly[op],
    }
    if err := g.Add_drive(disk, &optargs); err != nil {
        log.Fatal(err)
    }

    /* Run the libguestfs back-end. */
    if err := g.Launch(); err != nil {
        log.Fatal(err)
    }

    switch op {
    case OpList:
        devices, err := g.List_devices()
        if err != nil {
            log.Fatal(err)
        }

        for _, dev := range devices {
            fmt.Println(dev)
        }
    case OpCat:
        for {
            buf, err := g.Pread_device(devname, bs, offset)
            if err != nil {
                log.Fatal(err)
            }
            if len(buf) == 0 {
                break
            }
            n, err1 := os.Stdout.Write(buf)
            if err1 != nil {
                log.Fatal(err1)
            }
            if n != len(buf) {
                log.Fatal("stdout: short write")
            }
            offset += int64(len(buf))
        }
    case OpWrite:
        buf := make([]byte, bs)
        for {
            n, err := os.Stdin.Read(buf)
            if err != nil {
                log.Fatal(err)
            }
            if n == 0 {
                break
            }

            nw, err1 := g.Pwrite_device(devname, buf[:n], offset)
            if err1 != nil {
                log.Fatal(err1)
            }
            if nw != n {
                log.Fatalf("short write at offset %d", offset)
            }
            offset += int64(n)
        }
    default:
        panic("unknown op")
    }

    if err := g.Shutdown(); err != nil {
        log.Fatal(err)
    }
}
</code>





More information about the Libguestfs mailing list