This project is meant to be used with the following RPC libraries:
- Go:https://github.com/gufeijun/rpch-go
- C:https://github.com/gufeijun/rpch-c
- Node:https://github.com/gufeijun/rpch-node
hgen is the compiler for the Interface Definition Language (IDL) of a cross-language RPC framework, similar in concept to gRPC.
Instead of using a third-party IDL like Protocol Buffers, hgen uses its own simple, custom-designed syntax. The purpose of this compiler is to parse our custom syntax and generate stub code for target languages, which then interfaces with the RPC frameworks listed above. Currently, it supports mutual RPC calls between Go, C, and Node.js.
The compiler's frontend is a hand-written recursive descent parser for an LL(1) grammar, and the backend uses Go's template engine for code generation. The grammar can be found in bnf.txt.
For details on the RPC framework's protocol design, please see the rpch-go
Create a math.gfj file:
service Math{
// Add two numbers
uint32 Add(uint32,uint32) // Multiple input parameters of basic types are allowed
// Subtract two numbers
int32 Sub(int32,int32)
// Multiply two numbers
int32 Multiply(TwoNum)
// Divide two numbers
Quotient Divide(uint64,uint64)
void NoReturnFunc(void) // Services can have no return value or request parameters
}
message Quotient{
uint64 Quo // Quotient
uint64 Rem // Remainder
}
message TwoNum{
int32 A
int32 B
}
// A message can contain other messages as members
message ComplexStruct {
TwoNum Nums
Quotient noConcern
}There are only two keywords:
- message defines composite structures.
- service defines a collection of services.
Basic Types:int8、uint8、int16、uint16、int32、uint32、int64、uint64、float32、float64、string、void、stream、istream、ostream。stream is a streaming type, currently only supported by rpch-go. See rpch-go for more details。
In addition to the three language implementations of rpch, we introduced Go's standard RPC library and the gRPC framework for a comparative analysis.
The test case is a simple Add service (adding two numbers). Our framework can implement this service in two ways via the IDL:
Implementation 1
service Math{
int32 Add(int32,int32)
}No JSON serialization is needed; data is transferred directly in little-endian format, resulting in higher efficiency.
Implementation 2
service Math{
Response Add(Request)
}
message Response{
int32 Result
}
message Request{
int32 A
int32 B
}This requires serialization to transfer composite message structures, leading to lower efficiency but better representing typical application scenarios. Since gRPC can only use this method, this implementation is more representative of real-world business cases. Therefore, in addition to the first method, we also benchmarked our framework with this second implementation.
We measured throughput and latency with a varying number of concurrent clients. When the number of concurrent clients was 500 or less, each client sent 10,000 RPC requests. When it exceeded 500, each client sent 1,000 requests to save time.
The tests used a synchronous request-response model, where a new request is not sent until the response to the previous one is received. All tests were run multiple times, and the average values were taken.
rpch-c represents the framework implemented in C. rpch-c(json) represents the C implementation using the second method (JSON serialization).
Throughput (requests/sec) under different numbers of concurrent clients:
| rpch-c | rpch-c(json) | rpch-go | rpch-go(json) | rpch-node | stdrpc | grpc-go | |
|---|---|---|---|---|---|---|---|
| 1 | 9980 | 9320 | 11792 | 9355 | 1039 | 6993 | 3686 |
| 10 | 74239 | 72727 | 91491 | 78740 | 8702 | 71994 | 29420 |
| 100 | 178922 | 144928 | 148456 | 115380 | 12649 | 110803 | 37926 |
| 200 | 167940 | 130387 | 139860 | 111988 | 15759 | 107718 | 39118 |
| 300 | 158587 | 125697 | 134735 | 109601 | 17549 | 104102 | 39425 |
| 500 | 150299 | 119927 | 131144 | 107794 | 17370 | 101161 | 39538 |
| 1000 | 151604 | 120460 | 130371 | 111089 | 16420 | 102162 | 39757 |
| 2000 | 145041 | 1209575 | 129514 | 109922 | 15709 | 99789 | 37434 |
| 5000 | 143661 | 113254 | 123066 | 102303 | 14772 | 90283 | 36342 |
Latency per request (ms) under different numbers of concurrent clients:
| rpch-c | rpch-c(json) | rpch-go | rpch-go(json) | rpch-node | stdrpc | grpc-go | |
|---|---|---|---|---|---|---|---|
| 1 | 0.1002 | 0.1073 | 0.0848 | 0.1069 | 0.9628 | 0.143 | 0.2713 |
| 10 | 0.1347 | 0.1375 | 0.1093 | 0.127 | 1.1491 | 0.1389 | 0.3399 |
| 100 | 0.5589 | 0.69 | 0.6736 | 0.8667 | 7.9056 | 0.9025 | 2.6367 |
| 200 | 1.1909 | 1.5339 | 1.43 | 1.7859 | 12.691 | 1.8567 | 5.1127 |
| 300 | 1.8917 | 2.3867 | 2.2266 | 2.7372 | 17.0947 | 2.8818 | 7.6093 |
| 500 | 3.3267 | 4.1692 | 3.8126 | 4.6385 | 28.785 | 4.9426 | 12.6462 |
| 1000 | 6.5961 | 8.3015 | 7.6704 | 9.0018 | 60.903 | 9.7884 | 25.1527 |
| 2000 | 13.7892 | 16.5323 | 15.4424 | 18.1948 | 127.3164 | 20.0422 | 53.427 |
| 5000 | 34.8042 | 44.1485 | 40.6287 | 48.8744 | 338.4807 | 55.3812 | 137.5815 |
(The horizontal axis represents the number of concurrent clients, and the vertical axis represents throughput in RPC requests per second. Higher is better.)
(The horizontal axis represents the number of concurrent clients, and the vertical axis represents the latency per request in milliseconds. Lower is better.)
Based on the benchmark results, we can draw the following conclusions in order of performance: rpch-c > rpch-go > rpch-c(json) > rpch-go(json) > stdrpc >> grpc-go > rpch-node.
Overall Performance:
- The custom rpch framework developed in C (rpch-c) achieves the highest performance, followed closely by the Go implementation (rpch-go).
- Serialization Overhead: Employing JSON serialization results in a noticeable performance degradation. However, even with this overhead, both C and Go implementations still outperform Go's standard RPC library (stdrpc).
- Framework Comparison: The performance of grpc-go is significantly lower, only about twice that of rpch-node. Stability: All frameworks exhibit stable performance, with latency increasing linearly as the number of concurrent clients grows. The performance rankings for latency are consistent with those for throughput.
- Language Impact: As expected, the C implementation delivers the best performance, while the Node.js implementation is the least performant.
There are two ways to install hgen:
- Build from Source
# for linux
git clone github.com/gufeijun/hgen
cd hgen
go build -o hgen main.goThis will generate an executable file named hgen.
-
Download Binaries
Pre-compiled binaries are available on the Releases page.
Usage:
Usage of hgen:
hgen [options] <IDLfiles...>
options:
-dir string
the dirpath where the generated source code files will be placed (default "gfj")
-lang string
the target languege the IDL will be compliled to (default "c")

