reduce CPU usage dramatically

I noticed that on tilde.town users with a high botany score used up a
lot of CPU cycles. I skimmed through the code and didn't immediately see
any tight loops, but after profiling against a user's borrowed .botany
directory I saw the culprit: the score increase thread.

This thread was designed to increase the user's score by 1 every time
the thread did an iteration of its infinite loop. It would sleep for an
interval scaled *down* based on how high a user's generation bonus was.
This meant that the sleep interval trended towards zero, creating a
tight loop for high scoring users.

This commit changes the code to use a constant sleep inteveral but scale
the score increment *up* based on generation.

I also removed the death check thread entirely since we were already
checking for death in the score thread. I also short circuited the death
check.

This had the effect of reducing CPU load for a high scoring user by a
factor of about 50.
pull/53/head
Nate Smith 2023-12-06 20:56:11 -08:00
parent 99c1fda072
commit ed7498bd4a
2 changed files with 16 additions and 26 deletions

View File

@ -4,11 +4,9 @@ import time
import pickle import pickle
import json import json
import os import os
import random
import getpass import getpass
import threading import threading
import errno import errno
import uuid
import sqlite3 import sqlite3
import menu_screen as ms import menu_screen as ms
from plant import Plant from plant import Plant
@ -16,7 +14,9 @@ from plant import Plant
# TODO: # TODO:
# - switch from personal data file to row in DB # - switch from personal data file to row in DB
# - is threading necessary? # - is threading necessary?
# - reduce CPU usage # - use a different curses window for plant, menu, info window, score
# notes from vilmibm
# there are threads. # there are threads.
# - life thread. sleeps a variable amount of time based on generation bonus. increases tick count (ticks == score). # - life thread. sleeps a variable amount of time based on generation bonus. increases tick count (ticks == score).
@ -41,7 +41,6 @@ from plant import Plant
# - exit # - exit
# quits program # quits program
# part of the complexity of all this is everything takes place in one curses window; thus, updates must be manually synchronized across the various logical parts of the screen. # part of the complexity of all this is everything takes place in one curses window; thus, updates must be manually synchronized across the various logical parts of the screen.
# ideally, multiple windows would be used: # ideally, multiple windows would be used:
# - the menu. it doesn't change unless the plant dies OR the plant hits stage 5, then "harvest" is dynamically added. # - the menu. it doesn't change unless the plant dies OR the plant hits stage 5, then "harvest" is dynamically added.
@ -91,25 +90,10 @@ class DataManager(object):
def start_threads(self,this_plant): def start_threads(self,this_plant):
# creates threads to save files every minute # creates threads to save files every minute
death_check_thread = threading.Thread(target=self.death_check_update, args=(this_plant,))
death_check_thread.daemon = True
death_check_thread.start()
autosave_thread = threading.Thread(target=self.autosave, args=(this_plant,)) autosave_thread = threading.Thread(target=self.autosave, args=(this_plant,))
autosave_thread.daemon = True autosave_thread.daemon = True
autosave_thread.start() autosave_thread.start()
def death_check_update(self,this_plant):
# .1 second updates and lock to minimize race condition
while True:
is_dead = this_plant.dead_check()
if is_dead:
self.save_plant(this_plant)
self.data_write_json(this_plant)
self.update_garden_db(this_plant)
self.harvest_plant(this_plant)
this_plant.unlock_new_creation()
time.sleep(.1)
def autosave(self, this_plant): def autosave(self, this_plant):
# running on thread, saves plant every 5s TODO: this is unnecessary # running on thread, saves plant every 5s TODO: this is unnecessary
# and breaks shit probably # and breaks shit probably

View File

@ -3,6 +3,8 @@ import os
import json import json
import threading import threading
import time import time
import uuid
import getpass
class Plant: class Plant:
# This is your plant! # This is your plant!
@ -187,6 +189,8 @@ class Plant:
return rarity return rarity
def dead_check(self): def dead_check(self):
if self.dead:
return True
# if it has been >5 days since watering, sorry plant is dead :( # if it has been >5 days since watering, sorry plant is dead :(
time_delta_watered = int(time.time()) - self.watered_timestamp time_delta_watered = int(time.time()) - self.watered_timestamp
if time_delta_watered > (5 * (24 * 3600)): if time_delta_watered > (5 * (24 * 3600)):
@ -327,10 +331,12 @@ class Plant:
def life(self): def life(self):
# I've created life :) # I've created life :)
generation_bonus = round(0.2 * (self.generation - 1), 1)
score_inc = 1 * (1 + generation_bonus)
while True: while True:
if not self.dead: if not self.dead:
if self.watered_24h: if self.watered_24h:
self.ticks += 1 self.ticks += score_inc
if self.stage < len(self.stage_list)-1: if self.stage < len(self.stage_list)-1:
if self.ticks >= self.life_stages[self.stage]: if self.ticks >= self.life_stages[self.stage]:
self.growth() self.growth()
@ -340,10 +346,10 @@ class Plant:
# Do something # Do something
pass pass
if self.dead_check(): if self.dead_check():
# Do something else self.save_plant(this_plant)
pass self.data_write_json(this_plant)
self.update_garden_db(this_plant)
self.harvest_plant(this_plant)
this_plant.unlock_new_creation()
# TODO: event check # TODO: event check
generation_bonus = round(0.2 * (self.generation - 1), 1) time.sleep(2)
adjusted_sleep_time = 1 / (1 + generation_bonus)
time.sleep(adjusted_sleep_time)