gpt4 book ai didi

c - 如何快速动态地加载经常重新生成的C代码?

转载 作者:IT王子 更新时间:2023-10-29 00:09:51 24 4
gpt4 key购买 nike

我希望能够动态生成C代码并将其快速重新加载到正在运行的C程序中。

我在Linux上,怎么做?

Linux上的库.so文件可以在运行时重新编译并重新加载吗?

是否可以在不生成.so文件的情况下进行编译,编译后的输出能否以某种方式进入内存,然后重新加载?我想快速重新加载已编译的代码。

最佳答案

您想做的事情是合理的,我正在MELT(扩展GCC的高级 Realm 特定语言; MELT通过使用MELT编写的翻译器本身进行编译)中做到了这一点。

首先,在生成C代码(或许多其他源语言)时,一个好的建议是在内存中保留某种abstract syntax tree(AST)。因此,首先构建所生成的C代码的整个AST,然后将其作为C语法发出。不要想到没有显式AST的代码生成框架(换句话说,使用一堆printf生成C代码是维护的噩梦,您需要一些中间表示形式)。

其次,生成C代码的主要原因是利用良好的优化编译器(另一个原因是C的可移植性和普遍性)。如果您不关心所生成代码的性能(TCC将C很快地编译为非常幼稚且缓慢的机器代码),则可以使用其他方法,例如使用一些JIT库,例如Gnu lightning(非常快地生成慢速机器代码),Gnu LibjitASMJIT(生成的机器代码要好一些),LLVMGCCJIT(生成好的机器代码,但生成时间可与编译器相比)。

因此,如果生成C代码并希望其快速运行,那么C代码的编译时间就可以忽略不计(因为您可能会派生一个gcc -O -fPIC -shared命令来从生成的foo.so中创建一些共享库foo.c)。根据经验,生成C代码比编译它(用gcc -O)花费的时间少得多。在MELT中,C代码的生成速度比GCC的编译速度快10倍以上(通常快30倍)。但是由C编译器完成的优化是值得的。

发出C代码并将其编译 fork 为.so共享对象后,就可以对其进行dlopen了。别害羞,我的manydl.c示例演示了在Linux上您可以dlopen大量共享对象(成百上千个)。真正的瓶颈是生成的C代码的编译。实际上,您实际上不需要在Linux上使用dlclose(除非您正在编写需要运行数月的服务器程序)。一个未使用的共享模块实际上可以保持dlopen的状态,并且您大多数情况下是在泄漏进程地址空间(这是一种便宜的资源),因为大部分未使用的.so都将被换出。 dlopen快速完成,需要花费时间来编译C源代码,因为您确实希望优化由C编译器完成。

您可以使用许多其他不同的方法,例如有一个字节码解释器并为该字节码生成,请使用Common Lisp(例如Linux上的SBCL,可动态编译为机器代码),LuaJit,Java,MetaOcaml等。

正如其他人所建议的那样,您不太在意编写C文件的时间,它实际上将保留在文件系统缓存中(另请参见this)。而且编写它比编译它要快得多,因此保留在内存中不值得麻烦。如果您担心I/O时间,请使用一些tmpfs。

附加物

您问

Can a library .so file on Linux be re-compiled and re- loaded at runtime?



当然可以:您应该派生一个命令,从生成的C代码构建库(例如 gcc -O -fPIC -shared generated.c -o generated.so,但是您可以间接地做到这一点,例如通过运行 make -j,尤其是如果 generated.so足够大以至于可以分割 generated.c的话然后用C生成的几个文件!),然后使用 dlopen动态加载库(提供了完整的路径,例如 /some/file/path/to/generated.so,可能还有 RTLD_NOW标志),并且必须使用 dlsym在其中查找相关符号。不要考虑重新加载(第二次)相同的 generated.so,最好发出一个唯一的 generated1.c(然后是 generated2.c等)C文件,然后将其编译为一个唯一的 generated1.so(第二次编译为 generated2.so,等等) ...)然后对其进行 dlopen(这可以完成数十万次)。您可能希望在发出的 generated*.c文件中具有一些 constructor函数,这些函数将在 dlopengenerated*.so时执行

您的基本应用程序应该已经定义了关于 dlsym -ed名称集(通常是函数)以及如何调用它们的约定。它只应通过 generated*.so -ed函数指针直接调用 dlsym中的函数。在实践中,您将决定,例如,每个 generated*.c定义一个函数 void dynfoo(int)int dynbar(int,int),然后将 dlsym"dynfoo""dynbar"一起使用,然后调用这些函数指针(由 dlsym返回)。您还应该定义有关如何以及何时调用这些 dynfoodynbar的约定。您最好将基本应用程序与 -rdynamic链接,以便 generated*.c文件可以调用您的应用程序函数。

希望您的 generated*.so改为 重新定义现有名称。例如,您不想在 malloc中重新定义 generated*.c,并希望所有堆分配函数都神奇地使用您的新变体(这可能行不通,即使这样做也很危险)。

除了在应用程序清理和退出时,您可能不会费心 dlclose一个动态加载的共享对象(但我一点也不费心 dlclose)。如果您对某些动态加载的 dlclose文件执行 generated*.so,请确保其中不使用任何文件:该文件中没有指针,甚至没有调用帧中的返回地址。

附言目前,MELT转换器已将57LTOC的MELT代码转换为将近1770KLOC的C代码。

关于c - 如何快速动态地加载经常重新生成的C代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12319329/

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