Optimizing for z-code readable memory limitations?

I’m bumping up against the readable memory limitations of z-code. I’ve already had to “use memory economy” to compile my game, so I know I pretty much can’t add new content without switching to glulx (which I am considering). I am guessing there have to be some ways of optimizing for z-code in Inform 7. I’m not really a programmer, so I don’t know what I’m doing here. Any advice on getting this under control?

If you’re not relying on any particular Z-Machine features, there’s no reason not to switch to Glulx, and it’s extremely simple to do (via radio button on the Settings tab).

If you are relying on particular Z-Machine features and don’t want to give it up, then it’s best to be aware of which Inform 7 features require the most readable memory due to their implementation at the I6 level. Lists and “mutable” text (i.e. strings that can change) are particular culprits; both can demand heavy use of a heap memory structure that is set up to handle run-time temporary allocation needs.

Will you explain a bit more about your project and what you think the most memory-consuming portions might be?

1 Like

I’ve been working on an iterative project called Locked Door while learning to use Inform 7. It’s my first Inform project that has actually resulted in a a playable game. I started with the absolute smallest prototype I could imagine that still resembled a text adventure (two rooms separated by a locked door, a key that unlocks the door, and a trophy item in the second room that triggers an ending when taken). Every iteration, I try to fix the mistakes I’ve found in the previous iteration, add new content that makes the game more interesting, and learn something new.

I’m not really a programmer and am not very experienced with Inform. I’m implementing my ideas the only way I know how, so it’s hard for me to guess what I might be doing wrong.

If by “mutable text” you mean things like
say "[The second noun] is not interested in [the noun]."
then that could be the problem, as there is quite a lot of that happening.

Some info from Results > Console:

I've now read your source text, which is 11459 words long.
I've also read Standard Rules by Graham Nelson, which is 42655 words long.
I've also read English Language by Graham Nelson, which is 2297 words long.
I've also read Basic Screen Effects by Emily Short, which is 2218 words long.
I've also read Rideable Vehicles by Graham Nelson, which is 1819 words long.

  The 11459-word source text has successfully been translated into an
    intermediate description which can be run through Inform 6 to complete
    compilation. There were 50 rooms and 98 things.

In:  1 source code files             78480 syntactic lines
 72197 textual lines               2713788 characters (ISO 8859-1 Latin1)
Allocated:
 10518 symbols (maximum 20000)    13312162 bytes of memory
Out:   Version 8 "Extended" story file 1.220129 (479K long):
    27 classes (maximum 200)           200 objects (maximum 639)
   215 global vars (maximum 233)     32007 variable/array space (maximum 180000)
   148 verbs (maximum 255)             621 dictionary entries (maximum 2000)
   249 grammar lines (version 2)       365 grammar tokens (unlimited)
   119 actions (maximum 200)            37 attributes (maximum 48)
    38 common props (maximum 62)        49 individual props (unlimited)
126731 characters used in text      113418 bytes compressed (rate 0.894)
     0 abbreviations (maximum 64)     3310 routines (unlimited)
 77179 instructions of Z-code        46828 sequence points
 64712 bytes readable memory used (maximum 65536)
490168 bytes used in Z-machine       34120 bytes free in Z-machine
Compiled with 1834 suppressed warnings

Some info from Index > Innards:

|table|table of locale priorities|414 bytes|
|object|each rideable animal |268 bytes|
|object|each staircase |234 bytes|
|object|each animal|210 bytes|
|object|each player's holdall|210 bytes|
|object|each raised supporter |206 bytes|
|object|each man|206 bytes|
|object|each woman|206 bytes|
|object|each vehicle|206 bytes|
|object|each rideable vehicle |206 bytes|
|object|each door|188 bytes|
|object|each container|160 bytes|
|object|each person|160 bytes|
|object|each on/off button |156 bytes|
|object|each supporter|156 bytes|
|object|each backdrop|156 bytes|
|object|each device|152 bytes|
|table|table of final question options|132 bytes|
|object|each waterway |120 bytes|
|object|each thing|114 bytes|
|table|table of abbreviation |110 bytes|
|map|map of rooms and doors|100 bytes|
|object|each room|86 bytes|
|object|each direction|78 bytes|
|object|each region|78 bytes|
|object|each object|52 bytes|
|action|each|24 bytes||

Nope, those things in brackets are called substitutions, and they should not cause memory problems in general – you would have to write some special ones.

I don’t consider myself a real programmer, either, but frequently the jargon is helpful when trying to be precise, so I’ve been trying to learn it. Don’t be afraid to ask for definitions. Here are some basics of the situation.

First, the Z-Machine virtual machine was invented in the 1980s and designed for multi-platform use where “platform” means one of the plethora of 8-bit personal computers available at the time. The low requirements mean that it is relatively easy to create a virtual machine to run on any modern machines, and the patina of Infocom fame still imbues it with a certain nostalgia value, but it does impose some strict limits.

Memory within the Z-machine is segmented into three sections. First is “dynamic” or writable memory. Second is “static” or the first of two types of read-only memory. Third is “high” or the second of the two types of read-only memory. (These are explained in detail in the Z-Machine Standards documentation; see pinned thread on the I6 forum if interested.)

Some features, such as lists, require creation of new memory structures when they are manipulated in certain ways. A list with 5 elements takes less memory than a list with 1000 elements. If a list starts with 5 elements and keeps getting new elements added, more memory must be dedicated to handle the longer list. Another feature that requires dynamic memory is text that can be changed. If you take some text and change characters or words within it, the entirety of the text must be kept in dynamic memory, so that you have the freedom to keep changing it. Creation of new memory structures must use the dynamic memory, because both types of read-only memory can’t be changed.

When a Z-machine game file is being written, it specifies in advance how much writable memory there can be. (Glulx was designed differently and allows expansion of writable memory on demand.) As a result, the I7 compiler takes a guess as to how much writable memory will be required for things like lists, and it sets this aside for use at run-time to serve as what’s called a “heap” (the technical term for memory that can be allocated at run-time). Because the total of dynamic and static memory can only go up to 64K (65536 or 2^16 bytes, which is the limit imposed by the 16-bit address scheme that they share), and because of certain inflexible demands stemming from the way that Inform 6 and Inform 7 work (the details of which are way more technical than this stuff), the I7 compiler can only choose between allocating 8K or 16K for the heap. The algorithm that does this selection prefers a generous allocation, so it doesn’t take that much to convince it to go with the higher value. (It can be convinced to try for 32K, as well, but this causes a different compilation error.)

Your compilation summary output doesn’t look like you’re doing anything too outrageous with respect to object count, which might contribute to the problem in extreme cases. Can you copy the exact error that you’re experiencing? I recently ran into the same out-of-writable memory error that you seem to be discussing, and the reason in that case was the use of many lists as part of a table definition. (The author had thought that, because they were constant lists, they would not make demands on the heap. This is not the case, because it turns out that constant lists are copied to the heap when used.)

Are there points in your code where you make use of lists or you change text? If so, perhaps you can post those parts for review. Like I said, though, unless you are specifically seeking to retain the Z-Machine, you shouldn’t hesitate to switch to Glulx, and I say that as a Z-Machine fan.

7 Likes

Here’s the error:

**Translating the Source - Out of Readable Memory**

The application ran your source text through the Inform 7 compiler, as usual, and it found no problems translating the source. This process results in what's called a **story file** , which is a program for a small virtual computer. Unfortunately, the story file for this source text broke a fundamental limit in this virtual computer: the maximum space available for "readable memory", which really means the space available in which to store tables, rulebooks, things, rooms and dictionary words.

Inform can produce story files for several different virtual computers, and the one used by the current project can be selected using the Settings panel. If you are currently using the **Z-machine** format, try switching the project to **Glulx** format (you can make this change at the Settings panel), and limits like this will probably not bother you again. Although Z-machine story files used to be much more widely playable than Glulx ones, these days Glulx interpreters are widely available, so it's probably not worth making big sacrifices to stay within the Z-machine memory size.

**Sorry for the inconvenience.**

I’ll look over my code and see. Not totally sure what you mean about changing text. I have a couple of rooms that look different the first time you enter them, and some where the description is slightly different depending on conditions.

Things like this:
The description of Some Dude is "Just some dude. [if the player is painted]You are covered head to toe in green paint. [end if]You are wearing [a list of things worn by the player]."
or
White Room is a room. "[one of]Possibly the most boring room you've ever seen. Every surface is white.[or]A plain white room.[stopping] Open archways lead east and west. A [wooden door] leads north, and an [office door] leads south."

The Mysterious Passage is south from Tunnel East. "There is a huge, colorful [billboard] here. An exit leads north. [if the stone arch is closed]A spooky [stone arch] blocks your path to the south.[otherwise]A spooky [stone arch] opens to a tunnel leading south.[end if]".

The only things I’ve done that might be out of the ordinary are a modified status line:

To say the player's capitalised surroundings:
	let the masthead be "[the player's surroundings]" in upper case;
	say the masthead.

Table of Fancy Status
left	central	right 
"Time: [time of day]"	"Location: [the player's capitalised surroundings] ([map region of the location])"	"Score: [score]"
""	"Near: [if a room is adjacent][the list of adjacent rooms][end if][if a room is adjacent and a door is visible], [end if][if a door is visible][the list of visible doors][end if]"	""

Rule for constructing the status line:
	 fill status bar with Table of Fancy Status;
	 rule succeeds.

Rule for printing the name of an unvisited room (called the target) while constructing the status line: 
	let aim be the best route from the location to the target; 
	say "somewhere [aim]".
	
After printing the name of a visited room (called the target) while constructing the status line: 
	let aim be the best route from the location to the target;
	if aim is not nothing:
		say " ([aim])".
	
Rule for printing the name of a direction (called the aim) while constructing the status line: 
	choose row with a heading of the aim in the Table of Abbreviation; 
	say "[shortcut entry]".
	
Table of Abbreviation 
heading	shortcut
north	"N"
northeast	"NE"
northwest	"NW"
east	"E"
southeast	"SE"
south	"S"
southwest	"SW"
west	"W"
up	"U"
down	"D"
inside	"in"
outside	"out"

and some countdown timers to keep track of whether a couple of burning objects have burned up yet.

What I mean by changing texts are the kinds of things that happen under the hood when you use phrases like the ones described in Writing With Inform 20.8 Replacements. To pull an example from that chapter:

replace character number 3 in V with "lecul";

Small texts like the ones in the examples should not really cause problems, but large texts (such as entire room descriptions, the contents of a long letter or document, etc.) can – but only if they are changed by having parts of the text replaced while the game is running.

Again, the use of bracketed phrases in text like [the noun] or [if ...]...[end if] are what are called substitutions in I7 jargon, and they aren’t the same. (Under the covers they are routines that print various sets of static strings which are parked in high memory, so even though the amount of text can be large, they take very little from “readable” memory. Again, “readable” memory means the combination of the dynamic and static memory areas described above.)

None of the stuff you posted looks suspect to me; others may weigh in.

Have you actually tried switching to Glulx? If so, was there some specific drawback that you are trying to avoid by sticking to the Z-Machine?

1 Like

Cody, I have played your eight locked door works. Albeit I generally frown on uploading learning-phase games on IF Archive, I reckon that the locked door series is potentially an excellent tutorial.

I recommend that you release the sources of that series, with appropriate commenting (I’m sure many veterans here will help you in writing the comments.

I suggest you to look on Scavenger hunt tutorial sources, for how a tutorial game can be commented.

Congrats: you can potentially deliver an excellent tutorial, on par with Scavenger hunt :slight_smile: I suppose is an excellent result from your learning :clap:

Best regards from Italy,
dott. Piergiorgio.

1 Like

I wasn’t actually sure whether I should avoid Glulx or not. I’d read that there might be compatibility issues with Android, but I’ve also heard that maybe that’s not an issue anymore?

Good to know. I was considering whether to do away with the status line anyway, as it gets mangled up pretty badly in Frotz.

I will definitely consider that. My main reservation would be that all the Locked Door iterations so far are pretty buggy, except maybe the first one. I catch a lot of issues between every iteration. The source for later versions also includes a lot of obscenities, because I wanted to have responses for them. :grinning:

Fabularium is working reasonably well, but it doesn’t exactly seem to be frequently updated either.

1 Like

I like the idea, but yes, it tends to run off the screen when there are too many exits (particularly in the dwarven ruins). I imagine it would be rather unusable on mobile.

I’d say either left-align the exit list (instead of centering it), which would give you some more space to work with, or move the exit list to the room description. Or allow the player to have it either way, e.g. through exits status and exits text commands.

1 Like

The player's surroundings substitution comes from the I7 Standard Rules:

To say the/-- player's surroundings
    (documented at phs_surroundings):
    (- SL_Location(true); -).

The actual definition of the I6 routine being run is:

[ SL_Location even_before;
    if ((not_yet_in_play) && (even_before == false)) return;
    if (location == thedark) {
	    BeginActivity(PRINTING_NAME_OF_DARK_ROOM_ACT);
	    if (ForActivity(PRINTING_NAME_OF_DARK_ROOM_ACT) == false)
 			DARKNESS_NAME_INTERNAL_RM('A');
	    EndActivity(PRINTING_NAME_OF_DARK_ROOM_ACT);
    } else {
	    FindVisibilityLevels();
	    if (visibility_ceiling == location) print (name) location;
	    else print (The) visibility_ceiling;
    }
];

Although your own player's capitalised surroundings does count as creating a mutable text (for the purpose of replacing lower case letters with their upper case equivalents), the demand is quite small (only the text usually present on the left hand part of the status bar), so I would count that as a “small text” for the purposes of my explanation above.

To ArdiMaster’s point, however, that substitution alone would create a demand for heap memory totalling at least 8K. The heap is a shared resource pool, though, and many different things will drive its creation (which is conditional on the source code using at least one feature that requires it, if I’m not mistaken), so I will be surprised if that is the sole driver here. Even an absolute legal minimum project (with a single propertyless room definition and nothing else) creates an 8K heap in Z-machine, so you would have had to do something special to prevent this.

If you would like to do as Piergiorgio_d_errico suggests and post your source code, I am sure that more specific advice can be provided. Several people seem to be interested in the mystery.

The choice of VM is your own to make, but Glulx is a very well supported platform, generally speaking. And it does inherently eliminate this writable memory constraint (up to some very large value that I don’t think anyone has reached to date). The Glulx experts here may have more to say about this.

1 Like

Oops, I was getting confused with the value DynamicMemoryAllocation. Sorry.

1 Like

Is there a less obnoxious way to post my source than simply pasting it here as preformatted text?

You could attach it as a file, or create a GitHub repository. (Or put it on some other cloud storage solution of your choice and link it here.)

1 Like

Ok, here’s my WIP source for Locked Door X:

Pardon the mess, I’m REALLY not a programmer.

There isn’t anything obviously memory-intensive in your code. I think that the project just got large enough that it doesn’t really fit alongside all of the default machinery in vanilla I7 6M62 under Z-Machine.

Compilation with the -z switch shows that over 33K of readable memory is occupied by arrays; this is the largest consumption category for readable memory (over half of the 64K available). The largest users of array memory are:

bytes     array
-----     -----------------------
  320     MStack
  448     blockv_stack
  494     valued_property_offsets
  644     property_metadata
  814     ResponseTexts
  846     rulebooks_array
  896     RE_PACKET_space
 1008     requisition_stack
 1154     ActionData
 1200     Map_Storage
 2054     TEXT_TY_Buffers
 8208     Flex_Heap

In addition, 7.8K is taken up by a plethora of 4-byte arrays, driven by I7’s standard string handling. That leaves about another 8K in miscellaneous small arrays dedicated to various subsystems.

The requested heap is only the default 8K, so that’s not the issue.

For those interested, the commands to get this information made use of xmlstarlet applied to the gameinfo.dbg file:

xmlstarlet sel -T -t -m '/inform-story-file/array[byte-count >= 256]' -v "./byte-count" -o ' ' -v "./identifier" -n gameinfo.dbg | sort -n

xmlstarlet sel -t -v "inform-story-file/array/byte-count" gameinfo.dbg | sort -n | uniq -c
1 Like

There’s the use of $OMIT_UNUSED_ROUTINES, whose gain a whopping 76k on a .z8 lab with 1 room and 2 things, leaving 189032 bytes free in Z-machine (v8, of course)

(as noted elsewhere, I think that $OMIT_UNUSED_ROUTINES can be considered a sort of punyinform for Inform 7…)

Best regards from Italy,
dott. Piergiorgio.

1 Like