Site icon Developer's tips

String transfer from a function in GO to a code on C without memory allocation (Part 1)

perfomance optimizatopm: string transfer from GO to C w/o memory allocationCGO documentation illuminates string transfer issue rather poorly. They only mention that C.CString() function should be used in order to convert a string object into a pointer to a buffer with a zero-terminated string – char*, which is coherent for the C code. This is great; however, a memory block is being allocated during this procedure (malloc), which has to be removed by a call (free) after one finishes using it. This seems rather wasteful if you are going to call the function in C very often. Is it possible to avoid allocation /memory release operations? The string transfer could work much faster without the memory allocation.


If we check, what kind of code is generated for GO functions with a string type parameter, which are exported for access from C,

//export MyGoCallback
func MyGoCallback(param1 string) {
…
}

we will be able to see that param1 type in the generated function is GoString. This is a structure that is declared in the included _cgo_export.h file, which is generated during the construction as

typedef struct { const char *p; GoInt n; } GoString;

If a string is a GoString structure, why can’t we just transfer its address to C instead of allocating memory, copying a string into the allocated buffer and releasing the memory after using it?

The attached example implements the string transfer scheme without memory allocation. At the same time string object address is being transmitted into the function in C as unsafe.Pointer and then we receive direct access to the string data after casting the type to a pointer in GoString. It is necessary to note that the string in utf8 encoding and does not terminate with a zero.

Implementation

Module code in GO (gostring.go):

package main
// extern void Test(void* p);
import "C"
import "unsafe"

func main() {  
  str := "test test"
  C.Test(unsafe.Pointer(&str))
}

Function code Test() in C (test.c)::

#include <_cgo_export.h>
#include <string.h>
#include <stdio.h>
void Test(void* p)
{
  GoString* gs = (GoString*)p;
  char buf[64];
  int n = gs->n < sizeof(buf) - 1 ? gs->n : sizeof(buf) - 1;
  strncpy(buf, gs->p, n);
  buf[n] = '\0';
  printf("received: %s\n", buf);
}

As a result of the example build by a command

go build .

and after starting the application we can see the following on the screen:

received: test test

Test function cuts the string if its length exceeds 63 characters. In a real situation it is necessary to use a larger buffer and use dynamically allocated buffer if its size is not sufficient. You can find a full implementation of functions for using received GoString pointer in the attached files gostr2c.c and gostr2c.h. If you use macros from gostr2c.h, the Test function can be rewritten as:

void Test(void* p)
{
  GO2C(p);
  printf("received: %s\n", pp);
  GO2C_FREE(p);
}

Conclusion

Why is this method not documented in GO? Will the proposed scheme always work?
To be continued in Part 2

gostring.zip

Exit mobile version