Skip to content

Commit

Permalink
bindings: add crate and Go module with Service inter-op mechanism
Browse files Browse the repository at this point in the history
A Service looks like a bidirectional channel, but is one which is
synchronously polled to send and receive framed message code and data
payloads. It's intended as a fundamental building block for building
bidirectional in-process streaming services that interoperate between
Go and Rust -- an interface which is a heck of a lot faster than gRPC.

It achieves zero-copy semantics by pushing Go []byte buffers down
into CGO invocations, and surfacing Rust-owned []byte arenas and
Frame payloads. There are some rules callers need to adhere to,
such as not scribbling "sent" memory until Poll() is called, and
not referencing Rust-owned memory returned by Poll() after _another_
Poll() call is made.

It amortizes the CGO overhead by vectorizing dispatch. On the Rust
side, some common functions are provided to monomorphize appropriate
bindings for an implementation of a Service trait.

Lies, damn lies, and benchmarks:

This shows a variety of stride patterns (number of sent messages
per Poll() call), some of which are deliberately worst case.

It's comparing the actual "upper case" service with a no-op service
that has the same setup & moving parts, but avoids the actual CGO
invocation itself.

Comparative benchmarks with equivalent "naive" CGO and "pure" Go
Services are included. These are also zero-copy / return owned
memory.

    $ go test ./go/bindings/ -bench='.' -benchtime 3s
    goos: linux
    goarch: amd64
    pkg: github.com/estuary/flow/go/bindings
    BenchmarkUpperService/cgo-1-24          43781984                79.1 ns/op
    BenchmarkUpperService/noop-1-24         270172464               13.3 ns/op
    BenchmarkUpperService/cgo-3-24          48714016                72.2 ns/op
    BenchmarkUpperService/noop-3-24         368625391                9.70 ns/op
    BenchmarkUpperService/cgo-4-24          91550871                36.8 ns/op
    BenchmarkUpperService/noop-4-24         363729646               10.1 ns/op
    BenchmarkUpperService/cgo-11-24         77817141                45.2 ns/op
    BenchmarkUpperService/noop-11-24        369495175               9.76 ns/op
    BenchmarkUpperService/cgo-15-24         83527456                42.2 ns/op
    BenchmarkUpperService/noop-15-24        382336641                9.84 ns/op
    BenchmarkUpperService/cgo-17-24         135539218               27.0 ns/op
    BenchmarkUpperService/noop-17-24        423105061                8.66 ns/op
    BenchmarkUpperService/cgo-31-24         100000000               32.6 ns/op
    BenchmarkUpperService/noop-31-24        406740273                9.10 ns/op
    BenchmarkUpperService/cgo-32-24         151226794               23.7 ns/op
    BenchmarkUpperService/noop-32-24        423966495                8.67 ns/op
    BenchmarkUpperService/cgo-63-24         129356660               27.7 ns/op
    BenchmarkUpperService/noop-63-24        426944920                8.82 ns/op
    BenchmarkUpperService/cgo-137-24        150434529               23.9 ns/op
    BenchmarkUpperService/noop-137-24               441497529                8.39 ns/op
    BenchmarkUpperService/cgo-426-24                155104615               23.3 ns/op
    BenchmarkUpperService/noop-426-24               452368488                8.30 ns/op
    BenchmarkUpperServiceNaive-24                   34605169               104 ns/op
    BenchmarkUpperServiceGo-24                      278482276               12.8 ns/op
    PASS
    ok      github.com/estuary/flow/go/bindings     111.130s
  • Loading branch information
jgraettinger committed Dec 11, 2020
1 parent 4a30be1 commit 0fc0ff8
Show file tree
Hide file tree
Showing 11 changed files with 1,066 additions and 0 deletions.
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions crates/bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "bindings"
version = "0.0.0"
authors = ["Estuary Technologies, Inc"]
edition = "2018"

[lib]
crate_type = ["staticlib"]

[dependencies]

[build-dependencies]
cbindgen = "*"
18 changes: 18 additions & 0 deletions crates/bindings/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extern crate cbindgen;

use std::env;
use std::path::PathBuf;

fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

let config = cbindgen::Config::from_file(PathBuf::from(&crate_dir).join("cbindgen.toml"))
.expect("failed to parse cbindgen config");

cbindgen::Builder::new()
.with_crate(crate_dir)
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file("flow_bindings.h");
}
1 change: 1 addition & 0 deletions crates/bindings/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language = "C"
97 changes: 97 additions & 0 deletions crates/bindings/flow_bindings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

/**
* Opaque pointer for a Service instance in the ABI.
*/
typedef struct {
uint8_t _private[0];
} ServiceImpl;

/**
* Output frame produced by a Service.
*/
typedef struct {
/**
* Service-defined response code.
*/
uint32_t code;
/**
* Begin data offset into the Channel arena.
*/
uint32_t begin;
/**
* End data offset into the Channel arena.
*/
uint32_t end;
} Out;

/**
* Channel is shared between CGO and Rust, and holds details
* about the language interconnect.
*/
typedef struct {
ServiceImpl *svc_impl;
uint8_t *arena_ptr;
uintptr_t arena_len;
uintptr_t arena_cap;
Out *out_ptr;
uintptr_t out_len;
uintptr_t out_cap;
uint8_t *err_ptr;
uintptr_t err_len;
uintptr_t err_cap;
} Channel;

/**
* Input frame produced from CGO, which is a single service invocation.
* 16 bytes, or 1/4 of a typical cache line.
*/
typedef struct {
const uint8_t *data_ptr;
uint32_t data_len;
uint32_t code;
} In1;

/**
* Four invocations, composed into one struct.
* 64 bytes, or one typical cache line.
*/
typedef struct {
In1 in0;
In1 in1;
In1 in2;
In1 in3;
} In4;

/**
* Sixteen invocations, composed into one struct.
* 256 bytes, or four typical cache lines.
*/
typedef struct {
In4 in0;
In4 in1;
In4 in2;
In4 in3;
} In16;

Channel *upper_case_create(void);

void upper_case_invoke1(Channel *ch, In1 i);

void upper_case_invoke4(Channel *ch, In4 i);

void upper_case_invoke16(Channel *ch, In16 i);

void upper_case_drop(Channel *ch);

ServiceImpl *create_upper_case_naive(void);

uint32_t upper_case_naive(ServiceImpl *svc,
uint32_t _code,
const uint8_t *in_ptr,
uint32_t in_len,
const uint8_t **out_ptr,
uint32_t *out_len);
2 changes: 2 additions & 0 deletions crates/bindings/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod service;
mod upper_case;
Loading

0 comments on commit 0fc0ff8

Please sign in to comment.