Hi there,

I recently managed a data parsing C library in a CGO program by for long 
time network IO.

The data flow is as follows:

user socket <----> socketpair[0] <---> socketpair[1] <----> data store
|----------Go domain--------------------|----------C domain-------------|



With such a data flow, I need to pass a file descriptor to C universe for a 
long time blocking CGO call (the CGO call returns only if the connection is 
broken), the CGO call lies in a goroutine.

Thus, my first question is: Considering the scheduling strategy of 
goroutines in Go universe, will the Go universe IO performance between 
a user socket and socketpair[0] get suffering by such a blocking CGO call?


The second question set is regarding IO performance.

My benchmark shows the pure Go syscall Write and Read is roughly 15% slower 
than C system call, and net package IO performance is roughly equal to CGO 
call performance, as shown as follows:

[image: Cgo, Go and C in system call (3).png]

Test in go 1.11; Machine: MacBook Pro 2014 Retina; 
Data: 
https://docs.google.com/spreadsheets/d/1DwtZmP8fKKr3pOQWVJrD30DSOzv4_qB5KZvsd-DQ1KA/edit?usp=sharing

So, questions are, what did I do wrong regarding these benchmarks? I'm 
currently using syscall.Write() and syscall.Read() approach in different 
write/read goroutines, is there any way to achieve performance closer to C 
system call (expect down to 5%) for such a long time network IO?

Thank you in advance.

Benchmarks:

package syscall

import (
"net"
"os"
"syscall"
"testing"
)

const message = "hello, world!"

var buffer = make([]byte, 13)

func writeAll(fd int, buf []byte) error {
for len(buf) > 0 {
n, err := syscall.Write(fd, buf)
if n < 0 {
return err
}
buf = buf[n:]
}
return nil
}

func BenchmarkReadWriteCgoCalls(b *testing.B) {
fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
for i := 0; i < b.N; i++ {
CwriteAll(fds[0], []byte(message))
Cread(fds[1], buffer)
}
}

func BenchmarkReadWriteGoCalls(b *testing.B) {
fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
for i := 0; i < b.N; i++ {
writeAll(fds[0], []byte(message))
syscall.Read(fds[1], buffer)
}
}

func BenchmarkReadWriteNetCalls(b *testing.B) {
cs, _ := socketpair()
for i := 0; i < b.N; i++ {
cs[0].Write([]byte(message))
cs[1].Read(buffer)
}
}

func socketpair() (conns [2]net.Conn, err error) {
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
if err != nil {
return
}
conns[0], err = fdToFileConn(fds[0])
if err != nil {
return
}
conns[1], err = fdToFileConn(fds[1])
if err != nil {
conns[0].Close()
return
}
return
}

func fdToFileConn(fd int) (net.Conn, error) {
f := os.NewFile(uintptr(fd), "")
defer f.Close()
return net.FileConn(f)
}

=====================================================

package syscall

/*
#include <unistd.h>
int write_all(int fd, void* buffer, size_t length) {
    while (length > 0) {
        int written = write(fd, buffer, length);
        if (written < 0)
            return -1;
        length -= written;
        buffer += written;
    }
    return length;
}
int read_call(int fd, void *buffer, size_t length) {
return read(fd, buffer, length);
}
*/
import "C"
import (
"unsafe"
)

// CwriteAll is a cgo call for write
func CwriteAll(fd int, buf []byte) error {
_, err := C.write_all(C.int(fd), unsafe.Pointer(&buf[0]), 
C.size_t(len(buf)))
return err
}

// Cread is a cgo call for read
func Cread(fd int, buf []byte) (int, error) {
ret, err := C.read_call(C.int(fd), unsafe.Pointer(&buf[0]), 
C.size_t(len(buf)))
return int(ret), err
}

C:

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>

int write_all(int fd, void* buffer, size_t length) {
    while (length > 0) {
        int written = write(fd, buffer, length);
        if (written < 0)
            return -1;
        length -= written;
        buffer += written;
    }
    return length;
}

int read_call(int fd, void *buffer, size_t length) {
return read(fd, buffer, length);
}

struct timespec timer_start(){
    struct timespec start_time;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
    return start_time;
}

long timer_end(struct timespec start_time){
    struct timespec end_time;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
    long diffInNanos = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + 
(end_time.tv_nsec - start_time.tv_nsec);
    return diffInNanos;
}

int main() {
    int i = 0;
    int N = 500000;
    int fds[2];
    char message[14] = "hello, world!\0";
    char buffer[14] = {0};

    socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
    struct timespec vartime = timer_start();
    for(i = 0; i < N; i++) {
        write_all(fds[0], message, sizeof(message));
        read_call(fds[1], buffer, 14);
    }
    long time_elapsed_nanos = timer_end(vartime);
    printf("BenchmarkReadWritePureCCalls\t%d\t%ld ns/op\n", N, 
time_elapsed_nanos/N);
}





-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to