A high-performance CGO cross-language calling method
By lazy unbinding logical processors and system threads, unnecessary stack switching is cancelled, cross-language call flow of Go and C languages is optimized, the problem of low CGO call performance is solved, and significant performance improvement is achieved.
Patent Information
- Application Number
- CN202211228993.9
- Authority / Receiving Office
- CN · China
- Patent Type
- Patents(China)
- Current Assignee / Owner
- Filing Date
- 2022-10-08
- Publication Date
- 2025-08-08
- Estimated Expiration
- 2042-10-08
AI Technical Summary
The performance of CGO in Go language is low across language calls, mainly due to the high call overhead caused by frequent unbinding and stack switching of logical processors and system threads.
Lazy unbinding logical processors and system threads are adopted to cancel unnecessary stack switching, and optimize the cross-language call process by obtaining the stack space upper bound of C language functions at compile time.
It significantly reduces the overhead of cross-language calling and improves performance, especially in the Go language calling C scenario, which improves by about 7 times, maintains runtime security and functional integrity.
Smart Images

Figure CN115934374B_ABST
Abstract
Description
Technical Field
[0001] The present invention relates to the field of cross-language calling technology, and in particular to a high-performance CGO cross-language calling method. Background Art
[0002] Go, also known as Golang, is popular among developers for its simple syntax, ease of development, and advanced language features like garbage collection, multiple return values, and concurrent programming.
[0003] The Go language provides a cross-language intercommunication mechanism called CGO, which allows the codes of the two languages Go and C to call each other, making it easier to reuse historical or high-performance C code libraries. However, the performance of CGO calls has always been criticized by users. Its call chain is long and the call overhead is high. By conducting experiments on Go language calling an empty function in C language, and C language repeatedly calling an empty function in Go language n (n=1M) times, the average overhead of a single CGO call was calculated, and the results shown in Table 1 were obtained. It can be seen that the overhead of Go language calling C language and C language calling Go language is much higher than that of Go language calling Go language, specifically up to 48 times to 2707 times.
[0004] Table 1. CGO call overhead
[0005] Calling method time Go language calls Go language 1.442ns Go language calls C language 69.90ns C language calls Go language (Go language is the host language) 119.749ns C language calls Go language (C language is the host language) 3904.44ns
[0006] This situation is mainly caused by the runtime scheduling model GMP of the Go language. In the runtime scheduling model GMP, G stands for Goroutine, M stands for Machine or worker thread, which is the traditional system thread, and P stands for Processor, which is the user-level code logical processor. Figure 1 As shown in the figure, the scheduling unit of the Go language is a Go coroutine. The Go coroutine in the queue LRQ of the logical processor P can be executed only after the system thread M is associated with a logical processor P, unless the system thread M is blocked or there is no logical processor P associated with it when the system call takes too long.
[0007] Under the GMP model, if a Go function runs for too long, the Go coroutine will be preempted, and all Go coroutines will be suspended during garbage collection (GC). When a Go function calls a C function, the execution of the C function will restrict the scheduling behavior of the Go runtime. At this time, the system thread M running the C language will be marked and removed from the Go runtime's scheduling scope.
[0008] In addition, the stack space size of the Go coroutine changes dynamically, initially at 2KB. However, the execution of C language functions may require a statically unknown larger stack space, so it is not suitable to execute C language functions directly on the Go coroutine stack space. The Go language runtime will have a special Go coroutine g0, which is responsible for scheduling and managing each Go coroutine running on the system thread M. When the system thread M is initialized, the special Go coroutine g0 will be allocated a system stack of a fixed size that exceeds the normal Go coroutine stack. Figure 2 As shown in the figure, when a Go function calls a C function, it switches to the system stack of the special Go coroutine g0 for execution. When the C function call returns to the Go function, it needs to switch back again. This switching is the main reason for the high overhead of CGO calls. Summary of the Invention
[0009] To solve the above technical problems, the present invention provides a high-performance CGO cross-language calling method.
[0010] In order to solve the above technical problems, the present invention adopts the following technical solutions:
[0011] A high-performance CGO cross-language calling method that lazily unbinds the logical processor P and system thread M in the Go language cross-language intercommunication: During the mutual calling process between the Go language and the C language, if the Go coroutine receives a preemption signal sent by the Go language, the following steps are performed:
[0012] a) Jump to the signal processing function of the Go language;
[0013] b) Unbind the logical processor P where the current Go coroutine is located from the system thread M, and mark it as entering the system call state;
[0014] c) switching to the C language function execution, at which point the system thread M is classified outside the Go language runtime scheduling range;
[0015] d) When the C language function finishes executing or the C language function calls back the Go language function, determine whether the logical processor P and the system thread M were unbound before the C language function was executed. If so, rebind the logical processor P and the system thread M before executing the Go language function.
[0016] Furthermore, this includes eliminating stack switching in cross-language intercommunication between Go and C:
[0017] When compiling a Go language program, the upper bound of the stack space used by the C language function is obtained in the following way: c : User specified, static analysis of C language functions, default size;
[0018] When a Go language function calls a C language function, according to Space c The value of indicates the stack space size for the Go coroutine that executes the C language function, so that both the C language function and the Go language function can run in the same stack space of the Go coroutine.
[0019] Compared with the prior art, the beneficial technical effects of the present invention are:
[0020] The present invention removes the operation of unbinding the logical processor P and the system thread M from the overall cross-language call process. The operation is only called when the Go language runtime attempts to preempt the Go coroutine in the HCGO call process, so that cross-language calls in most cases do not need to perform this operation. By eliminating stack switching, the cross-language call chain is optimized and the call overhead is reduced. BRIEF DESCRIPTION OF THE DRAWINGS
[0021] Figure 1 This is a diagram of the Go language's runtime scheduling model GMP;
[0022] Figure 2 This is a schematic diagram of the call of the cross-language interoperability mechanism CGO in the prior art;
[0023] FIG3( a ) is a flowchart illustrating the normal execution of a Go language function calling a C language function in the high-performance cross-language calling mechanism HCGO of the present invention;
[0024] FIG3( b ) is an execution flow chart of preemption during the execution of a C language function in the high-performance cross-language calling mechanism HCGO of the present invention;
[0025] FIG4(a) and FIG4(b) are schematic diagrams of a backtracking mechanism based on stack camouflage in the high-performance cross-language calling mechanism HCGO of the present invention;
[0026] 5( a ) and 5 ( b ) are schematic diagrams of a Go language function calling a C language function and a C language function calling a Go language function in the high-performance cross-language calling mechanism HCGO of the present invention, respectively. DETAILED DESCRIPTION
[0027] A preferred embodiment of the present invention will be described in detail below with reference to the accompanying drawings.
[0028] CGO is a mechanism provided by the Go language that allows the Go language and the C language to call each other. To address the shortcomings of the CGO call chain being lengthy and the call overhead being high, the present invention proposes a high-performance cross-language call mechanism, HCGO, which optimizes the CGO call chain primarily through two optimization concepts: "lazy unbinding of logical processors P and system threads M" and "canceling stack switching." This improves the performance of CGO calls and reduces the overhead of CGO calls while maintaining security and functionality. Experiments have shown that compared to the native CGO call mechanism, the high-performance CGO cross-language call method of the present invention can achieve approximately 7 times the performance improvement in the Go language calling C language scenario.
[0029] The high-performance CGO cross-language calling method in this invention is mainly divided into three parts: Go language program compilation process, runtime CGO calling process, and adaptation of other runtime mechanisms.
[0030] Among them, the Go language program compilation process mainly modifies the Go compiler, and adds the recognition and adaptation of the new high-performance cross-language call mechanism HCGO while being compatible with the native CGO call mechanism; the runtime CGO call process mainly optimizes the cross-language call chain and reduces the call overhead by lazily unbinding the logical processor P and the system thread M and canceling the stack switch; the adaptation of the remaining runtime mechanisms is achieved by implementing a stack-masquerading-based traceback mechanism and modifying some runtime functions to ensure the runtime security and functional integrity of the Go language under the high-performance cross-language call mechanism HCGO.
[0031] 1. Go language program compilation process
[0032] The high-performance cross-language call mechanism HCGO supports three different ways to enable it at different granularities:
[0033] (1) In the Go language code, the high-performance cross-language call mechanism HCGO is enabled through the [import "HC"] tag, and [HC.xxx] is used to instruct the xxx function to use the high-performance cross-language call mechanism HCGO to perform cross-language calls.
[0034] (2) Add the parameter -hcgo to the Go language compilation command. At this time, all compiled files will use the high-performance cross-language call mechanism HCGO.
[0035] (3) Set "HCGO=true" in the environment variable. At this time, all Go programs will use the high-performance cross-language calling mechanism HCGO.
[0036] 2. Runtime CGO call process
[0037] In the native CGO calling process, three steps are required before and after calling a C language function:
[0038] (1) Unbind the logical processor P and the system thread M and mark the entry into the system call state (or bind the logical processor P and the system thread M and mark the exit from the system call state);
[0039] (2) Switching the Goroutine, that is, switching from the ordinary Goroutine to the special Goroutine g0, thereby switching the execution of the C language function to the system stack of the special Goroutine g0;
[0040] (3) Perform conversion of ABI calling convention.
[0041] In the calling process of the high-performance cross-language calling mechanism HCGO, unnecessary unbinding operations are removed by lazily unbinding the logical processor P and the system thread M, and the switching operation of the Go coroutine is canceled by canceling the stack switch. Only the ABI calling convention conversion needs to be performed before and after the C language function call.
[0042] 2.1 Lazy Unbinding of Logical Processors P and System Threads M
[0043] In the design of the high-performance cross-language call mechanism HCGO, the Go coroutine does not perform the unbinding operation of the logical processor P and the system thread M before each C language function is called. Instead, the unbinding operation of the logical processor P and the system thread M is removed from the overall call process. The Go coroutine is only called when the Go language runtime attempts to preempt the Go coroutine in the process of calling the high-performance cross-language call mechanism HCGO. This makes it unnecessary to perform this operation in most cross-language calls.
[0044] As shown in Figure 3(a), under normal circumstances, the call process of the high-performance cross-language call mechanism HCGO does not perform the unbinding operation. As shown in Figure 3(b), when the Go coroutine in the call process of the high-performance cross-language call mechanism HCGO receives a preemption signal sent by the Go language runtime, the following process will be carried out:
[0045] (1) Jump to the signal processing function of the Go language;
[0046] (2) Unbind the logical processor P and system thread M where the current Go coroutine is located, and mark it as entering the system call state;
[0047] (3) The C language function is executed normally. At this time, the system thread M has been divided into the scope of the Go language runtime scheduling and will no longer block the Go language preemption.
[0048] (4) When the C language function finishes executing or the C language function calls back the Go language function, the status of the current logical processor P and system thread M is judged. If it is found that the unbinding operation has been performed during the execution of the C language function, it will be re-bound and then the Go language function will be executed again.
[0049] 2.2 Cancel stack switching
[0050] In Go, the stack size of a Go coroutine might be too small to support calling arbitrary C functions. Therefore, the native CGO calling mechanism must first switch the stack space of the C function to the system stack of the special Go coroutine g0 when performing cross-language calls. This operation violates memory locality and results in a high cache miss rate.
[0051] In the design of the high-performance cross-language call mechanism HCGO, first, during the Go program compilation phase, by modifying the CGO compilation process, one of the following three approaches can be used to obtain the upper bound of the C language function stack space usage: (1) user-specified; (2) static analysis of the C language function; (3) 8MB by default; thus, at runtime, it can indicate how much stack space the Go coroutine that is about to call the C language function requires. Secondly, during the runtime call phase, the CGO cross-language call chain is modified. Before executing the C language function, the stack extension mechanism provided by the Go language runtime itself is used to expand the Go coroutine stack space to the specified size to support the C language function call; and in the subsequent call process, the stack switching operation is canceled, allowing the C language function and the Go language function to run in the same stack space.
[0052] 3. Adaptation of other mechanisms at runtime
[0053] Since the high-performance cross-language calling mechanism HCGO cancels the unbinding of logical processors P and system threads M and the stack switching operations, it breaks some assumptions of the Go language runtime on the CGO calling mechanism. Therefore, in order to maintain the security and functional integrity of the Go language runtime, the remaining runtime mechanisms need to be adapted.
[0054] 3.1 Traceback mechanism based on stack camouflage
[0055] The Go language's backtracking mechanism is used in many scenarios, such as stack scanning during garbage collection, stack copying during stack expansion and contraction, and error message printing during program crashes. In these scenarios, the backtracking mechanism starts from the Go coroutine's current stack frame and scans the stack frames of each function toward the bottom of the stack, achieving these functions.
[0056] In the high-performance cross-language calling mechanism HCGO, the C language function called by the Go language function runs in the same stack space as the Go language function, and the Go language backtracing mechanism cannot and should not scan the stack frame of the C language function. Therefore, a backtracing mechanism based on stack camouflage is also implemented in the high-performance cross-language calling mechanism HCGO, such as Figure 4(a) 、 4(b) shown.
[0057] To facilitate locating the stack frame record of the last Go language function before calling a C language function, the high-performance cross-language call mechanism HCGO introduces the field hcgopc to store the current program counter of the Go language function call and the field hcgosp to store the stack top pointer of the stack frame. When the Go language calls a C language function through the high-performance cross-language call mechanism HCGO, and then calls back a Go language function, when the C language function calls back the first Go language function, the current hcgosp value will be pushed onto the stack (that is, the stack top pointer of the previous Go language function will be saved) for backtracing purposes. After the backtracing scan is triggered:
[0058] (1) If it is recognized that the current Go coroutine is in the process of calling a C language function of the high-performance cross-language call mechanism HCGO, as shown in Figure 4(a), the backtracing mechanism is enabled to skip the stack frame of the current C language function through the pre-recorded fields hcgopc and hcgosp, and directly start backtracing from the stack frame of the last Go language function before the high-performance cross-language call mechanism HCGO is called;
[0059] (2) If it is recognized that the current Go coroutine is in the process of calling back a Go language function by the high-performance cross-language calling mechanism HCGO, as shown in Figure 4(b), the C language function is skipped by obtaining the stack top pointer of the previous Go language function in the first Go language function stack frame that is previously saved after the C language function calls back the Go language function.
[0060] 3.2 Modifications to other runtime mechanisms
[0061] The Go runtime includes special adaptations for the CGO calling mechanism. However, the runtime state of the high-performance cross-language calling mechanism, HCGO, differs from that of the CGO mechanism, causing some runtime adaptations for C language function calls to fail. Therefore, after analyzing the differences between the performance of the high-performance cross-language calling mechanism, HCGO, and the native CGO calling mechanism in the Go runtime, we modified some runtime mechanisms to address these differences. These modifications affected Go's crash reporting, signal handling, and backtracing mechanisms.
[0062] (1) Crash error reporting mechanism. Since the high-performance cross-language call mechanism HCGO does not switch to the Go coroutine during execution, HCGO will enter the panic error reporting process during crash handling, while the crash error of external code execution should enter the fatal error reporting process. Therefore, the crash error reporting mechanism of the Go language is modified so that the crash handling in the high-performance cross-language call mechanism HCGO can enter the fatal error reporting process.
[0063] (2) Signal processing mechanism. For the high-performance cross-language call mechanism HCGO that does not perform the "unbinding logical processor P and system thread M" operation, the Go language runtime cannot perceive that it is executing external code, and thus will treat it as a normal Go coroutine signal processing process during signal processing. Therefore, the Go signal processing mechanism is modified so that during the call process of the high-performance cross-language call mechanism HCGO, the signal processing process under the CGO call mechanism is executed; when receiving certain specific signals, the runtime forwards the signal to the C language function currently executing on the Go coroutine; when adjusting the signal stack position of the Go language, it is adjusted to execute the Go coroutine called by the high-performance cross-language call mechanism HCGO.
[0064] (3) Backtracking mechanism. Modify the backtracking mechanism so that the high-performance cross-language calling mechanism HCGO supports setting custom CGO callback functions during the backtracking process (used to support custom scanning of C language function stack frames).
[0065] 4. Advantages and positive effects
[0066] Table 1. Comparison of the overhead of high-performance cross-language calling mechanisms HCGO and CGO
[0067] Calling method HCGO expenses CGO Overhead Speedup Go language calls C language 9.488ns 69.90ns 7.367 C language calls Go language (Go language is the host language) 72.274ns 119.749ns 1.657 C language calls Go language (C language is the host language) 3852.35ns 3904.44ns 1.014
[0068] Table 2 shows a comparison of the call overhead of the high-performance cross-language call mechanism HCGO and the CGO call mechanism under several different call modes. The experimental setup for testing call overhead is as follows: N (N is a constant in the tens of millions) cross-language calls to an empty function are repeated, and the total time T taken is recorded. The table records the average time per call, i.e., t = T / N. As can be seen from the table, in the scenario where the most commonly used language is Go calling C, the high-performance cross-language call mechanism HCGO can achieve a speedup of over 7 times. In the scenario where Go is the host language and C calls Go, the high-performance cross-language call mechanism HCGO can also achieve a speedup of approximately 1.6 times. However, in the scenario where C is the host language and C calls Go, the Go runtime initialization operation must be started before entering the call chain. This runtime initialization operation consumes a high overhead, resulting in the high-performance cross-language call mechanism HCGO not having a significant speedup compared to the CGO call mechanism.
[0069] At the same time, when using the high-performance cross-language calling mechanism HCGO, it can fully pass the CGO-related security tests in the Go language community, which shows that the high-performance cross-language calling mechanism HCGO has a certain degree of security.
[0070] In the high-performance cross-language calling mechanism HCGO, the process of a Go function calling a C function is shown in Figure 5(a). When a Go function calls a C function through [C.xxx] or [HC.xxx], the following three steps are executed:
[0071] (1) Enter the runtime function runtime.hcgocall. The runtime.hcgocall function has two tasks: performing stack expansion before calling the C language function; setting the flag bits related to the high-performance cross-language call mechanism HCGO, such as hcgopc and hcgosp, and performing state transitions;
[0072] (2) Call the assembly function runtime.asmhcgocall to convert the ABI calling convention between C language and Go language;
[0073] (3) Call the C language function.
[0074] The process of a C function calling a Go function under the high-performance cross-language calling mechanism HCGO is shown in Figure 5(b). When a C function initiates a call to a Go function, the high-performance cross-language calling mechanism HCGO will perform the following five steps:
[0075] (1) Call the crosscall2 function to perform the ABI calling convention conversion;
[0076] (2) Execute the assembly function runtime.cgocallback to determine whether the current call process is the call process of the high-performance cross-language call mechanism HCGO. If so, enter the call chain of the high-performance cross-language call mechanism HCGO; otherwise, execute the call chain of the native CGO;
[0077] (3) Execute the runtime function runtime.hcgocallback to set and save the flag bits related to the high-performance cross-language call mechanism HCGO (such as saving pc and sp to the stack);
[0078] (4) Execute the runtime.hcgocallbackg function to save the system call status to prevent it from being modified during the callback process;
[0079] (5) Execute the runtime.hcgocallbackg1 function to prepare parameters and call the actual Go language function.
[0080] Example
[0081] Method 1 for enabling the high-performance cross-language call mechanism HCGO: The high-performance cross-language call mechanism HCGO can be enabled by using [import "HC"] in the code and calling the C function named sum through [HC.sum]; the code is as follows:
[0082] / / HCGO activation method 1
[0083] / / int sum(int a,int b){return a+b;}
[0084] import "HC"
[0085] func main(){
[0086] HC.sum(1,2)
[0087] }.
[0088] Since the high-performance cross-language call mechanism HCGO is enabled by changing [import "C"] in the application code to [import "HC"] in the first method, two methods are provided as follows without modifying the application code: the second method is to add the [-hcgo] parameter to the compilation command, and the third method is to set the system environment variable [HCGO=true] to achieve this. The code is as follows:
[0089] #HCGO activation method 2
[0090] $ go run sum_cgo.go -hcgo;
[0091] #HCGO activation method three
[0092] $HCGO=true go run sum_cgo.go.
[0093] It will be apparent to those skilled in the art that the present invention is not limited to the details of the exemplary embodiments described above and that the invention can be embodied in other specific forms without departing from the spirit or essential characteristics of the invention. Therefore, the embodiments should be considered in all respects as illustrative and non-limiting, and the scope of the invention is defined by the appended claims rather than the foregoing description. It is intended that all variations within the meaning and range of equivalents of the claims be embraced herein, and any reference signs in the claims should not be construed as limiting the claims to which they relate.
[0094] In addition, it should be understood that although this specification is described in terms of implementation methods, not every implementation method contains only one independent technical solution. This narrative method of the specification is only for the sake of clarity. Those skilled in the art should regard the specification as a whole. The technical solutions in each embodiment can also be appropriately combined to form other implementation methods that can be understood by those skilled in the art.
Claims
1. A high-performance CGO cross-language calling method, characterized in that: Lazily unbind the logical processor P and system thread M in the Go language cross-language call: During the mutual call between the Go language and the C language, if the Go coroutine receives a preemption signal sent by the Go language, the following steps are performed: a) Jump to the signal processing function of the Go language; b) Unbind the logical processor P where the current Go coroutine is located from the system thread M, and mark it as entering the system call state; c) switching to the C language function execution, at which point the system thread M is classified outside the Go language runtime scheduling range; d) When the C language function finishes executing or the C language function calls back the Go language function, determine whether the logical processor P and the system thread M were unbound before the C language function was executed. If so, rebind the logical processor P and the system thread M before executing the Go language function; This includes canceling stack switching in cross-language intercommunication between Go and C: When compiling a Go language program, the upper bound of the stack space used by the C language function is obtained in the following way: c : User specified, static analysis of C language functions, default size; When a Go language function calls a C language function, according to Space c The value of indicates the stack space size for the Go coroutine that executes the C language function, so that both the C language function and the Go language function can run in the same stack space of the Go coroutine.