Modularity/File Size vs. Efficiency
Moderator: Moderators
Modularity/File Size vs. Efficiency
I'm just wondering what you would consider more important in making a game: Reducing file size or more efficiency.
Really, do whatever you need to fit the size and frame rate targets that you have set. If your game is 132 KiB, and your publisher wants you to get it down to 128 KiB so that it will fit in the smaller ROM chip, you might need to cut out some lookup tables. If you want to have a lot of big levels, space efficiency of the map encoding is important. If a lot of critters in one area are causing the CPU to take longer than 29,000 cycles to make a frame, you'll need to find some tradeoffs to get more speed.
Not everybody's targets are the same. Micronics games, for instance, appear to trade off frame rate for development time.
Not everybody's targets are the same. Micronics games, for instance, appear to trade off frame rate for development time.
Most important: it's fun. Depending on what approach you take, you may need to work on reducing data size (if it's a game with large maps), improving efficient (if it involves lots of objects on screen and fast action), or something else entirely (perhaps coming up with a design that can handle the complexity of a turn-based game).
tepples is always faking that we're commercial developers. He probably took that habit from GBAdev.Publisher? lolwut?
Not that at some point we're all doing this - tepples just say it loud.
Personally I hate frame rate drops so i'd rather have a "bigger" file (anyways even if you make a VERY large 512k game it's really small by todays standards).
Useless, lumbering half-wits don't scare us.
I wouldn't be saying it out loud if it weren't for Sivak and bunnyboy. (No, not that bunnyboy.) Then again, I meant "publisher" in a fairly broad sense. For example, if you are entering a 16K competition, the compo sponsor is your publisher, and you'll need to fit everything into 16384 bytes.Bregalad wrote:tepples is always faking that we're commercial developers. He probably took that habit from GBAdev.
Not that at some point we're all doing this - tepples just say it loud.
For me right now efficiency is the most important. My game does complicated things, and it needs to take a lot of space to do what it does at all. It sometimes needs to take even more to do it fast. The routine that runs the main character is 3.75 kilobytes or something like that.
However I code for space on the side, since I also want more levels. So I take a page from the atari programmer's book, and turn jmps into branches whenever possible. (Which saves a byte. Saved bytes add up though. I've saved enough already for at least a medium sized level)
Sometimes it's easy like this:
becomes
Sometimes it's trickier like realizing no instructions change the carry flag in between a cmp, sec, sbc etc and a jmp. Then changing the jmp to a branch that brances on the condition that the carry flag with definitely be.
I do some other probably crazy things to save space. But mostly I'm about speed.
If I was doing something else I'd work on saving space, since I think it's kind of fun, actually. So yeah, depends on the game.
However I code for space on the side, since I also want more levels. So I take a page from the atari programmer's book, and turn jmps into branches whenever possible. (Which saves a byte. Saved bytes add up though. I've saved enough already for at least a medium sized level)
Sometimes it's easy like this:
Code: Select all
bne label
jmp label2
Code: Select all
bne label
beq label2
I do some other probably crazy things to save space. But mostly I'm about speed.
If I was doing something else I'd work on saving space, since I think it's kind of fun, actually. So yeah, depends on the game.
I usually optimize for speed, but I can't completely forget about the size. Even though mappers allow you to have a lot of PRG-ROM, the addressing space of the 6502 is still pretty small, and only 32KB of PRG can be mapped at any given time, which means that the way your code and data are organized will haven an impact on the efficiency of the program as well.
I often use branches instead of jumps when a flag (N, C, Z or V) is guaranteed to always have the same value at that point, even if I have to shuffle some instructions around to make sure that value is constant.
Another thing that helps, one byte at a time, is not doing CLC or SEC before additions and subtractions if the value of the carry is known, even if that means compensating the added/subtracted value. For example, if I want to add $20 to a variable but the carry is always set, I add $1F instead.
That's the kind of optimization I do when a block of code is already working without any "tricks" though, because sometimes if you do them while still prototyping the logic you might end up changing the way a flag behaves and screw up the rest of the logic, and that kind of bug can be hard to find.
I often use branches instead of jumps when a flag (N, C, Z or V) is guaranteed to always have the same value at that point, even if I have to shuffle some instructions around to make sure that value is constant.
Another thing that helps, one byte at a time, is not doing CLC or SEC before additions and subtractions if the value of the carry is known, even if that means compensating the added/subtracted value. For example, if I want to add $20 to a variable but the carry is always set, I add $1F instead.
That's the kind of optimization I do when a block of code is already working without any "tricks" though, because sometimes if you do them while still prototyping the logic you might end up changing the way a flag behaves and screw up the rest of the logic, and that kind of bug can be hard to find.
Optimizations like doing BEQ BNE instead of BEQ JMP are pretty silly, unless it's in a macro that's used hundreds of times. Focus your efforts on things that yield hundreds of bytes of savings. Things like this are fragile (change the BEQ to something else and it breaks if you aren't watching). Others, like avoiding a CLC or SEC, are useful only in critical loops where a few cycles saved is significant. Everywhere else they just make the code more brittle. Given that assembly code is some of the hardest to debug, you don't need techniques like this making it even harder. I think most cases of this have ill effects, especially when people here use them in introductory NES code of all places.
tokumaru has it exactly right: If you're going to do things like this, do them last, and only when you can see that they will make a noticeable difference in speed or code size. Otherwise, you're de-optimizing clarity. Always remember that downside.
tokumaru has it exactly right: If you're going to do things like this, do them last, and only when you can see that they will make a noticeable difference in speed or code size. Otherwise, you're de-optimizing clarity. Always remember that downside.
My current savings by using branches instead of jmps is 143ish bytes. Nothing to sneeze at. Space is space. That's a lot of animations or a level to three. (In my formats)
Another thing I neglected to mention is to tag your branches that used to be jmps with a comment. This is so if more added code brings the label the branch points to out of range, you just turn it into a jmp, rather than the first instinct of creating an extended branch which takes up both more cycles AND bytes. (But of course this is avoided entirely if you convert jmps to branches last.)
I do agree it sometimes makes code less readable in the less obvious cases, and you should only do it when you're sure the logic in place is set in stone.
As for avoiding clc and sec, it can indeed save you a lot of cycles in loops. clc/sec before a loop instead of during every iteration is simple enough. My first 8-bit division routine took 214 cycles (Max). After optimization, it takes 133 (Max). (I used to divide a lot, so this was VERY important. I might be able to do it even faster if I bring the tile type back that used division)
Other than during loops, I'll get rid of them if I see it, but I don't actively look for instances where I can avoid them. They're used so seldom the byte (and cycle savings) don't often make it worth it.
I'm not advocating being careless. Be REALLY sure before you make a change like any of these. I have stuff like this comment in my code:
I'm pretty sure all paths that lead to that label push a clear carry byte to the stack, but I'm not sure. If I REALLY need that byte later, I can always come back
Never make a change you're not sure of for one byte. I'm not saying that.
Another thing I neglected to mention is to tag your branches that used to be jmps with a comment. This is so if more added code brings the label the branch points to out of range, you just turn it into a jmp, rather than the first instinct of creating an extended branch which takes up both more cycles AND bytes. (But of course this is avoided entirely if you convert jmps to branches last.)
I do agree it sometimes makes code less readable in the less obvious cases, and you should only do it when you're sure the logic in place is set in stone.
As for avoiding clc and sec, it can indeed save you a lot of cycles in loops. clc/sec before a loop instead of during every iteration is simple enough. My first 8-bit division routine took 214 cycles (Max). After optimization, it takes 133 (Max). (I used to divide a lot, so this was VERY important. I might be able to do it even faster if I bring the tile type back that used division)
Other than during loops, I'll get rid of them if I see it, but I don't actively look for instances where I can avoid them. They're used so seldom the byte (and cycle savings) don't often make it worth it.
I'm not advocating being careless. Be REALLY sure before you make a change like any of these. I have stuff like this comment in my code:
Code: Select all
velmask.highmid.pos:
bne velmask.highmidstart2
plp
jmp velmask.end;bcc would probably work, but I'm scared to try it
Last edited by Kasumi on Mon Jul 05, 2010 6:11 pm, edited 1 time in total.