add a fast path test

This commit is contained in:
blueloveTH 2023-04-14 20:33:54 +08:00
parent 0e483050b7
commit 8bb3cefb34
5 changed files with 103 additions and 62 deletions

View File

@ -1,5 +1,5 @@
clang++ -pg -O2 -std=c++17 -fno-rtti -stdlib=libc++ -Wall -o pocketpy src/main.cpp
time ./pocketpy benchmarks/primes.py
time ./pocketpy benchmarks/fib.py
mv benchmarks/gmon.out .
gprof pocketpy gmon.out > gprof.txt
rm gmon.out

View File

@ -343,14 +343,24 @@ __NEXT_STEP:;
TARGET(CALL)
TARGET(CALL_UNPACK) {
int ARGC = byte.arg;
PyObject* callable = frame->top_n(ARGC+1);
bool method_call = frame->top_n(ARGC) != _py_null;
if(method_call) ARGC++; // add self into args
Args args = frame->popx_n_reversed(ARGC);
// fast path
if(byte.op==OP_CALL && is_type(callable, tp_function)){
ArgsView args = frame->top_n_view(ARGC + int(method_call));
PyObject* ret = _py_call(callable, args, {});
frame->pop_n(ARGC + 2);
if(ret == nullptr) goto __PY_OP_CALL;
else frame->push(ret); // a generator
DISPATCH();
}
Args args = frame->popx_n_reversed(ARGC + int(method_call));
if(!method_call) frame->pop();
if(byte.op == OP_CALL_UNPACK) unpack_args(args);
PyObject* callable = frame->popx();
frame->pop();
PyObject* ret = call(callable, std::move(args), no_arg(), true);
if(ret == _py_op_call) { __ret=ret; goto __PY_OP_CALL; }
frame->push(ret);

View File

@ -254,6 +254,10 @@ struct Frame {
_data.pop_back_n(n);
}
ArgsView top_n_view(int n){
return ArgsView(_data.end()-n, _data.end());
}
void _gc_mark() const {
// do return if this frame has been moved
if(_data._data == nullptr) return;

View File

@ -9,7 +9,7 @@ namespace pkpy {
using List = pod_vector<PyObject*>;
class Args {
class Tuple {
PyObject** _args;
int _size;
@ -19,26 +19,26 @@ class Args {
}
public:
Args(int n){ _alloc(n); }
Tuple(int n){ _alloc(n); }
Args(const Args& other){
Tuple(const Tuple& other){
_alloc(other._size);
for(int i=0; i<_size; i++) _args[i] = other._args[i];
}
Args(Args&& other) noexcept {
Tuple(Tuple&& other) noexcept {
this->_args = other._args;
this->_size = other._size;
other._args = nullptr;
other._size = 0;
}
Args(std::initializer_list<PyObject*> list) : Args(list.size()){
Tuple(std::initializer_list<PyObject*> list) : Tuple(list.size()){
int i = 0;
for(PyObject* p : list) _args[i++] = p;
}
Args(List&& other) noexcept : Args(other.size()){
Tuple(List&& other) noexcept : Tuple(other.size()){
for(int i=0; i<_size; i++) _args[i] = other[i];
other.clear();
}
@ -46,7 +46,7 @@ public:
PyObject*& operator[](int i){ return _args[i]; }
PyObject* operator[](int i) const { return _args[i]; }
Args& operator=(Args&& other) noexcept {
Tuple& operator=(Tuple&& other) noexcept {
if(_args!=nullptr) pool64.dealloc(_args);
this->_args = other._args;
this->_size = other._size;
@ -57,6 +57,9 @@ public:
int size() const { return _size; }
PyObject** begin() const { return _args; }
PyObject** end() const { return _args + _size; }
List to_list() noexcept {
List ret(_size);
// TODO: use move/memcpy
@ -73,14 +76,29 @@ public:
if(old_args!=nullptr) pool64.dealloc(old_args);
}
~Args(){ if(_args!=nullptr) pool64.dealloc(_args); }
~Tuple(){ if(_args!=nullptr) pool64.dealloc(_args); }
};
using Args = Tuple;
inline const Args& no_arg() {
static const Args _zero(0);
return _zero;
}
typedef Args Tuple;
// a lightweight view for function args, it does not own the memory
struct ArgsView{
PyObject** _begin;
PyObject** _end;
ArgsView(PyObject** begin, PyObject** end) : _begin(begin), _end(end) {}
ArgsView(const Tuple& t) : _begin(t.begin()), _end(t.end()) {}
ArgsView(): _begin(nullptr), _end(nullptr) {}
PyObject** begin() const { return _begin; }
PyObject** end() const { return _end; }
int size() const { return _end - _begin; }
bool empty() const { return _begin == _end; }
PyObject* operator[](int i) const { return _begin[i]; }
};
} // namespace pkpy

View File

@ -338,6 +338,7 @@ public:
Str disassemble(CodeObject_ co);
void init_builtin_types();
PyObject* call(PyObject* callable, Args args, const Args& kwargs, bool opCall);
PyObject* _py_call(PyObject* callable, ArgsView args, ArgsView kwargs);
void unpack_args(Args& args);
PyObject* getattr(PyObject* obj, StrName name, bool throw_err=true);
PyObject* get_unbound_method(PyObject* obj, StrName name, PyObject** self, bool throw_err=true, bool fallback=false);
@ -687,19 +688,8 @@ inline void VM::init_builtin_types(){
for(auto [k, v]: _modules.items()) v->attr()._try_perfect_rehash();
}
// TODO: callable/args here may be garbage collected accidentally
inline PyObject* VM::call(PyObject* callable, Args args, const Args& kwargs, bool opCall){
if(is_type(callable, tp_bound_method)){
auto& bm = CAST(BoundMethod&, callable);
callable = bm.method; // get unbound method
args.extend_self(bm.obj);
}
if(is_type(callable, tp_native_function)){
const auto& f = OBJ_GET(NativeFunc, callable);
if(kwargs.size() != 0) TypeError("native_function does not accept keyword arguments");
return f(this, args);
} else if(is_type(callable, tp_function)){
inline PyObject* VM::_py_call(PyObject* callable, ArgsView args, ArgsView kwargs){
// callable is a `function` object
const Function& fn = CAST(Function&, callable);
const CodeObject* co = fn.decl->code.get();
FastLocals locals(co);
@ -748,6 +738,25 @@ inline PyObject* VM::call(PyObject* callable, Args args, const Args& kwargs, boo
return PyIter(Generator(this, Frame(co, _module, std::move(locals), fn._closure)));
}
_push_new_frame(co, _module, std::move(locals), fn._closure);
return nullptr;
}
// TODO: callable/args here may be garbage collected accidentally
inline PyObject* VM::call(PyObject* callable, Args args, const Args& kwargs, bool opCall){
if(is_type(callable, tp_bound_method)){
auto& bm = CAST(BoundMethod&, callable);
callable = bm.method; // get unbound method
args.extend_self(bm.obj);
}
if(is_type(callable, tp_native_function)){
const auto& f = OBJ_GET(NativeFunc, callable);
if(kwargs.size() != 0) TypeError("native_function does not accept keyword arguments");
return f(this, args);
} else if(is_type(callable, tp_function)){
// ret is nullptr or a generator
PyObject* ret = _py_call(callable, args, kwargs);
if(ret != nullptr) return ret;
if(opCall) return _py_op_call;
return _run_top_frame();
}