Sfoglia il codice sorgente

optimize for empty function

blueloveTH 1 anno fa
parent
commit
49ee693d40

+ 8 - 0
benchmarks/function_0.py

@@ -0,0 +1,8 @@
+def f(a, b, c):
+    pass
+
+for i in range(10000000):
+    f(1, 2, 3)
+    f(1, 2, 3)
+    f(1, 2, 3)
+    f(1, 2, 3)

+ 11 - 0
benchmarks/function_1.py

@@ -0,0 +1,11 @@
+class A:
+    def f(self, a, b, c):
+        pass
+
+a = A()
+for i in range(10000000):
+    a.f(1, 2, 3)
+    a.f(1, 2, 3)
+    a.f(1, 2, 3)
+    a.f(1, 2, 3)
+

+ 3 - 1
include/pocketpy/codeobject.h

@@ -107,7 +107,9 @@ struct FuncDecl {
 
     Str signature;              // signature of this function
     Str docstring;              // docstring of this function
-    bool is_simple;
+
+    bool is_simple;             // whether this function is simple (no *arg, **kwarg, nested)
+    bool is_empty;              // whether this function is empty (no code)
 
     NameDictInt kw_to_index;
 

+ 8 - 0
src/compiler.cpp

@@ -67,6 +67,14 @@ namespace pkpy{
             if(func->kwargs.size() > 0) func->is_simple = false;
             if(func->starred_arg >= 0) func->is_simple = false;
             if(func->starred_kwarg >= 0) func->is_simple = false;
+
+            func->is_empty = false;
+            if(func->code->codes.size() == 1){
+                Bytecode bc = func->code->codes[0];
+                if(bc.op == OP_RETURN_VALUE && bc.arg == 1){
+                    func->is_empty = true;
+                }
+            }
         }
         contexts.pop();
     }

+ 12 - 3
src/vm.cpp

@@ -862,7 +862,7 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
     PyObject* callable = p1[-(ARGC + 2)];
     Type callable_t = _tp(callable);
 
-    bool method_call = p0[1] != PY_NULL;
+    int method_call = p0[1] != PY_NULL;
 
     // handle boundmethod, do a patch
     if(callable_t == tp_bound_method){
@@ -872,11 +872,11 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
         callable_t = _tp(callable);
         p1[-(ARGC + 2)] = bm.func;
         p1[-(ARGC + 1)] = bm.self;
-        method_call = true;
+        method_call = 1;
         // [unbound, self, args..., kwargs...]
     }
 
-    ArgsView args(p1 - ARGC - int(method_call), p1);
+    ArgsView args(p1 - ARGC - method_call, p1);
     ArgsView kwargs(p1, s_data._sp);
 
     PyObject** _base = args.begin();
@@ -884,11 +884,13 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
 
     if(callable_t == tp_function){
         /*****************_py_call*****************/
+        // check stack overflow
         if(s_data.is_overflow()) StackOverflowError();
 
         const Function& fn = PK_OBJ_GET(Function, callable);
         const CodeObject* co = fn.decl->code.get();
         int co_nlocals = co->varnames.size();
+
         if(fn.decl->is_simple){
             if(args.size() != fn.decl->args.size()){
                 TypeError(_S(
@@ -896,6 +898,13 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){
                 ));
             }
             if(!kwargs.empty()) TypeError(_S(co->name, "() takes no keyword arguments"));
+
+            // fast path for empty function
+            if(fn.decl->is_empty){
+                s_data.reset(p0);
+                return None;
+            }
+
             // [callable, <self>, args..., local_vars...]
             //      ^p0                    ^p1      ^_sp
             s_data.reset(_base + co_nlocals);

+ 12 - 0
tests/21_functions.py

@@ -140,3 +140,15 @@ try:
     exit(1)
 except TypeError:
     pass
+
+# empty function
+def f(a, b, c):
+    pass
+
+assert f(1, 2, 3) == None
+
+class A:
+    def f(self, a, b, c):
+        pass
+    
+assert A().f(1, 2, 3) == None