Source-Python-Dev-Team / Source.Python

This plugin aims to use boost::python and create an easily accessible wrapper around the Source Engine API for scripter use.
http://forums.sourcepython.com
GNU General Public License v3.0
163 stars 31 forks source link

Improve TypeManager.function/TypeManager.virtual_function fetch speed by using cache. #479

Open CookStar opened 1 year ago

CookStar commented 1 year ago

Entity has a mechanism to speed up the retrieval of dynamic_attributes/server_classes, but TypeManager itself does not. With this improvement, instances created by TypeManager can speed up the retrieval of function/virtual_function.

In virtual_function, I tried to improve the performance by using type_info and caching, but it did not improve the performance.

Test Code (CS:GO/Linux):

import time

from memory import find_binary
from memory import make_object
from memory.manager import CustomType
from memory.manager import TypeManager

""" TypeManager.function Test"""

# _ZN9CCSPlayer12RoundRespawnEv
signature = b"\x55\x89\xE5\x56\x53\x83\xEC\x1C\x8B\x5D\x08\xFF\x35\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x83\xC4\x10\x84\xC0\x0F\x85\x2A\x2A\x2A\x2A\x8B\x13"

# Cache signature
find_binary("server", srv_check=False)[signature]

manager = TypeManager()

class Test(CustomType, metaclass=manager):
    _binary = "server"
    _srv_check = False
    _size = 0

    _spawn = manager.function(signature)

# Cache function
Test._spawn

s = time.perf_counter()
for i in range(100000):
    Test._spawn
e = time.perf_counter()
print(e-s, "Test._spawn")

# Cache function
test = Test()
test._spawn

s = time.perf_counter()
for i in range(100000):
    test._spawn
e = time.perf_counter()
print(e-s, "test._spawn")

l = [Test() for i in range(100000)]

s = time.perf_counter()
for i in range(100000):
    l[i]._spawn
e = time.perf_counter()
print(e-s, "Test()._spawn")

""" TypeManager.virtual_function Test"""

manager = TypeManager()

class Test(CustomType, metaclass=manager):
    blind = manager.virtual_function(551, (DataType.FLOAT, DataType.FLOAT, DataType.FLOAT))

pointer = Entity.find("player").pointer
test = make_object(Test, pointer)
test.blind

s = time.perf_counter()
for i in range(100000):
    test.blind
e = time.perf_counter()
print(e-s, "test.blind")

l = [make_object(Test, pointer) for i in range(100000)]

s = time.perf_counter()
for i in range(100000):
    l[i].blind
e = time.perf_counter()
print(e-s, "make_object(Test, pointer).blind")

Since the implementation has changed, the test results have also changed. Output :

TypeManager.function Test
4.355441149324179 Test._spawn
4.830625455826521 test._spawn
4.801660872995853 Test()._spawn
↓
OLD: 0.02216128632426262 Test._spawn
OLD: 0.008792959153652191 test._spawn
OLD: 0.2989997826516628 Test()._spawn

NEW: 0.023133378475904465 Test._spawn
NEW: 0.29126785323023796 test._spawn
NEW: 0.293557733297348 Test()._spaw

TypeManager.virtual_function Test
0.5497394129633904 test.blind
0.5683586820960045 make_object(Test, pointer).blind
↓
OLD: 0.008566077798604965 test.blind
OLD: 0.5120681896805763 make_object(Test, pointer).blind

NEW: 0.46108797565102577 test.blind
NEW: 0.4727563001215458 make_object(Test, pointer).blind