title: Go syscall v.s. C system call
body: <p>Go, and C both involve system calls directly (Technically, C will call a stub).</p>
  
  <blockquote>
    <p>Technically, write is both a system call and a C function (at least on many systems). However, the C function is just a stub which invokes the system call. Go does not call this stub, it invokes the system call directly, which means that C is not involved here</p>
    
    <p>From <a href="https://stackoverflow.com/questions/52081841/differences-between-c-write-call-and-go-syscall-write">Differences between C write call and Go syscall.Write</a></p>
  </blockquote>
  
  <p>My benchmark shows, pure C system call is 15.82% faster than pure Go system call in the latest release (go1.11).</p>
  
  <p>What did I miss? What could be a reason and how to optimize them?</p>
  
  <p>Benchmarks:</p>
  
  <p>Go:</p>
  
  <pre><code>package main_test
  
  import (
      "syscall"
      "testing"
  )
  
  func writeAll(fd int, buf []byte) error {
      for len(buf) &gt; 0 {
          n, err := syscall.Write(fd, buf)
          if n &lt; 0 {
              return err
          }
          buf = buf[n:]
      }
      return nil
  }
  
  func BenchmarkReadWriteGoCalls(b *testing.B) {
      fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
      message := "hello, world!"
      buffer := make([]byte, 13)
      for i := 0; i &lt; b.N; i++ {
          writeAll(fds[0], []byte(message))
          syscall.Read(fds[1], buffer)
      }
  }
  </code></pre>
  
  <p>C:</p>
  
  <pre><code>#include &lt;time.h&gt;
  #include &lt;stdio.h&gt;
  #include &lt;unistd.h&gt;
  #include &lt;sys/socket.h&gt;
  
  int write_all(int fd, void* buffer, size_t length) {
      while (length &gt; 0) {
          int written = write(fd, buffer, length);
          if (written &lt; 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, &amp;start_time);
      return start_time;
  }
  
  long timer_end(struct timespec start_time){
      struct timespec end_time;
      clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &amp;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 &lt; 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%.2ld ns/op\n", N, time_elapsed_nanos/N);
  }
  </code></pre>
  
  <p>340 different running, each C running contains 500000 executions, and each Go running contains b.N executions (mostly 500000, few times executed in 1000000 times):</p>
  
  <p><a href="https://i.stack.imgur.com/MVOvW.png" rel="noreferrer"><img src="https://i.stack.imgur.com/MVOvW.png" alt="enter image description here"></a></p>
  
  <p>T-Test for 2 Independent Means: The t-value is -22.45426. The p-value is &lt; .00001. The result is significant at p &lt; .05.</p>
  
  <p><a href="https://i.stack.imgur.com/fnCFT.png" rel="noreferrer"><img src="https://i.stack.imgur.com/fnCFT.png" alt="enter image description here"></a></p>
  
  <p>T-Test Calculator for 2 Dependent Means: The value of t is 15.902782. The value of p is &lt; 0.00001. The result is significant at p ≤ 0.05.</p>
  
  <p><a href="https://i.stack.imgur.com/XTZmc.png" rel="noreferrer"><img src="https://i.stack.imgur.com/XTZmc.png" alt="enter image description here"></a></p>
  
  <hr>
  
  <p>Update: I managed the proposal in the answers and wrote another benchmark, it shows the proposed approach significantly drops the performance of massive I/O calls, its performance close to CGO calls.</p>
  
  <p>Benchmark:</p>
  
  <pre><code>func BenchmarkReadWriteNetCalls(b *testing.B) {
      cs, _ := socketpair()
      message := "hello, world!"
      buffer := make([]byte, 13)
      for i := 0; i &lt; 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)
  }
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/IcdWm.png" rel="noreferrer"><img src="https://i.stack.imgur.com/IcdWm.png" alt="enter image description here"></a></p>
  
  <p>The above figure shows, 100 different running, each C running contains 500000 executions, and each Go running contains b.N executions (mostly 500000, few times executed in 1000000 times)</p>