瀏覽代碼

GDScript: Optimize operators by assuming the types

This assumes that operators are called usually with the same type of
operands as the first time. So it stores the types of the first run and
if matched it uses an optimized path by calling the validated operator
function directly. Otherwise it uses the regular untyped evaluator.

With this change, if operators do use the same type they run quite
faster. OTOH, if the types mismatch it takes longer to run than they
would with the previous code.
George Marques 2 年之前
父節點
當前提交
c1bca65d09

+ 13 - 1
modules/gdscript/gdscript_byte_codegen.cpp

@@ -226,7 +226,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
 
 
 	if (opcodes.size()) {
 	if (opcodes.size()) {
 		function->code = opcodes;
 		function->code = opcodes;
-		function->_code_ptr = &function->code[0];
+		function->_code_ptr = &function->code.write[0];
 		function->_code_size = opcodes.size();
 		function->_code_size = opcodes.size();
 
 
 	} else {
 	} else {
@@ -577,6 +577,12 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
 	append(Address());
 	append(Address());
 	append(p_target);
 	append(p_target);
 	append(p_operator);
 	append(p_operator);
+	append(0); // Signature storage.
+	append(0); // Return type storage.
+	constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr()));
+	for (int i = 0; i < _pointer_size; i++) {
+		append(0); // Space for function pointer.
+	}
 }
 }
 
 
 void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
 void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
@@ -610,6 +616,12 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
 	append(p_right_operand);
 	append(p_right_operand);
 	append(p_target);
 	append(p_target);
 	append(p_operator);
 	append(p_operator);
+	append(0); // Signature storage.
+	append(0); // Return type storage.
+	constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr()));
+	for (int i = 0; i < _pointer_size; i++) {
+		append(0); // Space for function pointer.
+	}
 }
 }
 
 
 void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
 void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {

+ 2 - 1
modules/gdscript/gdscript_disassembler.cpp

@@ -113,6 +113,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 
 
 		switch (opcode) {
 		switch (opcode) {
 			case OPCODE_OPERATOR: {
 			case OPCODE_OPERATOR: {
+				constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
 				int operation = _code_ptr[ip + 4];
 				int operation = _code_ptr[ip + 4];
 
 
 				text += "operator ";
 				text += "operator ";
@@ -125,7 +126,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
 				text += " ";
 				text += " ";
 				text += DADDR(2);
 				text += DADDR(2);
 
 
-				incr += 5;
+				incr += 7 + _pointer_size;
 			} break;
 			} break;
 			case OPCODE_OPERATOR_VALIDATED: {
 			case OPCODE_OPERATOR_VALIDATED: {
 				text += "validated operator ";
 				text += "validated operator ";

+ 1 - 1
modules/gdscript/gdscript_function.h

@@ -473,7 +473,7 @@ private:
 	MethodBind **_methods_ptr = nullptr;
 	MethodBind **_methods_ptr = nullptr;
 	int _lambdas_count = 0;
 	int _lambdas_count = 0;
 	GDScriptFunction **_lambdas_ptr = nullptr;
 	GDScriptFunction **_lambdas_ptr = nullptr;
-	const int *_code_ptr = nullptr;
+	int *_code_ptr = nullptr;
 	int _code_size = 0;
 	int _code_size = 0;
 	int _argument_count = 0;
 	int _argument_count = 0;
 	int _stack_size = 0;
 	int _stack_size = 0;

+ 59 - 15
modules/gdscript/gdscript_vm.cpp

@@ -685,7 +685,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 
 
 		OPCODE_SWITCH(_code_ptr[ip]) {
 		OPCODE_SWITCH(_code_ptr[ip]) {
 			OPCODE(OPCODE_OPERATOR) {
 			OPCODE(OPCODE_OPERATOR) {
-				CHECK_SPACE(5);
+				constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
+				CHECK_SPACE(7 + _pointer_size);
 
 
 				bool valid;
 				bool valid;
 				Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
 				Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
@@ -694,28 +695,71 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
 				GET_VARIANT_PTR(a, 0);
 				GET_VARIANT_PTR(a, 0);
 				GET_VARIANT_PTR(b, 1);
 				GET_VARIANT_PTR(b, 1);
 				GET_VARIANT_PTR(dst, 2);
 				GET_VARIANT_PTR(dst, 2);
+				// Compute signatures (types of operands) so it can be optimized when matching.
+				uint32_t op_signature = _code_ptr[ip + 5];
+				uint32_t actual_signature = (a->get_type() << 8) | (b->get_type());
+
+				// Check if this is the first run. If so, store the current signature for the optimized path.
+				if (unlikely(op_signature == 0)) {
+					static Mutex initializer_mutex;
+					initializer_mutex.lock();
+					Variant::Type a_type = (Variant::Type)((actual_signature >> 8) & 0xFF);
+					Variant::Type b_type = (Variant::Type)(actual_signature & 0xFF);
 
 
+					Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(op, a_type, b_type);
+
+					if (unlikely(!op_func)) {
+#ifdef DEBUG_ENABLED
+						err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+#endif
+						initializer_mutex.unlock();
+						OPCODE_BREAK;
+					} else {
+						Variant::Type ret_type = Variant::get_operator_return_type(op, a_type, b_type);
+						VariantInternal::initialize(dst, ret_type);
+						op_func(a, b, dst);
+
+						// Check again in case another thread already set it.
+						if (_code_ptr[ip + 5] == 0) {
+							_code_ptr[ip + 5] = actual_signature;
+							_code_ptr[ip + 6] = static_cast<int>(ret_type);
+							Variant::ValidatedOperatorEvaluator *tmp = reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_code_ptr[ip + 7]);
+							*tmp = op_func;
+						}
+					}
+					initializer_mutex.unlock();
+				} else if (likely(op_signature == actual_signature)) {
+					// If the signature matches, we can use the optimized path.
+					Variant::Type ret_type = static_cast<Variant::Type>(_code_ptr[ip + 6]);
+					Variant::ValidatedOperatorEvaluator op_func = *reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_code_ptr[ip + 7]);
+
+					// Make sure the return value has the correct type.
+					VariantInternal::initialize(dst, ret_type);
+					op_func(a, b, dst);
+				} else {
+					// If the signature doesn't match, we have to use the slow path.
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 
 
-				Variant ret;
-				Variant::evaluate(op, *a, *b, ret, valid);
+					Variant ret;
+					Variant::evaluate(op, *a, *b, ret, valid);
 #else
 #else
-				Variant::evaluate(op, *a, *b, *dst, valid);
+					Variant::evaluate(op, *a, *b, *dst, valid);
 #endif
 #endif
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
-				if (!valid) {
-					if (ret.get_type() == Variant::STRING) {
-						//return a string when invalid with the error
-						err_text = ret;
-						err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
-					} else {
-						err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+					if (!valid) {
+						if (ret.get_type() == Variant::STRING) {
+							//return a string when invalid with the error
+							err_text = ret;
+							err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
+						} else {
+							err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+						}
+						OPCODE_BREAK;
 					}
 					}
-					OPCODE_BREAK;
-				}
-				*dst = ret;
+					*dst = ret;
 #endif
 #endif
-				ip += 5;
+				}
+				ip += 7 + _pointer_size;
 			}
 			}
 			DISPATCH_OPCODE;
 			DISPATCH_OPCODE;