Created
August 22, 2021 01:45
-
-
Save j-mao/1d833c66fc72c773c28c6ecf272e4d02 to your computer and use it in GitHub Desktop.
POC Battlecode Python engine without bytecode rewriting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import importlib | |
import sys | |
import threading | |
EXCEPTION_BYTECODE_PENALTY = 500 | |
class Unit: | |
def __init__(self, module, bytecode_limit): | |
self.module = module | |
self.bytecode_limit = bytecode_limit | |
self._bytecodes_used = 0 | |
self._thread = threading.Thread(target=self._run, daemon=True) | |
self._turn_start = threading.Condition() | |
self._turn_end = threading.Condition() | |
self._turn_running = False | |
self._age = 0 | |
def step(self): | |
with self._turn_start: | |
self._turn_running = True | |
if self._thread.is_alive(): | |
self._turn_start.notify() | |
else: | |
self._thread.start() | |
with self._turn_end: | |
while self._turn_running: | |
self._turn_end.wait() | |
return self._bytecodes_used | |
def kill(self): | |
# TODO: how to do this? possibly need multiprocess instead of thread | |
# can we multiprocess without terrible message-passing | |
pass | |
def _run(self): | |
if self.module in sys.modules: | |
# FIXME: Very likely insecure and hackable | |
del sys.modules[self.module] # force reimport | |
try: | |
sys.settrace(self._instrument) | |
player = importlib.import_module(self.module) | |
player.run(self._end_turn) | |
# FIXME: instrumenter includes overhead due to importlib | |
# NOTE: even a naked import statement costs >2000 bytecode, is this ok? | |
# FIXME: pycache makes the import not have constant cost | |
finally: | |
sys.settrace(None) | |
def _instrument(self, frame, event, arg): | |
if event == 'call': | |
# TODO: how to instrument C functions? eg. builtins, the math library, etc | |
# Likely need to use sys.setprofile to get 'c_call' event | |
# Would then need to enumerate the bytecode costs for all C functions | |
if frame.f_code is self._end_turn.__code__: | |
return None # Do not instrument the _end_turn function | |
frame.f_trace_opcodes = True | |
if event == 'opcode': | |
self._bytecodes_used += 1 | |
if event == 'exception': | |
# FIXME: This might multi-count an exception if it jumps over multiple frames | |
self._bytecodes_used += EXCEPTION_BYTECODE_PENALTY | |
while self._bytecodes_used >= self.bytecode_limit: | |
self._end_turn() | |
return self._instrument | |
def _end_turn(self): | |
sys.settrace(None) | |
self._age += 1 | |
with self._turn_end: | |
self._turn_running = False | |
self._turn_end.notify() | |
with self._turn_start: | |
while not self._turn_running: | |
self._turn_start.wait() | |
self._bytecodes_used = max(self._bytecodes_used-self.bytecode_limit, 0) | |
sys.settrace(self._instrument) | |
def run_match(players, turn_count, bytecode_limit): | |
runners = [Unit(m, bytecode_limit) for m in players] | |
for i in range(turn_count): | |
for r in runners: | |
bytecodes = r.step() | |
print(">>> Ending", r._age, bytecodes) | |
if __name__ == '__main__': | |
run_match(["player", "player"], 20, 10000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
a = 0 | |
def run(end): | |
global a | |
while True: | |
a += sum(i for i in range(10000)) # something expensive | |
print("the value is", a) | |
end() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment