Skip to content

Commit

Permalink
Revert "feat: support go-side object pool"
Browse files Browse the repository at this point in the history
This reverts commit a10d07c.
  • Loading branch information
ihciah committed Dec 9, 2024
1 parent 70a4cae commit 64ea306
Show file tree
Hide file tree
Showing 6 changed files with 20 additions and 294 deletions.
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,21 @@ For detailed example, please checkout [the example projects](./examples).
> Detailed design details can be found in this article: [Design and Implementation of a Rust-Go FFI Framework](https://en.ihcblog.com/rust2go/).
### Why Fast?

In order to achieve the ultimate performance, this project is not based on communication, but on FFI to pass specially encoded data. In order to reduce memory operations to a minimum, data that satisfies a specific memory layout is passed directly by reference rather than copied.

For example, `Vec<u8>` and `String` is represented as a pointer and a length. However, structs like `Vec<String>` or `Vec<Vec<u8>>` require intermediate representation. In order to reduce the number of memory allocations to one, I use a precomputed size buffer to store these intermediate structures.

### Memory Safety

On the Golang side, the data it receives is referenced from Rust. The Rust side will do its best to ensure the validity of this data during the call. So the Golang side can implement the handler arbitrarily, but manually deep copy when leaking data outside the function life cycle.

On the Rust side, it is needed to ensure that the slot pointer of the callback ffi operation, and the user parameters are valid when the future drops. This is archieved by implementing an atomic slot structure and providing a `[drop_safe]` attribute to require user passing parameters with ownership.

## Toolchain Requirements

- Golang: >=1.18
- For >=1.18 && < 1.20: generate golang code with `--go118`
- For >=1.20: generate golang code normally
- Rust: >=1.75 if you want to use async

With my experience, starting from Golang 1.21 there is a significant performance improvement in CGO. So I recommend using Golang 1.21 or later.

## Milestones
### Init Version
- [x] IDL(in rust) parse
Expand All @@ -63,14 +58,9 @@ With my experience, starting from Golang 1.21 there is a significant performance

### Performance Optimization
- [x] Shared memory based implementation
- [x] Go-side reference passing support saving stack grow cost for big size data
- [x] Go-side object pool support saving allocation cost for complicated data types

### Extended Features
- [ ] Support calling rust from golang

### Exploratory Features
- [ ] Support sense peer memory layout at boot time or compile time and access fields directly

## Credit
This project is inspired by [fcplug](https://github.com/andeya/fcplug).
37 changes: 0 additions & 37 deletions docs/cli-args.md

This file was deleted.

1 change: 0 additions & 1 deletion examples/example-monoio/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ fn main() {
src: "./src/user.rs".into(),
dst: "./go/gen.go".into(),
go118: true,
recycle: true,
..Default::default()
})
.build();
Expand Down
75 changes: 2 additions & 73 deletions examples/example-monoio/go/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ import "C"
import (
"reflect"
"runtime"
"sync"
"unsafe"
)

Expand Down Expand Up @@ -95,7 +94,6 @@ func CDemoCall_demo_check(req C.DemoComplicatedRequestRef, slot *C.void, cb *C.v
C.DemoCall_demo_check_cb(unsafe.Pointer(cb), resp_ref, unsafe.Pointer(slot))
runtime.KeepAlive(resp)
runtime.KeepAlive(buffer)
recDemoComplicatedRequest(&_new_req, _GLOBAL_POOL)
}

//export CDemoCall_demo_check_async
Expand All @@ -107,7 +105,6 @@ func CDemoCall_demo_check_async(req C.DemoComplicatedRequestRef, slot *C.void, c
C.DemoCall_demo_check_async_cb(unsafe.Pointer(cb), resp_ref, unsafe.Pointer(slot))
runtime.KeepAlive(resp)
runtime.KeepAlive(buffer)
recDemoComplicatedRequest(&_new_req, _GLOBAL_POOL)
}()
}

Expand All @@ -120,7 +117,6 @@ func CDemoCall_demo_check_async_safe(req C.DemoComplicatedRequestRef, slot *C.vo
C.DemoCall_demo_check_async_safe_cb(unsafe.Pointer(cb), resp_ref, unsafe.Pointer(slot))
runtime.KeepAlive(resp)
runtime.KeepAlive(buffer)
recDemoComplicatedRequest(&_new_req, _GLOBAL_POOL)
}()
}

Expand Down Expand Up @@ -168,36 +164,17 @@ func refString(s *string, _ *[]byte) C.StringRef {
}
}

func cntString(_ *string, _ *uint) [0]C.StringRef { return [0]C.StringRef{} }
func new_list_mapper[T1, T2 any](f func(T1) T2) func(C.ListRef) []T2 {
return func(x C.ListRef) []T2 {
input := unsafe.Slice((*T1)(unsafe.Pointer(x.ptr)), x.len)

// try to get from _GLOBAL_POOL
elem := _GLOBAL_POOL.Get(reflect.TypeOf([]T2{}))
var output []T2
if elem != nil {
output = elem.([]T2)
if cap(output) < len(input) {
// if the capacity is not enough, create a new one
// old one will not be used anymore
output = make([]T2, len(input))
} else {
// if the capacity is enough, truncate the slice
output = output[:len(input)]
}
} else {
// if not found in _GLOBAL_POOL, create a new one
output = make([]T2, len(input))
}

output := make([]T2, len(input))
for i, v := range input {
output[i] = f(v)
}
return output
}
}

func cntString(_ *string, _ *uint) [0]C.StringRef { return [0]C.StringRef{} }
func new_list_mapper_primitive[T1, T2 any](_ func(T1) T2) func(C.ListRef) []T2 {
return func(x C.ListRef) []T2 {
return unsafe.Slice((*T2)(unsafe.Pointer(x.ptr)), x.len)
Expand Down Expand Up @@ -324,47 +301,6 @@ func refC_intptr_t(p *int, _ *[]byte) C.intptr_t { return C.intptr_t(*p) }
func refC_float(p *float32, _ *[]byte) C.float { return C.float(*p) }
func refC_double(p *float64, _ *[]byte) C.double { return C.double(*p) }

type _GenericPool struct {
mapping map[reflect.Type]*sync.Pool
mu sync.RWMutex
}

func (p *_GenericPool) Get(typ reflect.Type) interface{} {
p.mu.RLock()
pool, ok := p.mapping[typ]
p.mu.RUnlock()
if !ok {
return nil
}
return pool.Get()
}

// x: []T
func (p *_GenericPool) Put(x interface{}) {
// check if x is []T
typ := reflect.TypeOf(x)
if typ.Kind() != reflect.Slice {
return
}

p.mu.RLock()
pool, ok := p.mapping[typ]
p.mu.RUnlock()
if !ok {
pool = &sync.Pool{}
p.mu.Lock()
if _, ok := p.mapping[typ]; !ok {
p.mapping[typ] = pool
}
p.mu.Unlock()
}
pool.Put(x)
}

var _GLOBAL_POOL = &_GenericPool{
mapping: make(map[reflect.Type]*sync.Pool),
}

type DemoUser struct {
name string
age uint8
Expand All @@ -376,8 +312,6 @@ func newDemoUser(p C.DemoUserRef) DemoUser {
age: newC_uint8_t(p.age),
}
}
func recDemoUser(s *DemoUser, p *_GenericPool) {
}
func cntDemoUser(s *DemoUser, cnt *uint) [0]C.DemoUserRef {
return [0]C.DemoUserRef{}
}
Expand All @@ -399,9 +333,6 @@ func newDemoComplicatedRequest(p C.DemoComplicatedRequestRef) DemoComplicatedReq
balabala: new_list_mapper_primitive(newC_uint8_t)(p.balabala),
}
}
func recDemoComplicatedRequest(s *DemoComplicatedRequest, p *_GenericPool) {
p.Put(s.users)
}
func cntDemoComplicatedRequest(s *DemoComplicatedRequest, cnt *uint) [0]C.DemoComplicatedRequestRef {
cnt_list_mapper(cntDemoUser)(&s.users, cnt)
return [0]C.DemoComplicatedRequestRef{}
Expand All @@ -422,8 +353,6 @@ func newDemoResponse(p C.DemoResponseRef) DemoResponse {
pass: newC_bool(p.pass),
}
}
func recDemoResponse(s *DemoResponse, p *_GenericPool) {
}
func cntDemoResponse(s *DemoResponse, cnt *uint) [0]C.DemoResponseRef {
return [0]C.DemoResponseRef{}
}
Expand Down
13 changes: 4 additions & 9 deletions rust2go-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ pub struct Args {
/// Disable auto format go file
#[arg(long, default_value = "false")]
pub no_fmt: bool,

/// Enable object pool
#[arg(long, default_value = "false")]
pub recycle: bool,
}

pub fn generate(args: &Args) {
Expand Down Expand Up @@ -81,19 +77,18 @@ pub fn generate(args: &Args) {
};
let import_cgo = if use_cgo { "\"runtime\"\n" } else { "" };

let import_reflect = if args.go118 { "\"reflect\"\n" } else { "" };
let import_sync = if args.recycle { "\"sync\"\n" } else { "" };
let import_118 = if args.go118 { "\"reflect\"\n" } else { "" };
let mut go_content = format!(
"package main\n\n/*\n{output}*/\nimport \"C\"\nimport (\n\"unsafe\"\n{import_cgo}{import_sync}{import_reflect}{import_shm})\n"
"package main\n\n/*\n{output}*/\nimport \"C\"\nimport (\n\"unsafe\"\n{import_cgo}{import_118}{import_shm})\n"
);
let levels = raw_file.convert_structs_levels().unwrap();
traits.iter().for_each(|t| {
go_content.push_str(&t.generate_go_interface());
go_content.push_str(&t.generate_go_exports(&levels, args.recycle));
go_content.push_str(&t.generate_go_exports(&levels));
});
go_content.push_str(
&raw_file
.convert_structs_to_go(&levels, args.go118, args.recycle)
.convert_structs_to_go(&levels, args.go118)
.expect("Unable to generate go structs"),
);
if use_shm {
Expand Down
Loading

0 comments on commit 64ea306

Please sign in to comment.