Need some help improving my CPU usage



  • Hello everyone,

    I have 3 rooms and i'm already using an average of 11CPU per tick, with higher peaks.

    I've been braking my mind trying to improve cpu usage, but my skills don't help so i decided to ask for some help here.

    These are my files, in case someone wants to help:

    (Link deleted. However if for some reason someone is interested on having my files it'll be a pleasure to link them again)

    P.S. Sorry if you guys think this is an abuse, but i'm really reaching my limit, and any tip or way to follow would be much appreciated. Thanks.



  • I did not read your files, but every gamestate changing action has a cost of 0,2 CPU if "ordered". With your Code you can reduce overhead, but not overcome this. Just count your intents and log CPU used, to see if and where you can optimize.



  • This post is deleted!


  • @saruss Thanks for the reply. I read you yesterday but forgot to answer.

    At this moment i'm trying to follow tips given here: https://github.com/bonzaiferroni/screepswiki/wiki/%23cpu-clinic-faq, and i already got a small improvement, but there's still a lot to do there hehe



  • I tried looking through the code, but with no comments I honestly don't know much about what's going on.

    My rooms do about 3-5 CPU per, with remote harvesting about 1 CPU per room. What I did is I went and put Game.cpu.getUsed(); everywhere...I would get the amount before the command or method and then get the CPU afterwords to find out where all my CPU was going.

    For instance, I found out that _.filter commands took more cpu than just room.find() commands. I also would fill up a spawnQueue with everything I needed, but my spawns could only do one at a time, so now my spawnQueue calculations end as soon as I've found one creep that needs to be spawned and it can be spawned.

    my Creeps were taking up the mass majority of my CPU though. Harvesters would try to harvest if there was no energy or it wasn't in range. Carts would attempt to transfer if they weren't in range. All my creeps now use if(creep.pos.inRangeTo(source, 1)) creep.harvest(source) instead of the error checking it did before that would call the command everytime.

    Good luck!

    👍


  • @famine wow thanks a lot! I didn't know Game.cpu.getUsed(); exists. Now i'll try to use it to find out what's really going on there.

    Regarding the spawn queue, it's something i'm not doing, and for sure i will. It seems quite easy to do.

    And about the things creeps try to do while out of range, i do ...

    let farmResult = creep.harvest(source);
    if(farmResult == ERR_NOT_IN_RANGE){
        creep.moveTo(source);
    }
    

    ...so i think i'm trying to harvest every tick redardless the distance to the source. I'll work on this too.



  • @Famine intents only cost 0.2 if it returns OK after scheduling it. When the source is empty or out of range it triggers errors, meaning it shouldn't cost as much.

    @Calfa a lot of new players have their creeps "reconsider" what to do every tick. There's a lot of overhead there. Consider looking for a way to save the "task" they choose so that after starting to do something, they can keep working every tick without as much CPU usage.

    An example of this would be [MOVE, CARRY] creeps looking for the nearest energy to pick up every tick, instead of finding a target once, saving it, and reusing that every tick.



  • I highly recommend screeps-profiler. Much easier than messing with Game.cpu.getUsed() by hand!



  • @keenathar a lot of new players have their creeps "reconsider" what to do every tick. There's a lot of overhead there. Consider looking for a way to save the "task" they choose so that after starting to do something, they can keep working every tick without as much CPU usage.

    That's exactly what i do.

    As an example, my "refillers" (which purpose is to transport energy while other creeps dropmine) do...

    if i carry 0 energy -> i'm empty
    else if i carry all energy i can -> i'm full
    
    if(i'm full)
        if i find enemies
            try to unload at towers
        else
            try to unload at extensions or spawns
            if i can't and i still have room available
                try to pick dropped energy
                if i can't
                    try to unload wherever you can
    
    else if i'm empty
        try to pick dropped energy
        if i can't
            load energy from nearest container or storage
    

    As you suggested, i'll try yo rewrite as many code as i can to have my creeps choosing a task and not wondering about anything until they finished it. It sounds less efficient to me, but i'll give it a try.

    Thanks!!

    @SystemParadox I have Screeps-Profiler, but i just know how to Game.profiler.profile(xxx);. And then i don't know how to go further in order to know where exactly a big amount of certain calls come from.

    Thanks again guys, i really really appreciate!



  • @calfa said in Need some help improving my CPU usage:

    As you suggested, i'll try yo rewrite as many code as i can to have my creeps choosing a task and not wondering about anything until they finished it. It sounds less efficient to me, but i'll give it a try.

    In fact, I have recently finished a rewrite of my just workers code from the model where each creep would try to look for things to do into a more centralized "dispatcher", that would also be aware what other creeps doing and only dispatch as many workers as necessary. And that resulted in a significant CPU use decrease, moreover, stuff is now indeed done more efficiently as well, because instead of all workers zooming off to do a small task, only one or two do, while others continue doing whatever they were doing at that time (or sitting at spawn and singing kumbaya).



  • I reply myself in case it's useful for someone else in the future.

    I'm working on each creep's role code to prevent them from trying to do the same thing each tick, like when using...

    let repairResult = creep.repair(target);
    if(repairResult == ERR_NOT_IN_RANGE){
        creep.moveTo(target);
    }
    

    Also, i have a module called "setMemory" in which i check if there are damaged structures, construction sites, spawns, energy sources, containers next to an energy source, enemies and a lot more stuff.

    I divided that module into parts like...

    if(Game.time % 5 == 0){
        //Here i look for enemies, construction sites, damaged structures, ...
    }   
    if(Game.time % 20 == 0){
         //Here i look for my towers, containers, ...
    }
    if(Game.time % 100 == 0){
        //Here i look for spawns, energy sources and stuff that doesn't change much...
    }
    

    So far, i'm managing 2 rooms with controller level 3~4 with 4~5 cpu. Each of those rooms has2 miners, 3 upgraders, 1 hauler and depending on the amount of construction sites/damaged structures it also has 1~2 builders and 1~2 repairers. I don't really know if it's cool or still much cpu usage, but comparing to what i was doing few days ago, it's f***** awesome!

    Thanks a lot guys for your help!!! I really really appreciate it!!!



  • As you suggested, i'll try yo rewrite as many code as i can to have my creeps choosing a task and not wondering about anything until they finished it. It sounds less efficient to me, but i'll give it a try.

    Trust me, you're saving a lot of processing 🙂 In your old situation each creep would search for enemies, for example, to determine if it should fill a tower. Those searches are moderately expensive, and literally a repeat of what an earlier hauler was already doing. Finding a way to cache that really helps.

    Additionally, the creeps are re-checking every tick. So not only do they technically find a new target every tick (with all cpu costs involved), it's also possible for them to switch targets really inefficiently.
    For example, if your creep is bringing energy to an extension but an enemy scout walks in, that enemy gets shot by the tower. Your hauler then turns around and wants to refill the tower instead. However, is it really that bad that the tower is at 990 energy instead of 1000? If you do, your creep has wasted a lot of its life first walking to the extension and then turning around, before having filled it. With a "saved" task, it will just fill the extension and only then see what to do.

    So if I understand you correctly, your new system searches for all enemies and objects on the room level and then lets the creeps use that data. That's already a huge improvement in removing duplicate searches!

    There's only one thing I'm not sure you've considered: the way you currently check for the different "categories" (% 5, % 20 and % 100), all these searches potentially happen on the same tick. Because 100 % 20 == 0 and 100 % 5 == 0, they are guaranteed to trigger together. A simple trick is using an offset for Game.time. If you check for Game.time % 5, (Game.time + 1) % 20 and (Game.time + 2) % 100 they no longer trigger in the same tick. So the cpu is spread out more evenly. In a small colony that won't really matter, but later on it might be helpful.



  • @keenathar I've been working on finishing tasks for the last days with a tiny success. Repair and Build are examples of what i got. Now i store targetToRepair in creep's memory and only when it runs out of energy and fills itself again will look for a new target which need to be repaired more urgently. For building, "targetToBuild" will remain in memory until it's finished.

    My "new" issue is for stuff regarding "loading" and "unloading" energy, because i don't really know how to keep this in memory (it all became a little big to me xD)

    The following are my modules for Loading and Unloading energy

    let loadEnergy = {
    
        /*
        creep:      the creep calling the function
        args:       a string telling the function which type of structure i want it to load from
        min:        minimum amount of energy the structure must have in order to be considered as a valid one
        maxDist:    the maximum distance from the creep in order to be considered as a valid one (a piece of shit actually, so i normally use Infinity)
        REUSE_PATH is a constant i set to 10 to see if i had any improvement on performance. Hard to say, tbh
        */
            run: function(creep, args, min, maxDist){
                let fromContainers = false;
                let fromStorages = false;
                let fromLinks = false;
                let fromSpawns = false;
                let fromTowers = false;
                let fromExtensions = false;
                if(args && args.length){
                    for (let i in args){
                        switch(args[i]){
                                case 'extensions'   :   {fromExtensions = true;}    break;
                                case 'containers'   :   {fromContainers = true;}    break;
                                case 'storages'     :   {fromStorages = true;}      break;
                                case 'links'        :   {fromLinks = true;}         break;
                                case 'towers'       :   {fromTowers = true;}        break;
                                case 'spawns'       :   {fromSpawns = true;}        break;
                                case 'any'          :   {fromExtensions = false;
                                                        fromContainers = true;
                                                        fromStorages = true;
                                                        fromLinks = true;
                                                        fromTowers = true;
                                                        fromSpawns = false;}        break;
                                                        
                                default:                console.log('Invalid input at LoadEnergy(): '+args[i]);     break;
    
                        }
                    }
                }
                let target = null;
                target = creep.pos.findClosestByPath(FIND_STRUCTURES, {
                    filter: function(structure){
                        return (
                            (structure.structureType == STRUCTURE_STORAGE        && _.sum(structure.store) > min &&  fromStorages)      ||
                            (structure.structureType == STRUCTURE_CONTAINER      && _.sum(structure.store) > min &&  fromContainers  && creep.room.memory.mineralContainer != structure.id)    ||
                            (structure.structureType == STRUCTURE_TOWER          &&       structure.energy > min &&  fromTowers)        ||
                            (structure.structureType == STRUCTURE_LINK           &&       structure.energy > min &&  fromLinks)         ||
                            (structure.structureType == STRUCTURE_SPAWN          &&       structure.energy > min &&  fromSpawns)        ||
                            (structure.structureType == STRUCTURE_EXTENSION      &&       structure.energy > min &&  fromExtensions)
                        );
                    }
                });
    
    //Creep has already found the closest target to load from, but if it's still further than maxDist, it won't go :/
                if(target != null && creep.pos.getRangeTo(target.pos) <= maxDist){
                    let loadResult;
                    if(creep.pos.isNearTo(target)){
                        creep.withdraw(target, RESOURCE_ENERGY);
                    }else{
                        creep.moveTo(target, {reusePath: REUSE_PATH, visualizePathStyle: {stroke: '#ffffff'}});
                        //Usually set to false
                        if(CREEPS_SPEAK){
                            creep.say('⬆');
                        }
                    }
                }else{
                    return 'noTarget';
                } 
            }
        }
    module.exports = loadEnergy;
    

    And for unloading....

    let unloadEnergy = {
    
    /*
        creep: again the creep calling the module
        args: a string representing the structures to be considered as valid
    */
        run: function(creep, args) {
            //Where to unload
            let atContainers = false;
            let atLinks = false;
            let atStorages = false;
            let atSpawns = false;
            let atTowers = false;
            let atExtensions = false;
            let atTerminals = false;
            if(args && args.length){
                for(let i in args){
                    switch(args[i]){
                        case 'containers':      atContainers = true;        break;
                        case 'links':           atLinks = true;             break;
                        case 'storages':        atStorages = true;          break;
                        case 'spawns':          atSpawns = true;            break;
                        case 'towers':          atTowers = true;            break;
                        case 'extensions':      atExtensions = true;        break;
                        case 'terminals':       atTerminals = true;         break;
                        case 'any':             atContainers = true;
                                                atLinks = true;
                                                atStorages = true;
                                                atSapwns = true;
                                                atTowers = true;
                                                atExtensions = true;
                                                atTerminals = true;     break;
                        default:                console.log('Invalid input at UnloadEnergy(): '+args[i]);       break;
                    }
                }
            }
            let target = creep.pos.findClosestByPath(FIND_STRUCTURES, {
                filter: function(structure){
                    return (
                        (structure.structureType == STRUCTURE_LINK && atLinks) ||
                        (structure.structureType == STRUCTURE_CONTAINER && _.sum(structure.store) < structure.storeCapacity && (structure.storeCapacity - _.sum(structure.store) > creep.carry.energy) && atContainers)    ||
                        (structure.structureType == STRUCTURE_STORAGE && _.sum(structure.store) < structure.storeCapacity && (structure.storeCapacity - _.sum(structure.store) > creep.carry.energy) && atStorages)      ||
                        (structure.structureType == STRUCTURE_TERMINAL &&  _.sum(structure.store) < structure.storeCapacity && atTerminals)     ||
                        (structure.structureType == STRUCTURE_SPAWN && structure.energy < structure.energyCapacity && atSpawns)        ||
                        (structure.structureType == STRUCTURE_TOWER && structure.energy < structure.energyCapacity && atTowers)        ||
                        (structure.structureType == STRUCTURE_EXTENSION && structure.energy < structure.energyCapacity && atExtensions)
                    );
                }
            });
            if(target != null) {
                let unloadResult;
                if(creep.pos.isNearTo(target)){
                    unloadResult = creep.transfer(target, RESOURCE_ENERGY);
                    if(CREEPS_SPEAK){
                        creep.say('⬇');
                    }
                }else{
                    creep.moveTo(target, {reusePath: REUSE_PATH, visualizePathStyle: {stroke: '#ffffff'}});
                    unloadResult = ERR_NOT_IN_RANGE;
                }
                return unloadResult;
            }else{
                return 'noTarget';
            }
        }
    };
    module.exports = unloadEnergy;
    

    I tried to write it as clear as possible, sorry if it isn't clear enough.

    Can you show me the way to do this lighter? I bet one hand these two modules are my biggest issue right now regarding cpu usage, because almost every creep i have uses them, and they are permanently asking for closest stuff and so on.



  • Finally!!!

    Thinking of it was vastly harder than doing it once i figured out how to. Only added a few lines, and cpu usage came down from 22~23 to about 19!

    Next thing i'm doing is a review of all tips you guys gave me, and maybe tomorrow i give a try to Bonzaiferroni - Traveler.

    I'll change this to "solved" but any tip you could give me will still be very welcome.

    Cheers!!



  • Hi again!

    Although I marked it as "Solved", because biggest issue is solved, i'm still trying to improve cpu usage. I just used profiler and found that:

    In first place, i have "moveTo", so i'm just giving a try to "Traveler".

    In second place i have "harvest" and don't think there's anything i can do about it, because only creeps harvesting are miners (all of them drop-mining. Some of them in my own rooms and a few more in neutral rooms) and they do "harvest" once per tick/creep and only when isNearTo(source).

    And in 3rd and 4rth place i found this...

    2089		416.5		0.199		Creep.move
    1672		350.9		0.210		Creep.moveByPath
    

    But i didn't use any of those in my code...why are they there?

    Also, how can i use profiler in a single module rather than using it on the main module? (like...

    Game.profiler.profile(loadEnergy, 500);
    

    Thank you!!! 😄



  • Creep.move and Creep.moveByPath are called by Creep.moveTo, as you can see here. You should also be aware that most profilers measure cumulative execution time, so the true average cost of Creep.moveByPath is about 0.02, as 0.199 is taken up when it calls Creep.move.



  • @muon Oh i see, should've supposed. Thanks!!

    Profling again with Traveler On, let's see 😄



  • I'll keep upgrading this post as i keep finding ways to improve CPU usage. Eventually, someone else will search for posts like this, and hope it can be useful for him/her.

    I use the drop-mining system and although i was already storing in memory each miner's source, i was still doing...

    if(creep.pos.isEqualTo(myContainer.pos)){
        creep.harvest(mySource);
    }
    

    The problem with this is that creep was checking for its position every tick, so when i realized, i started doing this:

    if(creep.memory.inPosition == true){
        creep.harvest(Game.getObjectById(creep.memory.mySource));
    }else{
        let myContainer = Game.getObjectById(creep.memory.myContainer);
        if(creep.pos.isEqualTo(myContainer.pos)){
            //When miners are created, in their memory mySource equals 0.
            if(creep.memory.mySource == 0){
                creep.memory.mySource = creep.pos.findClosestByPath(FIND_SOURCES).id;
            }else{
                creep.memory.inPosition = true;
        }else{
            //Using Bonzai Ferroni Traveler. Otherwise would just use moveTo() instead.
            creep.travelTo(myContainer);
        }
    }
    

    This way, once a miner reaches its mining position, "inPosition" will be set to true and for the rest of its lifetime it will only check for this boolean and if true will harvest, reducing the amount of cpu used.

    Another thing that might be obvious to you, but wasn't to me, is that i've been creating the same creeps from controller level 3 to controller level 6. The problem is that the amoun of other structures has increased, so there are many other new things happening. I considered creating bigger creeps but then i thought...why would i create bigger upgraders (an example) if those are doing fine? The answer is that a single bigger creep runs code once, while 3 smaller creeps run the same code 3 times.

    A similar principle applies to creeps which job is to reserve a room. Rather than continuously create creeps with 2 CLAIM parts, i will create (thanks @Robalian! ) a creep with some more CLAIM parts. Instead of creating it when the previous one dies (each 600 ticks), i will create them each few thousand ticks and it will bring controller to 5k very fast, reducing the average cpu consumed by this sort of creeps.

    As i said, hope this helps someone some day!!



  • If you're really tight for CPU you can take this even further, creating oversized creeps with more parts than they need allows them to finish their work with fewer actions and save CPU.

    This is how I managed to run 9 rooms with 10 CPU - I created harvesters with 9 WORK instead of 6 so they would make fewer calls to creep.harvest and cost less CPU. The energy wasted by having the extra parts was far outweighed by the ability to mine more sources. At one point I had them with 12 WORK, which is still a net benefit if you're right up to the CPU cap, but feels a bit excessive.

    👍


  • @systemparadox Thanks for the tip!! At this moment my cpu is near the limit, but i haven't modiffied the creep's number/size. In a future that's the next thing i'll do. Thank you so much!!