Go API speed improvements

The Go API could do with some speed improvements. This issue serves as a place to record them.

Note that some of these improvements assume that the Go API syntax will be changed as proposed in the syntax update issue #53 (closed): specifically that the API functions will become methods of a 'connection' object called conn that stores the tptoken, errstr, and potentially other things.

Speed improvements include:

  • Seven conditionals can be removed at the start of most API functions, e.g. key.SetValST():
    • Remove check of atomic variable &ydbInitialized at the start of every yottadb function. It should be checked when the 'connection' object is created (assuming the 'connection' mechanism proposed in the syntax upgrade issue).
    • Do not check in every method that its receiver is non-nil. Go will already produce a panic for this.
    • Do not check in every method that errstr is non-nil. The connection object must manage this separately.
    • In all key/buffer object methods remove the check in that key.Varnm and its buf_addr and buf_length are non-zero. The key and buffer object initializers should check this. Also remove the check for a nil key.Subary.
  • The SimpleAPI BufferT and BufferTArray mechanisms are intended to speed things up, but they actually slow things down because they force the user to invoke numerous functions to just to create one KeyT, instead of one function call. Not only does this make the API awkward to use, but the following calls are necessary:
    • Calls keyT.Alloc(), which does many (slow) mallocs instead of one:
      • BufferT.Alloc()
      • BufferTArray.Alloc(), which does one malloc for each subscript.
    • Calls keyT.BufferT.SetValStr() to fill key.BufferT with varname, which unfortunately:
      • Unnecessarily converts the Str to a []Byte array (which forces Go to do a copy)
      • Calls keyT.BufferT.SetValBAry() to finally fill key.BufferT.
    • Repeats the above for each subscript via calls to keyT.BufferTArray.SetValStr
    • So, to create a varname + 4-subscript keyT, there are 5 mallocs, 5 unnecessary string-conversion copies, 13 function calls (each with the overhead of stacking and panic checks).
    • However, by creating KeyT in a single function all of these can be reduced to 1 malloc, and 1 function call. The unnecessary string-conversion copies can be removed by passing the Str type directly to a self-made C.join function using the C._GoString_ type rather than converting the strings to []byte arrays.
    • When KeyT use is complete, a single call to KeyT.Free() will take the place of two calls to BufferT.Free() and BufferTArray.Free().
  • Calls to KeyT.SetValST() likewise require a call first to KeyT.BufferT.SetValStr() which creates an unnecessary string-conversion copy and then an unnecessary call to SetValBAry(). So 3 calls can be reduced to one without the conversion copy.
  • Calls to KeyT.ValST() likewise currently require an unnecessary subsequent call to KeyT.BufferT.GetVal(), when the result could have been returned immediately, instead.
  • Go v1.24 provides new directives that allow faster CGo calls. These directives should be added to YDBGo as they apply to our situation and should increase the speed of YDBGo.
  • Dynamic CGo pinning checks may be disabled using GODEBUG=cgocheck=0, or even enhanced. The potential benefits and risks this on YDBGo speed should be tested and documented.
  • #52 suggests investigating whether purego is a faster way to call the C API than CGo, but it is unlikely.

Other non-speed improvements:

  • Use atomic.Bool to define atomic variables at the bottom of yottadb.go. This will enforce atomic access of them and prevent accidental non-atomic access. See explanation.
Edited by Berwyn Hoyt