[Libguestfs] Poor write performance with golang binding

Richard W.M. Jones rjones at redhat.com
Wed Feb 19 18:11:33 UTC 2020


On Wed, Feb 19, 2020 at 03:00:11PM +0100, Csaba Henk wrote:
> 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?

TBH I've no idea.  The bindings are meant to be very thin wrappers
around the C API, so I can't imagine that they should cause
performance penalties as large as you have observed.

Can you enable tracing in both (g.set_trace (true)) and make sure that
the operations being done are the same for both languages?

Rich.

> 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>
> 
> 
> _______________________________________________
> Libguestfs mailing list
> Libguestfs at redhat.com
> https://www.redhat.com/mailman/listinfo/libguestfs

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-df lists disk usage of guests without needing to install any
software inside the virtual machine.  Supports Linux and Windows.
http://people.redhat.com/~rjones/virt-df/




More information about the Libguestfs mailing list