gpt4 book ai didi

c - 从Golang调用C函数

转载 作者:行者123 更新时间:2023-12-01 21:18:29 25 4
gpt4 key购买 nike

我想在Golang中编写 Controller 逻辑并处理Golang中的json和数据库,同时在C中使用数学处理模型。我认为调用C函数的开销必须与设置寄存器rcx,rdx,rsi,rdi,doin一样低fastcall和摆脱rax值(value)。但是我听说过大overhead in cgo

说我有常见的fastcall x64 c函数int64 f(int64 a,b,c,d){return a+b+c+d}我该如何从go中调用它,以便在go testing.B基准测试中获得最高的潜在基准测试分数?

PS没有指针传递,没有技巧,只是对如何以最可靠的方式访问C接口(interface)感兴趣

最佳答案

In my opinion overhead calling C function have to be as low, as as setting registers rcx, rdx, rsi, rdi, doin some fastcall and getting out rax value. But i've heard of big overhead in cgo <…>



您的意见是没有根据的。
从Go到C的调用具有明显的开销的原因是由于以下原因。

让我们首先考虑C

尽管语言没有任何要求,但是由典型的编译器编译并作为常规进程在典型OS上运行的典型C程序在很大程度上依赖于OS来执行其运行时环境的某些方面。
所谓的最可见和最重要的方面是堆栈:内核负责在加载和初始化程序的镜像之后以及将执行转移到新生进程的代码入口点之前对其进行设置。

同样,另一个关键点是,尽管并非严格要求,但大多数C程序还是依赖于OS本地线程来通过程序代码实现多个并发执行的流。

通常使用相同的 ABI来编译用C代码执行的函数,这是操作系统和硬件实现的目标组合(当然,除非程序员已经明确地设法告诉编译器执行其他操作,例如标记特定的具有不同的调用约定)。

C没有自动方法来管理非堆栈内存(“堆”)。
这种管理通常是通过 malloc(3)家族的C标准库函数完成的。
这些函数管理堆,并将通过它们分配的任何内存都视为“它们”(这很合逻辑)。
C不提供自动垃圾收集。

让我们回顾一下:一个用C编译的典型程序:使用OS提供的线程,并在这些线程中使用OS提供的堆栈。大多数情况下,函数调用遵循平台的ABI;堆内存由特殊的库代码管理;没有GC。

现在让我们考虑
  • Go代码的任何部分(程序的代码和运行时的代码)都在所谓的goroutine中运行,它们类似于超轻量级线程。
  • Go运行时提供的goroutine调度程序(编译/链接到用Go语言编写的任何程序中)实现了goroutine的所谓M×N调度,其中M个goroutine被多路复用到N个OS提供的线程中,其中M为通常比N.

  • Go中的
  • 函数调用不遵循目标平台的ABI。
    具体来说,AFAIK当代版本的Go会传递堆栈中的所有调用参数。
  • goroutine始终在OS提供的线程上运行。
    等待Go运行时管理的某些资源(例如, channel ,计时器,网络套接字等)上的goroutine不会占用OS线程。
    当调度程序选择执行例程时,它必须将其分配给Go运行时拥有的空闲OS线程;
    尽管调度程序尝试将goroutine置于挂起之前正在执行的同一个线程上,但这并不总是成功,因此goroutine可以在不同的OS线程之间自由迁移。

  • 以上几点自然导致goroutines具有自己的堆栈,这些堆栈完全独立于操作系统为其线程提供的堆栈。
    与C不同,这些堆栈是可增长和可重新分配的。

    堆内存由Go运行时自动管理,并直接完成,因此不使用C stdlib。
    Go具有GC,并且该GC是并发的,因为它与执行程序代码的goroutines完全同时运行。

    让我们来回顾一下:goroutine具有自己的堆栈,使用与平台的ABI或C都不兼容的调用约定,并且可能在不同的操作系统线程上执行。
    Go运行时直接管理堆内存,并具有完全并行的GC。

    现在考虑从Go到C的调用

    正如您现在应该看到的那样,运行Go和C代码的运行时环境的“世界”足够不同,以至于存在很大的“阻抗不匹配”,这在执行FFI时需要一定的网关连接,而成本不为零。

    特别是,当Go代码将要调用C时,必须执行以下操作:
  • goroutine必须锁定到当前正在其上运行的OS线程(“固定”)。
  • 因为必须根据平台的ABI完成目标C调用,所以必须保存当前的执行上下文-至少保存那些将被调用破坏的寄存器。
  • cgo机制必须验证递归的任何要传递给目标C调用的内存均不包含指向Go管理的其他内存块的指针,这是为了允许Go的GC继续并行工作。
  • 必须将执行从goroutine堆栈切换到线程的堆栈:必须在线程堆栈上创建一个新的堆栈框架,并且必须根据平台的ABI将目标C调用的参数放置在该位置(和在寄存器中)。
  • 进行调用。
  • 返回时,必须将执行切换回goroutine的堆栈-再次将所有返回的结果通过网关传递回正在执行的goroutine的堆栈框架。

  • 正如您可能会看到的那样,不可避免的成本是很大的,而在某些CPU寄存器中放置值是最微不足道的。

    有什么可以做的

    通常,有两种方法可以解决此问题:
  • 很少调用C。

    也就是说,如果每次对C的调用都执行大量的CPU密集型计算,则可以认为执行这些调用的开销与使这些调用执行的计算速度更快的 yield 相形见war。
  • 在汇编中编写关键功能。

    Go允许直接在目标硬件平台的程序集中编写代码。

  • 一种“技巧”可能会让您获得两全其美的效果,那就是利用大多数工业编译器的功能来输出其编译函数的汇编语言形式。因此,您可以使用C编译器提供的核心功能,例如自动矢量化(针对SSE)和积极的优化,然后捕获其生成的任何内容并将其包装在薄薄的程序集中,这基本上使生成的代码适应 native 代码去的ABI。

    有很多第三方第三方Go软件包可以做到这一点(比如 thisthat),显然Go运行时也可以做到这一点。

    关于c - 从Golang调用C函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60729198/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com