Nir Soffer
2022-Jan-24 13:35 UTC
[Libguestfs] [PATCH libnbd] golang: examples: Add simple_copy and aio_copy examples
Show how to read entire image using the simple synchronous API and how and the high performance asynchronous API. For simplicity, the example do not use extents, do not sparsify the image, and copy the image to only to stdout. This make it easy to evaluate the Go bindings performance. The aio_copy example includes an interesting ordering queue, ensuring that asynchronous reads are written in the right order even if they completed out of order. This allows using the fast asynchronous API even when the output does not support seek, and may perform better even if the output does support seek when using rotational disks. The aio_copy example does not use AioBuffer in the normal way to avoid unwanted copy when AioPread completes. Instead, we use a Go allocated buffer, in the same way we pass a Go allocated buffer to libnbd in the synchronous API. This usage is unsafe but required for getting decent performance. Testing show that we get better performance than nbdcopy with the defaults, or the same performance if we tune nbdcopy to use the one connection and 4 requests, and drop the data using null: output. All tests use 6 GiB fully allocated zero image created with: $ dd if=/dev/zero bs=1M count=6144 of=zero-6g.raw The image is served using qemu-nbd: $ qemu-nbd -r -t -e0 -k /tmp/nbd.sock --cache=none --aio=native -f raw zero-6g.raw $ hyperfine "simple_copy/simple_copy $URL >/dev/null" \ "nbdcopy --synchronous --allocated $URL /dev/null" \ "nbdcopy --request-size $((2048*1024)) --synchronous --allocated $URL /dev/null" Benchmark 1: simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null Time (mean ? ?): 3.210 s ? 0.065 s [User: 0.275 s, System: 0.836 s] Range (min ? max): 3.117 s ? 3.298 s 10 runs Benchmark 2: nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null Time (mean ? ?): 4.469 s ? 0.019 s [User: 0.295 s, System: 0.948 s] Range (min ? max): 4.447 s ? 4.510 s 10 runs Benchmark 3: nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null Time (mean ? ?): 3.266 s ? 0.012 s [User: 0.216 s, System: 0.732 s] Range (min ? max): 3.244 s ? 3.286 s 10 runs Summary 'simple_copy/simple_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran 1.02 ? 0.02 times faster than 'nbdcopy --request-size 2097152 --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null' 1.39 ? 0.03 times faster than 'nbdcopy --synchronous --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null' $ hyperfine "aio_copy/aio_copy $URL >/dev/null" \ "nbdcopy --allocated $URL /dev/null" \ "nbdcopy --allocated $URL null:" \ "nbdcopy --connections 1 --requests 4 --allocated $URL null:" Benchmark 1: aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null Time (mean ? ?): 2.013 s ? 0.035 s [User: 0.410 s, System: 0.877 s] Range (min ? max): 1.966 s ? 2.060 s 10 runs Benchmark 2: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null Time (mean ? ?): 4.501 s ? 0.025 s [User: 0.287 s, System: 0.949 s] Range (min ? max): 4.449 s ? 4.532 s 10 runs Benchmark 3: nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null: Time (mean ? ?): 2.422 s ? 0.018 s [User: 0.520 s, System: 1.772 s] Range (min ? max): 2.404 s ? 2.470 s 10 runs Benchmark 4: nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null: Time (mean ? ?): 2.019 s ? 0.009 s [User: 0.270 s, System: 0.845 s] Range (min ? max): 2.008 s ? 2.033 s 10 runs Summary 'aio_copy/aio_copy nbd+unix:///?socket=/tmp/nbd.sock >/dev/null' ran 1.00 ? 0.02 times faster than 'nbdcopy --connections 1 --requests 4 --allocated nbd+unix:///?socket=/tmp/nbd.sock null:' 1.20 ? 0.02 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock null:' 2.24 ? 0.04 times faster than 'nbdcopy --allocated nbd+unix:///?socket=/tmp/nbd.sock /dev/null' Signed-off-by: Nir Soffer <nsoffer at redhat.com> --- golang/examples/Makefile.am | 26 ++- golang/examples/aio_copy/aio_copy.go | 191 +++++++++++++++++++++ golang/examples/aio_copy/go.mod | 4 + golang/examples/simple_copy/go.mod | 4 + golang/examples/simple_copy/simple_copy.go | 93 ++++++++++ 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 golang/examples/aio_copy/aio_copy.go create mode 100644 golang/examples/aio_copy/go.mod create mode 100644 golang/examples/simple_copy/go.mod create mode 100644 golang/examples/simple_copy/simple_copy.go diff --git a/golang/examples/Makefile.am b/golang/examples/Makefile.am index bfeee245..90f9fbf8 100644 --- a/golang/examples/Makefile.am +++ b/golang/examples/Makefile.am @@ -16,27 +16,49 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA include $(top_srcdir)/subdir-rules.mk EXTRA_DIST = \ LICENSE-FOR-EXAMPLES \ get_size/go.mod \ get_size/get_size.go \ read_first_sector/go.mod \ read_first_sector/read_first_sector.go \ + simple_copy/go.mod \ + simple_copy/simple_copy.go \ + aio_copy/go.mod \ + aio_copy/aio_copy.go \ $(NULL) if HAVE_GOLANG -noinst_SCRIPTS = get_size/get_size read_first_sector/read_first_sector +noinst_SCRIPTS = \ + get_size/get_size \ + read_first_sector/read_first_sector \ + simple_copy/simple_copy \ + aio_copy/aio_copy \ + $(NULL) get_size/get_size: get_size/get_size.go cd get_size && \ $(abs_top_builddir)/run go build -o get_size read_first_sector/read_first_sector: read_first_sector/read_first_sector.go cd read_first_sector && \ $(abs_top_builddir)/run go build -o read_first_sector +simple_copy/simple_copy: simple_copy/simple_copy.go + cd simple_copy && \ + $(abs_top_builddir)/run go build -o simple_copy + +aio_copy/aio_copy: aio_copy/aio_copy.go + cd aio_copy && \ + $(abs_top_builddir)/run go build -o aio_copy + endif HAVE_GOLANG -CLEANFILES += get_size/get_size read_first_sector/read_first_sector +CLEANFILES += \ + get_size/get_size \ + read_first_sector/read_first_sector \ + simple_copy/simple_copy \ + aio_copy/aio_copy \ + $(NULL) diff --git a/golang/examples/aio_copy/aio_copy.go b/golang/examples/aio_copy/aio_copy.go new file mode 100644 index 00000000..d2f0d33b --- /dev/null +++ b/golang/examples/aio_copy/aio_copy.go @@ -0,0 +1,191 @@ +/* libnbd example + * Copyright (C) 2013-2022 Red Hat Inc. + * Examples are under a permissive BSD-like license. See also + * golang/examples/LICENSE-For-EXAMPLES + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +// Copy image from NBD URI to stdout. +// +// Example: +// +// ./aio_copy nbd+unix:///?socket=/tmp.nbd >/dev/null +// +package main + +import ( + "container/list" + "flag" + "os" + "sync" + "unsafe" + + "libguestfs.org/libnbd" +) + +var ( + // These options give best performance with fast NVMe drive. + requestSize = flag.Uint("request-size", 256*1024, "maximum request size in bytes") + requests = flag.Uint("requests", 4, "maximum number of requests in flight") + + h *libnbd.Libnbd + + // Keeping commands in a queue ensures commands are written in the right + // order, even if they complete out of order. This allows parallel reads + // with non-seekable output. + queue list.List + + // Buffer pool allocating buffers as needed and reusing them. + bufPool = sync.Pool{ + New: func() interface{} { + return make([]byte, *requestSize) + }, + } +) + +// command keeps state of single AioPread call while the read is handled by +// libnbd, until the command reach the front of the queue and can be writen to +// the output. +type command struct { + buf []byte + length uint + ready bool +} + +func main() { + flag.Parse() + + var err error + + h, err = libnbd.Create() + if err != nil { + panic(err) + } + defer h.Close() + + err = h.ConnectUri(flag.Arg(0)) + if err != nil { + panic(err) + } + + size, err := h.GetSize() + if err != nil { + panic(err) + } + + var offset uint64 + + for offset < size || queue.Len() > 0 { + + for offset < size && inflightRequests() < *requests { + length := *requestSize + if size-offset < uint64(length) { + length = uint(size - offset) + } + startRead(offset, length) + offset += uint64(length) + } + + waitForCompletion() + + for readReady() { + finishRead() + } + } +} + +func inflightRequests() uint { + n, err := h.AioInFlight() + if err != nil { + panic(err) + } + return n +} + +func waitForCompletion() { + start := inflightRequests() + + for { + _, err := h.Poll(-1) + if err != nil { + panic(err) + } + + if inflightRequests() < start { + break // A read completed. + } + } +} + +func startRead(offset uint64, length uint) { + buf := bufPool.Get().([]byte) + + // Keep buffer in command so we can put it back into the pool when the + // command completes. + cmd := &command{buf: buf, length: length} + + // Create aio buffer from pool buffer to avoid unneeded allocation for + // every read, and unneeded copy when completing the read. + abuf := libnbd.AioBuffer{P: unsafe.Pointer(&buf[0]), Size: length} + + args := &libnbd.AioPreadOptargs{ + CompletionCallbackSet: true, + CompletionCallback: func(error *int) int { + cmd.ready = true + return 1 + }, + } + + _, err := h.AioPread(abuf, offset, args) + if err != nil { + panic(err) + } + + queue.PushBack(cmd) +} + +func readReady() bool { + return queue.Len() > 0 && queue.Front().Value.(*command).ready +} + +func finishRead() { + e := queue.Front() + queue.Remove(e) + + cmd := e.Value.(*command) + b := cmd.buf[:cmd.length] + + _, err := os.Stdout.Write(b) + if err != nil { + panic(err) + } + + bufPool.Put(cmd.buf) +} diff --git a/golang/examples/aio_copy/go.mod b/golang/examples/aio_copy/go.mod new file mode 100644 index 00000000..074fabf7 --- /dev/null +++ b/golang/examples/aio_copy/go.mod @@ -0,0 +1,4 @@ +module main + +replace libguestfs.org/libnbd => ../../ +require libguestfs.org/libnbd v1.11.5 diff --git a/golang/examples/simple_copy/go.mod b/golang/examples/simple_copy/go.mod new file mode 100644 index 00000000..074fabf7 --- /dev/null +++ b/golang/examples/simple_copy/go.mod @@ -0,0 +1,4 @@ +module main + +replace libguestfs.org/libnbd => ../../ +require libguestfs.org/libnbd v1.11.5 diff --git a/golang/examples/simple_copy/simple_copy.go b/golang/examples/simple_copy/simple_copy.go new file mode 100644 index 00000000..e8fa1f76 --- /dev/null +++ b/golang/examples/simple_copy/simple_copy.go @@ -0,0 +1,93 @@ +/* libnbd example + * Copyright (C) 2013-2022 Red Hat Inc. + * Examples are under a permissive BSD-like license. See also + * golang/examples/LICENSE-For-EXAMPLES + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +// Copy image from NBD URI to stdout. +// +// Example: +// +// ./simple_copy nbd+unix:///?socket=/tmp.nbd >/dev/null +// +package main + +import ( + "flag" + "os" + + "libguestfs.org/libnbd" +) + +var ( + requestSize = flag.Uint("buffer-size", 2048*1024, "maximum request size in bytes") +) + +func main() { + flag.Parse() + + h, err := libnbd.Create() + if err != nil { + panic(err) + } + defer h.Close() + + err = h.ConnectUri(flag.Arg(0)) + if err != nil { + panic(err) + } + + size, err := h.GetSize() + if err != nil { + panic(err) + } + + buf := make([]byte, *requestSize) + var offset uint64 + + for offset < size { + if size-offset < uint64(len(buf)) { + buf = buf[:offset-size] + } + + err = h.Pread(buf, offset, nil) + if err != nil { + panic(err) + } + + _, err := os.Stdout.Write(buf) + if err != nil { + panic(err) + } + + offset += uint64(len(buf)) + } +} -- 2.34.1
Richard W.M. Jones
2022-Jan-24 16:13 UTC
[Libguestfs] [PATCH libnbd] golang: examples: Add simple_copy and aio_copy examples
On Mon, Jan 24, 2022 at 03:35:57PM +0200, Nir Soffer wrote:> Show how to read entire image using the simple synchronous API and how > and the high performance asynchronous API.[...] Looks fine, thanks. ACK Rich. -- 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/