C++ 运行时的动态函数解析
我的项目需要在运行时加载许多模块,每个模块都包含许多函数,其形式类似于下面的伪代码:C++ 运行时的动态函数解析,c++,templates,metaprogramming,jit,C++,Templates,Metaprogramming,Jit,我的项目需要在运行时加载许多模块,每个模块都包含许多函数,其形式类似于下面的伪代码: void someFunction(Context &ctx) { bool result; result = ctx.call("someFunction2")(ctx.arg["arg1"], ctx.arg["arg2"]) && ctx.call("someFunction3")(ctx.arg["arg1"], ctx.arg["arg
void someFunction(Context &ctx) {
bool result;
result = ctx.call("someFunction2")(ctx.arg["arg1"], ctx.arg["arg2"])
&& ctx.call("someFunction3")(ctx.arg["arg1"], ctx.arg["arg3"]);
ctx.result(result);
}
其中ctx.arg[“arg1”]
,ctx.arg[“arg2”]
,ctx.arg[“arg3”]
是在运行时传递给someFunction
的参数someFunction2
和someFunction3
无法在编译时进行静态解析,但在运行时,当加载所有模块时,将知道它们(是否已在其他模块中定义)
现在,一个简单的实现是使用哈希映射来存储所有这些函数的函数句柄,但是哈希运算会很慢,因为通常有10k个函数要搜索,每个函数将在其他函数中被多次调用(例如:枚举参数以找到将产生所需结果的正确组合)
因此,我正在寻找某种解决方案,在加载所有模块时对这些“ctx.call”执行一次性替换,而不是每次都执行“哈希和探测”。目前的主要问题是“替换”操作。我提出了一些想法,但它们并不完美:
第一种解决方案:创建一个内部函数
internal\u func(func\u handle1,func\u handle2,arg1,arg2,arg3)
,并使用std::bind
创建一个外部包装outer\u wrapper()
问题:用户不友好,必须明确告诉上下文要查找哪些函数和参数
第二种解决方案:使用metaprogramming+constexpr+宏自动计算函数和参数名称引用的数量,然后创建一个引用表,然后让上下文在运行时填充每个表 问题:我无法解决这个问题,需要一些帮助。我已经从facebook、mpl和boost的hana上阅读了致命库的文档,但似乎没有一个干净的方法可以做到这一点
第三种解决方案:使用JIT编译器 问题:C++编译器的选择是有限的。NativeJIT不够强大,容易:JIT似乎不可定制,不便于分发。ASMJIT不可用。
PS:问题上下文是“automated Planner”,这些函数用于构造谓词。
context ctx
只是一个示例,如果需要,您可以使用其他适当的语法,只要它们易于用于表示以下lisp表达式:
(and (at ?p ?c1)
(aircraft ?a)
(at ?a ?c3)
(different ?c1 ?c3))
PPS:更具体地说,我在想这样的事情:
void module_init() {
FUNC ("someFunction")("p", "a", "c1", "c3") (
bool result;
result = CALL("at")("p", "c1")
&& CALL("aircraft")("a")
&& CALL("at")("a", "c3")
&& CALL("different")("c1", "c3")
/// Users should also be able to access arguments as a "Variable"
/// class using ARG["p"]
return result;
)
}
用户将定义如下所示的函数:
void module_init() {
FUNC ("someFunction")("p", "a", "c1", "c3") (
bool result;
result = CALL("at")("p", "c1")
&& CALL("aircraft")("a")
&& CALL("at")("a", "c3")
&& CALL("different")("c1", "c3")
/// Users should also be able to access arguments as a "Variable"
/// class using ARG["p"]
return result;
)
}
然后,通过某种方式,FUNC()
将被转换为类似于:
struct func_someFunction {
some_vector<std::function<bool()>> functions;
some_vector<Variable*> args;
some_vector<std::string> func_name;
some_vector<std::string> arg_name;
bool operator()() {
/// above representation of Func(), but function and args are pointers in "functions" and "args"
}
}
struct func\u someFunction{
一些向量函数;
一些向量参数;
一些向量函数名;
一些向量参数名称;
布尔运算符()(){
///上面是Func()的表示形式,但函数和参数是“函数”和“参数”中的指针
}
}
然后,当加载所有模块时,系统将读取func\u name
和arg\u name
,并分别向函数和args
填充适当的函数指针和变量指针
状态:首先使用hashmap,完成后我将发布更新。
状态:我自己想出了一个解决方案,还测试了哈希实现,发布在下面。
任何想法都将不胜感激。谢谢
现在,一个简单的实现将使用哈希映射来存储所有这些函数的函数句柄,但是哈希将很慢,因为通常有10k个函数要搜索[…]
哈希表是O(1)查找成本。您是否尝试过此问题的广泛使用的解决方案并进行了性能分析?您是否尝试过使用不同的哈希算法来减少哈希时间和冲突?如果您需要在整个程序生命周期中根据运行时字符串键不断找到要运行的正确函数,那么就没有办法了nd使用哈希映射(Paul的回答)
但是,如果您在运行时初始化一个函数列表,该列表在程序持续时间内不会发生变化(即,在初始阶段之后,您不需要执行任何“查找”操作),那么您可以将这些函数放入一个连续的容器中(例如,std::vector
),以提高访问时间和缓存利用率:
// getFuncNames is where you are deciding on the list of functions to run
// funcs is a vector of function handles
// funcMap is a hash map of function names to function handles
for (auto& funcName : getFuncNames())
{
funcs.push_back(funcMap.at(funcName));
}
这可能有些过分,但可能是一个有用的想法:
使用字符串interning确保每个MyString(“飞机”)
生成相同的对象。当然,这意味着字符串必须是不可变的
将创建的每个字符串对象与高质量随机数(uint64\t
)关联,并将其用作该字符串的“哈希”
由于“散列”与字符串一起存储,因此“计算”它只需简单的内存加载。而且,由于您使用良好的PRNG生成“散列”,因此它作为散列表的键表现出色
每当std::string
被转换为您的interned string对象时,您仍然需要计算一个经典散列,以便在现有string对象的表中找到MyString
对象,但这是一项一次性工作,可以在lexer处理配置文件或加载模块时完成ed.字符串与其各自的函数实现等的实际匹配将与经典散列的计算分离。好的,因此我自己想出了一个解决方案,接近我问题的第一个解决方案,我制作了一个非常简单的问题示例,发布在github上,链接如下:
注意:此解决方案只是一个简单的演示,没有经过优化。进一步可能的优化包括:
对于hash-map方法,字符串插入可用于减少字符串构造开销,如和所建议的,它将导致中等性能(与指针相比大约降低50%)d
53.17% test_ptr test_ptr [.] main
35.38% test_ptr test_ptr [.] module_1_init(Domain&)::__internal_func_some_circuit::operator()
8.02% test_ptr test_ptr [.] module_2_init(Domain&)::__internal_func_and_circuit::operator()
1.90% test_ptr test_ptr [.] module_2_init(Domain&)::__internal_func_or_circuit::operator()
0.18% test_ptr libc-2.23.so [.] _int_malloc
0.15% test_ptr ld-2.23.so [.] do_lookup_x
0.15% test_ptr test_ptr [.] module_2_init(Domain&)::__internal_func_xor_circuit::operator()
33.11% test_hash test_hash [.] Domain::call<char const (&) [11], Domain::Variable&, Domain::Variable&>
25.37% test_hash test_hash [.] main
21.46% test_hash libstdc++.so.6.0.26 [.] std::_Hash_bytes
5.10% test_hash libc-2.23.so [.] __memcmp_sse4_1
4.64% test_hash test_hash [.] std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>
3.41% test_hash test_hash [.] module_1_init(Domain&)::__internal_func_some_circuit::operator()
1.86% test_hash libc-2.23.so [.] strlen
1.44% test_hash test_hash [.] module_2_init(Domain&)::__internal_func_and_circuit::operator()
1.39% test_hash libc-2.23.so [.] __memcpy_avx_unaligned
0.55% test_hash test_hash [.] std::_Hash_bytes@plt
in test_ptr:
void module_1_init(Domain &d) {
FUNC(some_circuit, d,
DEP(and_circuit, or_circuit, xor_circuit, not_circuit),
ARG(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10),
BODY(
return CALL(and_circuit, a1, a2)
&& CALL(or_circuit, a3, a4)
&& CALL(xor_circuit, a5, a6)
&& CALL(not_circuit, a7)
&& a8.value >= R1 && a9.value >= R2 && a10.value >= R3;
)
);
}
in test_hash:
void module_1_init(Domain &d) {
FUNC(some_circuit, d,\
ARG(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), \
BODY(
return CALL(and_circuit, a1, a2)
&& CALL(or_circuit, a3, a4)
&& CALL(xor_circuit, a5, a6)
&& CALL(not_circuit, a7)
&& a8.value >= R1 && a9.value >= R2 && a10.value >= R3;
)
);
}
in test_ptr:
void module_1_init(Domain &d) {
class __internal_func_some_circuit : public Domain::Function {
public:
enum func_dep_idx {
and_circuit,
or_circuit,
xor_circuit,
not_circuit,
__func_dep_idx_end };
Domain::Variable a1;
Domain::Variable a2;
...
Domain::Variable a10;
explicit __internal_func_some_circuit(Domain &d) :
a1(), a2(), a3(), a4(), a5(), a6(), a7(), a8(), a9(), a10(),
Domain::Function(d) {
arg_map = {{"a1", &a1}, {"a2", &a2}, {"a3", &a3} ..., {"a10", &a10}};
arg_pack = { &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10};
func_dep_map = {{"and_circuit", func_dep_idx::and_circuit},
{"or_circuit", func_dep_idx::or_circuit},
{"xor_circuit", func_dep_idx::xor_circuit} ,
{"not_circuit", func_dep_idx::not_circuit}};
func_dep.resize(__func_dep_idx_end);
}
bool operator()() override {
return func_dep[func_dep_idx::and_circuit]->call(a1, a2) &&
func_dep[func_dep_idx::or_circuit]->call(a3, a4) &&
func_dep[func_dep_idx::xor_circuit]->call(a5, a6) &&
func_dep[func_dep_idx::not_circuit]->call(a7) &&
a8.value >= 100 && a9.value >= 100 && a10.value >= 100;
}
};
d.registerFunction("some_circuit", new __internal_func_some_circuit(d))
in test_hash:
class __internal_func_some_circuit : public Domain::Function {
public:
Domain::Variable a1;
Domain::Variable a2;
...
Domain::Variable a10;
explicit __internal_func_some_circuit(Domain &d) :
a1() , a2(), a3(), a4(), a5(), a6(), a7(), a8(), a9(), a10(),
Domain::Function(d) {
arg_map = {{"a1", &a1}, {"a2", &a2} ..., {"a10", &a10}};
arg_pack = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10};
}
bool operator()() override {
return domain.call("and_circuit", a1, a2) &&
domain.call("or_circuit", a3, a4) &&
domain.call("xor_circuit", a5, a6) &&
domain.call("not_circuit", a7) &&
a8.value >= 100 && a9.value >= 100 && a10.value >= 100; }
};
d.registerFunction("some_circuit", new __internal_func_some_circuit(d))