Performance Best Practices

I feel this should be a page somewhere, but can’t find anything…

Are there obvious things to avoid when constructing I7 code to keep turn processing as fast as possible?

1 Like

Don’t iterate over all or many objects each turn. Cache the results of expensive computations instead of doing them repeatedly. If possible, precompute them and hardcode the results into the game.

This thread detailing Zarf’s performance tweaks for Hadean Lands might help. (Not sure to what extent it still applies to 6M62.)

1 Like

The ones I know about:

Avoid setting relations you don’t need. Extensive various-to-various relations are allegedly expensive memory-wise.

I also try to avoid giving everything in the world a default variable unless necessary. “A thing can be burnable” instead of “A thing is fireproof or burnable”.

I’ve also encountered problems with lots of “Every turn…” rules, and in the future hope to try to keep one section of concise every turn rules instead of using it blithely all over the code.

David, thanks bringing this up! It’s something I have been wondering about too.

Only thing I know:
The Inform 7 Handbook by Jim Aikin has a sub-chapter on Code Optimization. It advices you to avoid Before, Instead and After, since these rules are checked with each of the players command. There’s some tips to coding alternatives. It also warns against Every Turn and After reading a command.

1 Like

I think the advice is to avoid general or non-specific Before, Instead, After, Every Turn, and After Reading a Command rules. The more specific you can make a rule, the better off you are.

So, if a rule only means something in the Laboratory, and the rule needs to be checked every turn, this:

Every turn when the location is the Laboratory: say "The chemicals bubble furiously."
is preferred to this:

Every turn: If the location is the Laboratory: say "The chemicals bubble furiously."

It is also a good idea to look at rules that are particularly complex for ways to simplify them. Inform, and the various interpreters appear to be happier when you can take a rule with 27 lines and break it into smaller rules.

Of those two every-turn examples, the first is shorter and tidier but there’s no performance difference.

I’ll post a couple of things I noticed while working on my CYOA extension and which come from another thread about lists VS tables:

"My own test showed writing data to a huge list was considerably slower than doing so in a table with the same number of entries. I should have written this stuff down… you know, I did 100 and 1000 and maybe 10,000. Somwehere between 100 as we moved up towards 10,000, the list time overtook the table time very significantly. But that’s just building the list. (EDIT - this was significant for me because I had data structures I was rebuilding every turn from scratch. Later I switched to monitoring more of the project’s state so that the extension defaulted to NOT rebuilding every turn, but rebuilding only when particular changes occurred.)

Someone else here recently showed that looking things up in tables, especially if items were near the end, was surprisingly taxing. But that may have only been a surprise relative to what was expected : )"

The original thread was https://intfiction.org/t/i7-lists-or-table/9726/3

-Wade

EDIT PS: Also, regular expressions. Minimise them and be specific with them, and use lower-powered built-in Inform equivalents to either replace them, or screen for the need to use them, when possible.

All (indexed) text code is slow, but there’s lot of room for improvement by rewriting the implementation, if anyone is keen :wink:

If you have a lot of relations between objects which are not part of the model world you can sometimes get a lot of improvement by making the relations an alias of the containment relation. Making that change made Flexible Windows 30 times faster.

Okay, what I understood so far is this:

AVOID STUFF THAT RUNS EACH TURN:
Every turn and After reading a command should be used with caution.
But also, all before, Instead and After rules are consulted for each of the players commands. So try to replace them with specific rules like Check, Carry Out or Report … (source: Inform 7 handbook by Jim Aikin, page 265)

ALSO TRY TO AVOID:

  • Relations (Extensive various-to-various relations are allegedly expensive memory-wise.)
  • regular expressions

And hardcode expensive computations and use dirty hacks.

Sorry but … what is indexed text code?

There used to be two kinds of text: (regular) text and indexed text. They have now been combined together, but the performance issues of both remain. Any time you do something with text other than displaying it, you’re in indexed text mode. This includes searching/matching, replacing, accessing the individual characters etc.

I7’s implementation of these features is built on a layer or two of abstraction. This keeps it safe, but slows it down a lot too. For most of authors it will never be noticeable, but if you do a lot of text manipulation then it will be. An alternative less-abstracted implementation is definitely feasible, but it would take a fair bit of work to write.

This may be worth collating and stickying, no?

1 Like

Any test “if X is visible…” is expensive because it involves searching through all objects in the locale.

I’m curious about this. I would have thought that both of those formulations are doing the same thing. The second one creates a handy shorthand for “not burnable” in your code, but that wouldn’t make any difference in the compiled game, I’d have thought.

Yes, they’re effectively the same code.

Whether Hanon was off target or just mis-expressed himself, a related thing of mine (or maybe what he was trying to say) is:

I don’t give all things in the world a flag unless they need it or it will really help the code, if I can just give one thing a flag.

So if there’s one character whose happiness or anger is relevant, I say ‘Juanita can be happy or angry.’ Rather than ‘A person can be happy or angry. Juanita is (usually) happy.’

This approach can backfire if happiness/anger needs to interact with code in some very general case later on, but usually in a game where the case is this simple, this is OK.

The approach grows out of a general healthy conservatism of programming. I was thinknig ‘well, now the program doesn’t have to assign all those variables, and maybe it won’t have to iterate over them.’ Whether this is a case that ever made a real performance gain in any circumstance (I assumed it makes a memory difference, and memory is very burnable), I dunno.

-Wade

This saves memory, but I don’t think it improves speed.

I’m a bit unsure about when to tag.

Tagging all things in the world seems most stable. If the game code checks if the ‘noun’ is sorted-alphabetically, and the noun in this instance is a horse … sure, I should have stopped the horse from getting this far in the first place, but at least the tag tells the game what to do. And when the horse is defaulted to not sorted-alphabetically, this won’t even be mentioned with showme horse.

I happen to live in this wild sci-fi future where RAM is measured in gigabytes, so no practical limit here.

I just noticed that people are defaulted to ‘unlit’ and ‘inedible’ :slight_smile:

Actually, it’s also a fair point that Inform games can be played on a multitude of devices. FWIW. You don’t have to take that into consideration, and if you’re making something like Counterfeit Monkey where you just have to shrug and accept that performance will be sub-optimal in mobile/browser interpreters, then you should totally shrug and move on ahead.

…but if you’re not, give some thought to the little people. :wink:

The problem here is that before, Instead and After just feel much more intuitive, and that the alternatives doesn’t do exactly the same thing.