THE TRUTH ABOUT MPE/XL DISC FILES by Eugene Volokh, VESOFT Presented at 1989 INTEREX Conference, San Francisco, CA, USA (BEST PAPER AWARD For an Outstanding Paper Presentation) Published by INTERACT Magazine, Sep 1989. ABSTRACT Several years ago, I wrote a paper called "The Truth About Disc Files". In it, I tried to describe some aspects of files that, I felt, inquiring minds wanted to know -- things like extent considerations, blocking, file locking, etc. Some of those things remained the same under MPE/XL's file system, but many have substantially changed; this paper will try to describe some of the key differences and similarities between the MPE/XL and MPE/V file systems, and explain some of their practical implications. HOW FILES ARE STORED -- EXTENT CONSIDERATIONS One of the key limitations of MPE/V was that you had to know a file's maximum size when you were building the file. If you guessed too low, your programs would eventually abort with an END OF FILE error; if you guessed too high, you could waste a lot of disc space. Actually, technically speaking, you didn't have to know a file's true maximum size; you could always build the file with a very large file limit, e.g. :BUILD MYFILE;DISC=1000000 and you'd be rather certain never to overflow it. The trouble, of course, is that the file's disc space was not allocated on a simple as-needed basis, but rather one extent at a time. Since a file by default had a maximum of 8 extents the above :BUILD would build a file that was split into up to 8 chunks of contiguous disc space; the very act of building the file would allocate one chunk of this space, which would occupy 1,000,000 / 8 = 125,000 (contiguous!) sectors. (Remember that a sector is 256 bytes -- we used to say 128 words, but not any more; since a word means 2 bytes on Classics but 4 bytes on Spectrums, I will try to use "word" as infrequently as possible in this paper.) Even if you said :BUILD MYFILE;DISC=1000000,32 to build the file with up to 32 extents, the file would initially be allocated with over 31,000 sectors. In other words, it wasn't so much selecting the right file limit that was the problem, but rather that selecting a file limit that was too high would cause prohibitive consumption of disc space. This may seem somewhat nitpicky but it is actually quite relevant to MPE/XL. MPE/XL also requires you to specify the maximum size for a file. However -- and this is a big "however" -- it lets you specify a very large file limit without using huge quantifies of disc space. If in MPE/XL you said :BUILD MYFILE;DISC=1000000 the file would be allocated 2,048 sectors at a time, even if this would require it to have almost 500 extents when full. Thus you get the best of both worlds -- the file can grow to up to 1,000,000 records but will never have more than 2,047 sectors of wasted disc space. You'll find that MPE/XL often builds files (e.g. XL's built by LINKEDIT's -BUILDXL command) that have file limits of 4,096,000 -- more than they'll ever need but what does it matter? In fact, the highest "maximum maximum file size" -- i.e., the highest (file limit * record size) value that you can have -- is 8,388,607 sectors. One of the reasons why MPE/V had the 32-extent limit was that the disc addresses of all the extents were kept in the 256-byte file label. Since each disc address was 4 bytes, and a little bit less than 128 bytes were required for other file information (e.g. file name, creator id, file code, etc.), that left room for only about 32 extent pointers. MPE/XL didn't make the same mistake of keeping extent pointers in a single fixed-size array. Instead, each file has a linked list of "extent descriptor blocks", each of which has 20 12-bytes entries that point to the extents of the file. Thus, a file on MPE/XL will have: * a file label, which points to * an extent descriptor block which contains the disc addresses of 20 extents and also points to * a second extent descriptor block which contains the disc addresses of 12 extents. Granted, this is 3 sectors (as opposed to the 1 sector, which is all that is needed for the file label on MPE/V), but think of the flexibility -- new extents can be added to the file with no difficulty. To avoid possible performance problems with access to files that have many extents, MPE/XL builds a special "extent descriptor B-tree" whenever you open a file; that way, it can very quickly find the address of an extent even in a a many-hundred-extent file. All right you've said: :BUILD MYFILE;DISC=1000000 and now you do a :LISTF MYFILE,2. What do you get? FILENAME CODE ------------LOGICAL RECORD---------- ------SPACE----- SIZE TYP EOF LIMIT R/B SECTORS #X MX MYFILE 128W FB 0 1000000 1 0 0 * There are, obviously, three unusual things in this picture: * The number of sectors allocated to the file is 0. If you recall on MPE/V, even an empty file always has at least one sector allocated to it, and usually more. This is because on MPE/V, file labels were kept as the first sector of the first extent of the file so each file always had to have at least one extent allocated to it. In MPE/XL, file labels are kept separately from the file data (in a special portion of the disc called the "file label table" -- extent descriptor blocks are also kept there), so no data extents are allocated and thus 0 sectors are actually allocated for the data. Of course, the file label still takes up 1 sector of space, but that doesn't get budgeted to the file in MPE/XL. * The number of extents allocated to the file is O. See above paragraph. * The maximum number of extents for the file is "*". This means that the file was built without a maximum number of extents (though, as we'll explain later, even if it were built with a maximum number of extents, this number still wouldn't really be a maximum!). For extra credit try to guess what an "*" in the "# of extents" (as opposed to "maximum extents") column means; we'll discuss that later. Now, say that we write one record into this file and then do a :LISTF. We'd see: FILENAME CODE ------------LOGICAL RECORD---------- ------SPACE----- SIZE TYP EOF LIMIT R/B SECTORS #X MX MYFILE 128W FB 1 1000000 1 2048 1 * One 2,048-sector extent has been allocated to this file -- it will be enough for the first 2,048 records to be written into MYFILE. When we try to write the 2,049th record, another 2,048-sector extent will be allocated, and so on. As we mentioned before, this means that no more than 2,047 sectors in this file will ever be wasted (allocated but unused). This is quite nice, but if each such file wastes an average of 1,000 sectors (an average between those that waste 0 and those that waste all 2,047) and you have 2,000 such files, we're talking about 500 megabytes of wasted space about the size of one disc drive. Looking at it this way, saying that "at most 2,047 sectors can be wasted" is small comfort. It would have been nice if we could build the file indicating what we'd like its extent size to be; we might then build our files with huge file limits but tell MPE/XL that they are to be allocated only, say, 512 or 256 sectors at a time. Unfortunately this is (to the best of my knowledge) not possible. MPE/XL determines the size of the extents that it will try to allocate for a file using a (rather bizarre) formula based on the maximum number of sectors the file can ever contain: MAXSECT = (userlabelflimit*256+recordsize*flimit)/(16*256)*16 (i.e. the total number of sectors in the file's data portion, rounded up to the next multiple of 16 sectors) DEFAULTEXTENTSIZE = IF MAXSECT<= 127 then MAXSECT rounded up to the next highest multiple of 16 IF MAXSECT<=255 then MAXSECT rounded down to the lowest multiple of 16 IF MAXSECT<=4095 then 128 sectors IF MAXSECT<=65535 then (MAXSECT/32) rounded down to the next lowest multiple of 16 IF MAXSECT>=65536 then 2048 sectors Don't ask me why this is the case -- it just is. (It certainly makes some sense for extent size to vary as a function of the file size, but I'm not sure why it varies exactly in this unusual way.) Note, however that one other great new feature of MPE/XL is that if it can't find extents of the size that it wants (e.g. there is no empty chunk of 2048 extents), it will just allocate smaller extents. MPE/XL will not report an "OUT OF DISC SPACE" condition unless there really isn't enough disc space but it's reserved for virtual (transient) memory. Disc fragmentation does not appear to harm things except that it may make files be built with smaller extents. So, this all shows (among other things) that any files with a maximum of 65,536 or more sectors will have space allocated for them in 2,048-sector chunks. If you have one of those files how do you save the 1,000 sectors or so that will, on the average, be wasted? On MPE/V, you could always "squeeze" the file (FCLOSE it with disposition 8 or MPEX %ALTFILE;SQUEEZE), which would set the file's file limit to be equal to its end of file and thus save the wasted space. Unfortunately, it would also prevent any more records from being added to the file (unless you rebuild it or %ALTFILE;FLIMIT= it). MPE/XL has a very nice alternative to this that we call "trimming". It lets you tell MPE (using a new FCLOSE disposition) to deallocate any unused space allocated to the file without changing the file's file limit. In other words, this will save space without making files any less expandable; an operation such as MPEX's %ALTFILE @.@.@; XLTRIM can save you hundreds of thousands of sectors (it did for us and we only have a 925LX with two disc drives). Actually, before doing this we called PICS and asked whether there were any files that should not be trimmed and they told us that they didn't know of any; I then did the trim of all the files in the system and nothing seemed to fail. Be warned though -- it's certainly possible that some program (probably a heavily privileged one, since normal programs have no way of knowing whether a file has been trimmed or not) doesn't like its files trimmed. How does this work? Well, say that you have a file with five 2,048-sector extents the last of which contains only 537 actual sectors of data. When you tell MPE to trim the file, it will deallocate the last 1,504 sectors of the last extent, leaving it with only 544 sectors. (544 = 537 rounded up to the next highest multiple of 16; files are always allocated in multiples of 16 sectors.) Now, the file has four 2,048-sector extents and a 544- sector extent. If you start adding more records to it, more 2,048-sector extents will be allocated to it; you may then again want to trim the file. What makes this whole process work is that MPE/XL allows you to have extents of different sizes. If, like in MPE/V, all extents (except for the very last of the possible extents) had to be the same size, you wouldn't be able to throw away unallocated data because that would leave the last allocated extent with a different extent size. MPE/XL, however, is not bothered by this -- I've often seen files with many different sizes for many different extents. The "extent descriptor blocks" that I mentioned earlier actually contain several pieces of information for each extent: * the disc number on which the extent resides (actually, the volume table index); * the starting sector address of the extent; * the size of the extent, in sectors; * and, the sector number of the first sector of data (relative to the start of the file) that resides in this extent. Actually with a structure this flexible it's even possible for records #0 to #999 to be located in the second extent of a file and record #1000 to #1999 to be located in the first extent! (Of course when you read the file, the records will come out in the right order, but internally inside the extent descriptor blocks the extent information will be kept out of order.) What are the disadvantages of trimming files? To the best of my knowledge, there are very few. Trimming files will increase disc space fragmentation, but it's not clear to me that this is a problem on MPE/XL, especially since MPE/XL seems to handle correctly situations where it can't find extents of the size that it wants (if necessary, it just allocates more smaller ones). Trimming files does cause the file to have more extents, which may have a slightly adverse effect on performance. The file system usually tries to read 128 or more sectors (16,384 bytes) at a time, so if a file has all 16-sector extents (the smallest size possible), you will lose the advantages of these large I/Os since you'll never have 64 contiguous sectors. However, if a file has, say, 512-sector extents rather than 2,048-sector extents, this should cause minimal performance penalties (if any at all). On the other hand, if you feel that a trimmed file isn't giving you the performance you'd like, you can copy its data into a new copy of the file, and all the extents will then be the same size (whatever the extent-size algorithm we showed above dictates). You can even build the new file with a higher file limit (to increase the extent size) and then trim the file to save as much space from the last extent as possible. MPEX users can do this by saying %ALTFILE filename;FLIMIT=4096000;XLTRIM -- this will rebuild the file to have all its extents be 2,048 sectors each except for the last one, which will only be as large as necessary. This will give you the maximum disc space savings as well as the maximum possible extent sizes (if that's what you want). (Note that files with EOF = FLIMIT do not require trimming; the MPE/XL file system automatically allocates the last extent to be just large enough to fit all the records up to the FLIMIT, even if this makes the extent smaller than the other extents.) In light of all this, why does MPE/XL still support the maximum number of extents parameter of the FOPEN intrinsic and the :BUILD command? Well, compatibility is one reason. There are programs out there that might, for instance, do an FGETINFO or FFILEINFO of a file's maximum number of extents -- they should be able to get the value specified when the file was :BUILDed (:BUILt?). In fact, MPE goes so far as to return 8 as the maximum number of extents when you do an FFILEINFO for a file that was built without a maximum number of extents -- all this just to make sure that the program will get a value (though an incorrect one) that it will be able to handle. Another reason involves moving files from MPE/V to MPE/XL and back. If you move a file from MPE/V to MPE/XL, it will have the same "maximum extents" value that it did on MPE/V (even if it will be ignored by MPE/XL). Then, if you move the file back to an MPE/V system, it will have the same maximum-extents value that it originally had. If the MPE/XL file had no maximum extents, MPE/V will select a "reasonable" value for this (based on the number of sectors the file uses). Finally, the maximum number of extents is used (though in a very strange way) when you build a file specifying both the maximum number of extents and the number of extents to initially allocate. For instance, say that you enter :BUILD MYFILE;DISC=100000,32,4 What do you suppose will happen? here's what a :LISTF of the file will show: FILENAME CODE ------------LOGICAL RECORD---------- ------SPACE----- SIZE TYP EOF LIMIT R/B SECTORS #X MX X 128W FB 0 1000000 1 12512 1 32 The file was built with 1 extent of 12512 sectors! MPE/XL decided that what you wanted is a file with 4/32nd (= one eighth) of its space allocated, so it built you one like that, although with all that space allocated as one contiguous extent. From then on, the file will be allocated in normally-sized (in this case, 2,048-sector) chunks. Eventually, the file will need more than 32 extents (this file would if full, need more than 40), and MPE/XL will just blithely ignore the maximum extents value and allocate as many as it needs. Thus, you'll often see files with more extents than the maximum (rather perplexing when you first see them). Incidentally, to answer the question we asked earlier: if a file has 100 or more extents, MPE/XL will show an * in the number of extents column of a :LISTF ,2 listing. Finally, remember that IMAGE databases must still be fully allocated when they are built -- I believe that IMAGE does this not because of any file system limitation but rather for data consistency's sake; it doesn't want to run out of disc space in the middle of a DBPUT. I have, however, heard a hot rumor that a future version of TurboIMAGE/XL will allow a detail dataset to be expanded when it runs out of space (so that you can initially allocate it with less space than it will eventually need); but (or so the rumor says), each dataset can only be expanded once in its life -- once it's expanded, it better not overflow again! Seems bizarre but that's what I've heard. Believe it or not. HOW FILES ARE STORED -- BLOCKING CONSIDERATIONS In discussions of MPE/V, much was said about blocks, blocking factors, and their effects on speed and disc space. Just when we had all taken the time and trouble to learn all of their intricacies, MPE/XL has made them (almost) completely irrelevant. In MPE/XL, all physical disc I/O is done in multiples of one page, which is 4096 bytes. This is not to be confused with the pages that are 2,048 bytes -- yes, that's right, there are two kinds of pages, each of different size, and both of which are called pages. One, which is 2,048 bytes long, is the unit in which the hardware sees the world; the other which is 4,096 bytes long, is the unit used by the operating system from the memory manager on up (including the file system). In any event, physical I/O is done some number of 4,096-byte pages at a time (it's good that it's a 4,096-byte page because you want to do I/Os in fairly large chunks). Since each 4096-byte page consists of 16 256-byte sectors, a file is always allocated, read, and written in multiples of 16 sectors at a time. Remember that the whole point of MPE/V blocks was that a block was the unit of file transfer for this particular file. This may have made sense in the very earliest HP3000s, which often had as few as 128 Kilobytes of memory, and which couldn't afford huge chunks of this for file buffering. However, since on MPE/XL file transfer is always done 4,096 bytes at a time, the concept of a block becomes irrelevant. Each page has as many records in it as will fit; there are no inter-record gaps (as there used to be on MPE/V when the block size was not a multiple of one sector). In fact, records can even straddle pages -- if your file's records are 1,000 bytes long, then * the first 4,096-byte page will have 4 full records and the first 96 bytes of the fifth record; * the second 4,096-byte page will have the last 904 (1,000-96) bytes of the fifth record, the next 3 full records, and the next 192 bytes of the ninth record; * and so on. There's never any space wasted in a page (except of course, in the allocated-but-not-written portion of the last extent) -- not because of bad blocking factors and not even because of records with odd record length. If you build an ASCII file with 1-byte records, exactly 4,096 of them will fit into each 4,096-byte page. A curious thing, incidentally, is the lengths to which MPE/XL must go to make this efficient, reasonable, straightforward system compatible with MPE/V's baroque and inefficient mechanisms. If you read an odd-record-length file MR NOBUF, MPE/XL will actually insert padding bytes at the end of each record to be compatible with MPE/V; when do you an MPE/XL :STORE;TRANSPORT of a file whose blocks (in MPE/V) wouldn't be multiples of 256 bytes, MPE/XL will also insert padding at the end of each block to correspond to MPE/V's inefficient end-of-block padding. The blocking factor of a file is, like the maximum number of extents of a file, specifiable but largely ignored. It's relevant only for compatibility, for transporting files to MPE/V machines, and for NOBUF file accesses, in which a program written for MPE/V would expect to get data in units of MPE/V blocks. OTHER DISC SPACE CONSIDERATIONS So, if MPE/XL can build large files without wasting space and do file blocking more efficiently and trim wasted space without changing file limits, one question remains: Why does it use so much disc space? There is one philosophical explanation (it's somebody or other's Law, but I forgot whose): "The amount of disc space required will increase until it meets and exceeds the amount of disc space available". This is actually not just a facetious statement; as disc space use algorithms become more efficient and disc space becomes more plentiful, people will take advantage of this by building more and more files that are larger and larger. You'll get more bang for a buck's worth of disc space, but eventually you will exhaust it all the same. There are, however a few more pragmatic explanations: * The operating system uses much more disc space than on MPE/V. The groups MPEXL.SYS and PUB.SYS use 570,000 sectors (150 megabytes!) on our 925/LX -- PUB.SYS on our MICRO/3000 uses only 100,000 sectors. * Code -- programs and SLs -- uses a lot more space than it did before (this is actually a big part of the reason why the operating system uses more disc space). Why is this the case? Well, remember that all this "Reduced Instruction Set" means that it takes several RISC instructions to do the job of one Classic instruction. Thus, a program of 10,000 16-bit Classic instructions might be replaced by one of 50,000 32-bit RISC instructions -- a ten-fold increase. This is true of Native Mode code and of OCTCOMPed code. Compatibility Mode code still takes the same amount of space as it did under MPE/V. * Although trimming files is possible, to the best of my knowledge, few things in MPE do it routinely. Compatibility mode USLs, it seems, are pretty substantial culprits (using far more space than they would if trimmed), and other files should probably be periodically trimmed, too. Thus, our recommendations for saving disc space would be: * Purge old unused files. This #1 space-saving feature from MPE/V days is still as important as ever on MPE/XL (and will probably be for a long time to come). Discs inevitably get filled up with junk, data that the owner no longer uses, no longer wants, and has probably already forgotten about; not only does it waste disc space, but it also makes your full backups take more time and more tapes. If you periodically archive and then purge all the files (except, say, IMAGE datasets) that haven't been accessed in 120 days, you will save a lot of a disc space with minimal user complaints * Trim files (e.g. using MPEX's %ALTFILE @.@.@;XLTRIM) periodically. As I mentioned before, trimming seems to be safe for all files in the system. * Remember that native mode and OCTCOMPed program files are now big disc space hogs -- multiple unneeded copies of programs (which used to be rather harmless on MPE/V) may now substantially contribute to your disc space problems. MAPPED FILES Mapped files have been heralded (and correctly so) as a powerful and valuable new feature of MPE/XL. It has been discussed in a number of places, including chapter 11 of HP's "Accessing Files Programmer's Guide", and also, coincidentally, chapter 11 of SRN, Inc.'s excellent "Beyond RISC!" book. (I heartily recommend Beyond RISC! to anybody who's at all interested in Spectrums -- call SRN at 206-463-3030). At the RISC of beating a dead horse, I d like to go over some of the key points of mapped files in this paper, too. First of all, a Mapped File is actually not a type of file but rather a type of file access. Almost any file can be opened as a mapped file; once your program opens a file with the mapping option, it will be able to access the file as if it were an array in its own data area. Instead of accessing a file using FREAD and FWRITE (or the equivalent language constructs, such as PASCAL's READLN or WRITELN), you'll be able to access the data of the file just as you'd access any array (or record structure). MPE/XL will, behind your back, realize that this isn't a normal array but is rather a mapped file; whenever you access a piece of this array, MPE/XL will, if necessary, go out to disc to get the appropriate data. (This is actually true for your stack, data segments, etc., as well, but it's especially important for mapped files.) (Not very important note: Actually, the file system opens all files with mapped access for its own internal purposes; however, when I talk about mapped file access , I refer to file access that is mapped from the users point of view.) Let's look at what might be the perfect application for mapped files keeping a large array of data that must survive from one execution of a program to another. Say that you have a large number of payroll codes (numbered, say, from 0 to 99), each of which has various attributes (such as code name, pay scale, tax identifier, etc.) that your program must know about. Your program has to look payroll codes up in this file and extract the relevant data. Without mapped files, here's what your program might look like (in PASCAL): TYPE PAYROLL_CODE_REC = CRUNCHED RECORD CODE_NAME: PACKED ARRAY [1..20] OF CHAR; PAY_SCALE: INTEGER; TAX_ID: INTEGER; ... END; VAR PC_REC: PAYROLL_CODE_REC; ... FNUM:=FOPEN (DATAFILE, 1 (* old file *)); ... FREADDIR (FNUM, PC_REC, SIZEOF(PC_REC), PCODE); IF PCODE.TAX_ID=... THEN ... With mapped files, you'd say: TYPE PAYROLL_CODE_REC = CRUNCHED RECORD CODE NAME: PACKED ARRAY [1..20] OF CHAR; PAY SCALE: INTEGER; TAX ID: INTEGER; ... END; PAYROLL_CODE_REC_ARRAY = ARRAY [0..99] OF PAYROLL_CODE_REC; VAR PC_FILE_PTR: ^PAYROLL_CODE_REC_ARRAY; ... DOMAIN:=1 (* old file *); HPFOPEN (FNUM, STATUS, 2, FILENAME, 3, DOMAIN, 18, PC_FILE_PTR); ... IF PC_FILE_PTR^[PCODE].TAX_ID=... THEN ... Instead of doing an FREADDIR (using PASCAL's READDIR statement), we directly access the file as if it were an array. The HPFOPEN call (more about its unusual calling sequence later) indicates that the file is to be opened mapped and that the pointer PC_FILE_PTR is set to point to its data; then, whenever we refer to PC_FILE_PTR^ we get access to the entire file as an array of records. Why would we want to use mapped files? One reason is convenience -- in a situation like this one, it's easier (and makes more sense in light of the logic of the program) to view the file as an array rather than as a file. Instead of having to do a READDIR every time we want to get a record, we just access the record directly. Another reason is performance. Avoiding the extra READDIRs not only makes the program smaller and cleaner, but also saves the CPU time that would otherwise be taken by each READ, READDIR, WRITE, or WRITEDIR. Each file system intrinsic (which are ultimately called READ, READDIR, WRITE, and WRITEDIR, and all the similar constructs in the other languages) has to do a lot of work finding control blocks, checking file types, etc., even before a disc I/O is actually done. This can take many thousands of instructions, amounting to up to a millisecond per call (or more). Access to a mapped file can take as little as one instruction one memory access. As we will discuss later, mapped file access actually has some performance penalties, too, especially when we're doing sequential accesses to files that are not likely to be already in memory It is actually quite possible with mapped file access to lose much more on disc I/O increases than you would gain on CPU time savings. However, if you're accessing files that are already likely to be in memory -- which often includes many heavily-accessed files -- mapped I/O can give you very large performance gains (again, more about that later). Beyond convenience and optimization, I think that there are many more very interesting things that mapped files can let us do things that have rarely been contemplated in the past precisely because they were so difficult to do in the past. There is one idea that I have along these lines; I've never tried it in a production program, but I feel that it could very well prove quite useful. One of the things that mapped files can give us is shared variables. By this I don't mean global variables that are shared among all the procedures in a program, but rather variables that are shared among multiple programs and processes. For example, let's say that you have a program that runs in a job stream. The program might run for a long time, and you may want to check on its progress -- see which phase of processing it's in, what was the last record it processed, and so on. With mapped files, you can do the following: * Keep some crucial variables -- the current processing phase, the current record being processed, etc. -- in fields of a record structure. (This is a bit more complicated than having them all be separate variables, but not much.) * Have the record structure be associated with a mapped file by HPFOPENing the file (with shared access) and using the pointer that HPFOPEN returns as a pointer to the record structure. * Have another program that you can run online that will open the mapped file and print its contents for you. Whenever the background program modifies one of the fields of this mapped-file-resident data structure, the field will be automatically updated in the file (even though this almost certainly require, on the average, far less than one disc I/O for each field modification). Then, the online program can at any time look at the contents of the file and tell you what's going on; and, if the batch program aborts, you'll be able to see where it was in its processing when it aborted (since the data is saved in the permanent mapped file). This would also be an excellent tool if you'd like to write a debugger for some interpreter program that you have. As long as the interpreter keeps all its control variables in a mapped-file-resident area, then a debugger program (running in the same session or in a different one) can look at these variables and figure out exactly what the interpreter is doing. It can even change the variables, for instance, setting some debugging flag, changing the value of a user variable, or whatever; and, if all the important data is actually kept in this file, it would permit dump analysis in case the program aborts, and even interruptions and restarts (since the entire state of the program would be automatically saved). Another possible application is to have a program periodically (e.g. for every record that it processes) check a mapped-file-resident variable and terminate cleanly if it is set. Then, if we want to terminate all the processes running this program, we just set the variable, and all of them will stop. (Something like this could be done before with message files and soft interrupts, but it would require one record to be written to the message file for each process accessing it.) Of course, this could all have been done before mapped files instead of accessing the mapped-file-resident variables directly we could just do FREADs or FWRITEs to read or write the appropriate record from the file. However, this would have been prohibitively expensive and clumsy -- imagine that you had to do an intrinsic call every time you wanted to access a particular variable; it would badly slow things down and make your program much more complicated. As I said, all of the above are relatively untested ideas, but I feel that much can be gained by doing something along those lines. The really sad thing about mapped files -- something that I think is likely to drastically reduce their utility -- is that they can only be accessed from PASCAL/XL, C/XL, and SPLash!. FORTRAN/XL and COBOL/XL programs cannot access mapped files, not because of any file system limitation, but because those languages do not support pointers In FORTRAN and COBOL, all variables are preallocated when you run the program or enter a procedure; to use mapped files you have to be able to assign a particular address to a variable. Actually, if you really wanted to use mapped files from FORTRAN or COBOL, you could write a PASCAL, C, or SPLash! procedure that lets you access pointers; however, this would most likely cancel any convenience advantages that mapped files can give you. A few other notes about mapped files -- they're all documented in various places, but they're worth repeating: * There are two ways of opening a file for mapped access -- "long mapped" and "short mapped". Long-mapped access lets you access any size file but requires you to use long (64-bit) pointers; in PASCAL, they have to be declared as $EXTNADDR$. Short-mapped access only lets you access a file of at most 4 megabytes; furthermore, you may have no more than 6 megabytes worth of short-mapped files open for each process. On the other hand, short-mapped access lets you use 32-bit pointers, which are faster to operate with than the 64-bit ones. * Because of the restriction on short-mapped file size and the fact that you can't open a file short-mapped if it's already opened by somebody else without mapping, your general-purpose programs (e.g. copiers, editors, etc.) should probably open files long-mapped rather than short-mapped -- it seems to be a more versatile, less restrictive access method. * You can not access RIO, CIRcular, or MSG files as mapped files; you can access variable-record-length and KSAM files, but you'll see their internal structure (i.e. what you'd see if you read a variable file NOBUF or a KSAM file with COPY access) rather than their normal appearance. This may not seem to be such a big problem, and it often isn't; however, I've found that one of the most useful features of the MPE file system is its ability to substitute one type of file for another -- for instance, give a message file to a program that expects input from a standard file, or a variable-record-length file to a program that expects input from a fixed-record-length file. This interchangeability will be lost for files that you open with mapped access. * Remember that writing to a mapped file only writes the data; it does not increment the EOF. Even if you write data that ends up in record 1000 of the file, if the EOF is 200 it will stay 200. You have to do an FPOINT to the right record and then an FCONTROL mode 6 (as documented by the Accessing Files manual and by the Beyond RISC!) book to set the EOF to the right place. An interesting aspect of this is that the data that you write beyond the EOF will actually be written there and will remain readable the next time you open the file for mapped access. However, it will not be readable when you open the file normally, and will almost certainly disappear if the file is copied, :STOREd/:RESTOREd, or %ALTFILE;XLTRIMed. Thus, if you write to a mapped file and forget to adjust the EOF, your programs might very well keep working just fine until the the file is next :STOREd/:RESTOREd or %ALTFILE;XLTRIM. You can get some truly bizarre bugs that way. Don't even dare think that this is a useful feature and try to exploit it (e.g. to have some place to put hidden data that will appear not to actually be there)! Imagine trying to maintain or manage a system on which seemingly empty files were actually chock-full of data. * If you have processes share a mapped file, you may have to do appropriate locking to prevent problems (especially if you have more than one person writing to the file). For instance, you might write all your programs so that they FLOCK the file before making any updates to it; unfortunately, this will make your program more complicated after all, the whole point was to treat the file data just as if it were normal program variables. Furthermore, the very fact that it's so easy to modify one of these shared variables (just assign to it or pass it as an output parameter to a procedure) may make it easier for you to forget to put in an FLOCK in the right place. HOW THE FILE SYSTEM DOES I/O One rule that we learned under MPE/V is: always do disc I/Os in as large chunks as possible. If a file has 256-bye records, don't read it from (or write it to) disc one record at a time; read it ten records at a time or, even better, thirty or sixty at a time. The reason for this was, of course, that as the transfer count (the number of words of data read or written) on a particular disc I/O increases, the time to do the disc I/O increases much more slowly. Thus, it might take you 30 milliseconds to read 256 byes, but 100 milliseconds to read 8192 bytes; if you were planning to read those 8192 bytes anyway (and weren't just going to read one 256-byte record), you could read them ten times faster by reading them in one 8192-byte chunk than by reading them in 32 256-byte chunks. Furthermore, you'd incur the CPU overhead (which can be pretty substantial) of only one FREAD call rather than of 32 FREAD calls. On MPE/V, the file system would always do disc I/Os in units of one block. The default blocking factor (the number of records per block) was usually not well-chosen by the operating system; for instance any file whose record size was 65 words or more would, by default, have a blocking factor of 1. This might have made sense in the earliest HP3000s (on which memory was a very scarce resource), but not on 8-Megabyte series 70s, which tended to end up being quite disc I/O-bound. Thus, on MPE/V the recommendation was to raise the blocking factors of MPE (and KSAM) files that you frequently access, especially serially; this could save you a large fraction of the file's I/Os (increasing a blocking factor from 1 to 10 could cut by 90% the number of I/Os needed to read the file). When disc caching was introduced, this became somewhat less important, since the file system would pre-read from 16 to 96 sectors (4K bytes to 24K bytes) whenever you'd do a serial disc I/O; thus even a file with a low blocking factor could be read with relatively few disc I/Os. However, it still paid to have the blocking factor be high since going to cache was still more expensive than getting the record from the file system buffer (though not as expensive as going to disc). Finally beyond increasing the blocking factor, it was often a good idea to read or write the file NOBUF (so that each FREAD returned an entire block) or MR NOBUF (so that each FREAD returned several blocks). Reading a file NOBUF caused you to do the same number of disc I/Os (since the file system also read the file a block at a time); however you would save the CPU overhead of all those FREADs (which could be quite a lot). Reading a file MR NOBUF was even better, since it let you do even fewer disc I/Os (though increasing the blocking factor to a high enough value and then using plain NOBUF or even normal access could accomplish the same purpose). The trouble with reading files NOBUF or MR NOBUF is that your program had to do its own deblocking , i.e. it had to, by itself, separate each record in the block from the next not a very difficult task, but not a trivially easy one, either. To summarize (again, remember that this is on MPE/V), here are the ways you might read a file of 1024 256-byte records (depending on the file's blocking factor and access method): Blocking factor Type of access # of disc I/Os # of FREAD calls 1 Normal 1024 1024 4 Normal 256 1024 16 Normal 64 1024 16 NOBUF 64 64 16 MR NOBUF 32 32 reading 8192 bytes at a time) OK, enough re-cap. What's new in MPE/XL? Well, the good news is that all file system disc I/O is now done in units of several 4,096-byte pages, often 8 pages (32,768 bytes), though the number seems to vary rather unpredictably. That's a lot of data (probably close to the optimum from the disc drives point of view, since at some point the beneficial effects of reading larger and larger chunks of data will peter out), and it will substantially decrease the amount of disc I/O that will be done. Of course what makes this possible is all those megabytes of memory that you had to buy to make your Spectrum run; they allow HP to go straight after performance optimization without having to optimize memory usage as well (or so we hope). This means that blocking factors are now quite irrelevant to file performance (just as they are, as we mentioned before, irrelevant to disc space usage). You may set them high or set them low, but the file transfer unit will not change. The bad news is that each FREAD and FWRITE call still takes a good deal of time, about .25 or so milliseconds running stand-alone on my 925/LX. (This may not seem like much, but remember that not everybody gets to use a Spectrum as a personal computer! On heavily-loaded systems the FREADs and FWRITEs will take even longer to execute, and will adversely impact other users response times.) What can we do about all this file system CPU overhead? Well, we could access the files NOBUF or MR NOBUF the MR NOBUF would now be needed not so much to decrease disc I/Os (which at one I/O per 16,384 bytes can't really be decreased much further) as to decrease the number of file system calls. Alternatively, we could access these files as mapped files. Once we open the file, we could then access all the data in the file using simple memory accesses -- when a disc I/O is required, it'll be done for us by the memory manager, but no file system overhead will be required! The only problem -- and this is a really big one -- is that, together with the substantial CPU time savings that mapped files give us, they can also substantially increase the amount of disc I/O that is done. While the file system accesses the disc several 4,096-byte pages at a time (my observations showed me that it usually accesses 8 pages, or 32,768 bytes, in one shot), the memory manager (and thus mapped files) accesses the disc only 4,096 bytes at a time. Thus, while we can totally eliminate file system CPU overhead by using mapped files, we could at the same time quadruple the amount of disc I/O that needs to be done! Now, as it happens, this disc I/O increase only becomes an issue if the file is not already in memory; to the extent that it is in memory (and many parts of your most heavily used files will be), the disc I/O size is irrelevant because no disc I/O will be needed. However, if a file is entirely or largely not in memory, you could suffer a very serious performance penalty by using mapped files. To revisit our little table of the ways you can read a file of 1024 256-byte records, this time on MPE/XL (we'll assume that the file's blocking factor is 32 -- it's quite irrelevant except for NOBUF access): Type of access Maximum # of disc I/Os # of FREAD calls Normal 8 1024 NOBUF 8 64 MR NOBUF (reading 16384 8 16 bytes at a time) Mapped 64 0 (This assumes that the file system reads 8 4,096-byte pages at a time, something that experiments on MPE/XL version 1.2 seem to indicate.) Of course, the actual number of disc I/Os will vary depending on how much of the file is in memory. Stan Sieler of Allegro Consultants (co-author of Beyond RISC! and one of the foremost experts on MPE/XL and the RISC architecture) ran some experiments that showed that a mapped read of a file that was 100% non-memory-resident took more than 3 times the elapsed time (though only about half the CPU time) of a file-system read; a mapped read of a 100%- memory-resident file took less than 1/9th the elapsed and CPU time of a file-system read. The moral of the story: * Blocking factors are no longer relevant to performance. * NOBUF and MR NOBUF can still be a good idea. * Mapped file access is much faster for memory-resident files, much slower for non-memory-resident files. Finally (as Stan Sieler discusses in his paper and as we'll discuss more in the NM FILES VS. CM FILES chapter), KSAM access can be faster from Compatibility Mode than from Native Mode -- it's faster still from OCTCOMPed code. Oh, yes, one other thing: FOPEN calls are much faster on MPE/XL than they were on MPE/V (they typically take from 25 to about 100 milliseconds running stand-alone on our 925/LX, compared to about 300 to about 500 milliseconds on a stand-alone Micro/3000). This may not seem like much, but this can be very important for programs that open some files, do a few checks, and then terminate (e.g. logon UDC security programs). These programs can now take a lot less time than before. CM FILES VS. NM FILES Every so often, you'll hear people talk about CM (Compatibility Mode) files and NM (Native Mode) files. There are a few things that are worth saying about this distinction. The first thing that might come to mind is that a CM file is somehow accessible only from CM and an NM file only from NM. This is not so; both kinds of files are equally accessible from both modes (and, of course, from OCTCOMPed code, too); in fact the access is completely transparent -- nothing behaves any differently (at least externally) from one mode to the other. The distinction between CM files and NM files is purely internal. CM files are those for which the internal file system code is implemented in CM. KSAM files, message files, circular files and RIO files -- the code that handles these files has simply never been rewritten by HP in PASCAL/XL; whenever you access these files MPE/XL will execute the CM code that pertains to these files even if this requires switching from NM to CM. NM files of course are those whose internal code is implemented in NM -- they include all the "vanilla" files, including both fixed and variable-record length files and IMAGE databases The main way in which this CM/NM difference manifests itself is in speed of file access. As we said, if you try to access a CM file from NM (or an NM file from CM), the system will have to switch into the other mode in order to execute the appropriate file system code. In addition to the switch from, say your NM program to the CM file handling procedures, the CM file handling procedures will then have to switch to the NM internal file system procedures to do the actual file I/O. All these switches can take a non-trivial amount of time; for instance it took a CM program more than 23 times longer to read a circular file than an otherwise identical non-circular file; however even with this the CM program ran 20% faster than an NM program reading the same circular file! A bizarre incident indeed -- NM code running slower than CM code. This would not be that much of a problem if the only files that were slower in NM were message files, circular files, and RIO files -- after all, how much are these rather esoteric file types used in production? Unfortunately, the same thing applies to KSAM files, which can indeed often be quite performance-critical. My tests (and Stan Sieler's as well) showed that KSAM file accesses from NM were over 10% slower than from CM and about 20% slower than from OCTCOMPed code. This might very well mean that KSAM users ought not migrate their programs to Native Mode for now (presumably, HP will come out with an NM KSAM soon). It seems that converting to NM will slow your KSAM file accesses down by about 20% (compared to OCTCOMPed code -- if you're still in CM, you should probably be OCTCOMPing all your code); you'll have to balance this against whatever performance improvement you expect to get on your other, non-KSAM-file-access code. HPFOPEN A number of the new features of the MPE/XL File System (including mapped file access and a few others that we'll talk more about shortly) have been implemented in the new HPFOPEN intrinsic, a successor to the old well-loved FOPEN. Why a new intrinsic? Because the old FOPEN intrinsic, with its limited number of parameters (13 of them), just didn't have enough room for all the data that needed to be passed. By the time MPE/XL came around, * 14 of the 16 foptions bits and 13 of the 16 aoptions bits were used up; * The "device" parameter was actually used to pass no less than 4 different values (the device, the environment file, the tape density and the ;VTERM parameter); * The "forms message" parameter was used to pass 3 different values (the forms message, the tape label, and the KSAM file characteristics). The MPE/V designers squeezed every last bit (almost) out of the FOPEN intrinsic because it was designed in an inherently non-expandable way; there was no way HP could have fit in the new parameters required to support the new features of the MPE/XL file system. Much like the CREATEPROCESS intrinsic supplanted the CREATE intrinsic before it, HPFOPEN was designed to be a much more expandable (albeit, in some respects harder to use) version of FOPEN. The general calling sequence of HPFOPEN is HPFOPEN (FNUM, (* 32-bit integer by reference *) STATUS, (* 32-bit integer by reference *) ITEMNUM1, (* 32-bit integer by value *) ITEM1, (* by reference *) ... ITEMNUMn, (* 32-bit integer by value *) ITEMn,); (* by reference *) HPFOPEN takes as input a list of item numbers and item values; it returns the file number and the error status. A typical call might be (naturally we hope that you define constants for the item numbers -- 2, 3, 11, 12, 13, etc. -- and for the possible domain, access type, exclusive state, etc. values): LOCK:=1; DOMAIN:=1 (* old *); ACCESS_TYPE:=4 (* input/output *); FILENAME: = '/MYFILE.MYGROUP.MYACCT/'; EXCLUSIVE:=3 (* shared *); HPFOPEN (FNUM, STATUS, 2, FILENAME, 3, DOMAIN, 11, ACCESS_TYPE, 12, LOCK, 13, EXCLUSIVE); IF STATUS<>O THEN PRINTFILEINFO (0); The same call with the FOPEN intrinsic would be: FIlENAME:='MYFILE.MYGROUP.MYACCT '; FNUM:=FOPEN (FILENAME, 1, OCTAL('344')); IF CCODE<>2 (* condition code equal *) THEN PRINTFILEINFO (0); As you see, the HPFOPEN intrinsic call is actually rather more verbose than the FOPEN intrinsic call, and may be argued to be harder to write, especially since you actually have to declare the variables DOMAIN, LOCK, ACCESS_TYPE, and EXCLUSIVE (since they, like all HPFOPEN item values, must be passed by reference). On the other hand, it does keep you from having to remember what all the positional parameters are (quick -- which FOPEN parameter is the file code?). More importantly, HPFOPEN lets you do things that FOPEN won't: * Open a file for mapped access (items number 18 and 21). * Open a file given the entire right-hand side of a file equation (item number 52). This way you don't have to worry about all the other items or any magic numbers -- just say something like: FILEEQ:='%MYFILE.MYGROUP.MYACCT,OLD;ACC=INOUT;SHR;LOCK%'; HPFOPEN (FNUN, STATUS, 52, FILEEQ); This can be a lot cleaner than the normal HPFOPEN approach, especially if all the parameters are constant (rather than having one be a variable in which case you'd have to assemble the FILEEQ string using a STRWRITE or, in C, an sprintf). Unfortunately, not all file parameters are supported with this syntax -- exceptions include mapped files, user labels, disallowing file equations and several others. Note that the value of FILEEQ started and ended with a "%"; it actually didn't matter what character it started and ended with as long as it was the same character. Rather than rely on terminators such as blank, semicolon, or whatever, HPFOPEN lets you specify your own string terminator as the first character in the string. In almost all the HPFOPEN items that are strings (including the filename parameter itself) must be passed this way. * Open a file as a new file and immediately save it as a permanent file (item number 3, value 4); this avoids the MPE/V headache of having an FOPEN succeed and then at the very end of the program having the FCLOSE fail because a file with this name already exists. * Specify, when a file is opened, what disposition it is to be closed with (item number 50). In other words, if you open a file that you know should be purged when you're done with it, you can indicate this on the HPFOPEN call; then, even if the program aborts before FCLOSEing the file, the file will get deleted. * And a few other, less important things. In the future, though, new features that are added to the file system will be added through the HPFOPEN intrinsic, not through the already overloaded FOPEN, so this list will probably grow with time. One other important point: how are you to interpret the STATUS variable that HPFOPEN returns to you? The manual tells you that the low-order 16 bits are always 143 (the code indicating that this is a File System error) and the high-order 16 bits are values from a 16-page table in the Intrinsics Manual. Naturally, rather than referring the user of your program to this manual, you really ought to format the message yourself, using the HPERRMSG intrinsic: HPERRMSG (2, O, O, STATUS); Easy enough to do, but I'll bet you that half the programs you run won't do this. If they don't and you have MPEX Version 2.2.11 or later, you can just say %CALC WRITEMPEXLSTATUS (statusvalue) and get the text of the error message. Of course both the HPERRMSG call and the %CALC WRITEMPEXLSTATUS will work for all "MPE/XL-standard" 32-bit status results. Finally, a few other interesting points: * Whenever you create a file using HPFOPEN and do not specify a file limit, it will be built with a file limit of 8,388,607 records (not the measly little 1,023 that are the default on MPE/XL). This may be a good idea in theory, but in practice it means that you must dose your files with disposition 16 (the "trimming" disposition) since otherwise your file will be allocated in chunks of 2,048 sectors each, so you could easily have some 2048-sector files with one or two records. * It has been said that HPFOPEN is not callable from CM programs. HPFOPEN is indeed an NM procedure so it can't be called from CM programs as simply as, say, FOPEN can be; however, using HPSWTONMNAME (or HPLOADNMPROC and HPSWTONMPLABEL), one can relatively easy switch directly to HPFOPEN, passing to it whoever parameters you please. You don't have to write any native mode code to do this, nor do you have to put anything into any SLs or NLs -- it's a bit trickier than a direct call, but not by all that much. It is, however, true that there is no (documented) way of directly manipulating virtual pointers in CM, so mapped file access from CM is pretty much out. ACCESSING FILES While internal file structure and disc space considerations have changed dramatically with MPE/XL, the rules for accessing and sharing files have not (except for the addition of mapped files and the decrease in importance of NOBUF and MR NOBUF access). There's no reason to go into them in much detail now; I'll just go through a few of the key items that are worth repeating: * If you want to have multiple writers appending to a (non-KSAM) file, use ;SHR;GMULTI;ACC=APPEND access. If you do this, you will not need to lock the file. * If you want to have multiple writers doing any sort of writing other than appending, be sure that you lock the file, not just before the write but before any read done in preparation for the write. Thus, if a process needs to read a record, calculate a new value for a field in the record, and then write the record back, it must lock before the read and unlock after the write; otherwise, it risks the record being modified by somebody eke between the read and the write and then having this other person's modifications wiped out. * Attempts to lock a file (or a database) when you already have a file or database locked will normally fail with an FSERR 64. If you :PREP your program with ;CAP= MR, the attempt will succeed, but you stand the risk of causing a deadlock (which will still require a system reboot to resolve). * If you must use ;CAP = MR, make sure that all your multiple locks are acquired in the same order -- if one program locks file A and then file B, all programs must lock those files in that order; otherwise, if any program locks file B and then file A, a deadlock becomes quite possible. PROTECTING YOUR FILES AGAINST SYSTEM FAILURES MPE/XL relies very heavily (even more so than MPE/V) on disc caching -- keeping as much disc data in memory as possible to speed up access to it. Unlike MPE/V, which, by default, only used this cache for reads and always did the writes to disc, MPE/XL caches writes, too; if you write a record to a file, that record might not get written to disc for an indefinite amount of time. This has some substantial performance advantages (since a lot of disc I/O is avoided this way), but obviously puts your files very much at risk when the system crashes. KSAM files and IMAGE files seem to be protected by MPE/XL against loss of data at system failure time; unfortunately plain MPE files can very easily lose a lot of recently-written data when the system crashes. One of these forms of data loss could happen (and often did) on MPE/V -- when you're appending to an MPE file, the EOF pointer does not get updated on disc until the file is closed or a new extent is allocated. Thus, the data that you append to an MPE file can get completely destroyed by a system failure because the EOF pointer did not get properly set. The solution to this problem, just as in MPE/V, is to do FCONTROLs mode 6, which post the EOF pointer to disc, as often as possible when you're appending to an MPE file. You might, for instance, do an FCONTROL mode 6 after every write, which will give you almost complete safety but also slow things down substantially; or, you could keep a counter, and do FCONTROLs mode 6 every, say, five or ten records, thus minimizing your overhead while still protecting most of your data. Unfortunately, on MPE/XL, there's more to it than this. Any data that you write to a plain MPE file -- even if you're not appending to it -- might get lost in a system failure because it may not get posted to disc until some time after you do the writes. On MPE/V, this possibility was limited to the data that was in your memory buffers (usually no more than about 2 blocks worth of data); on MPE/XL, any data written since you opened the file could conceivably be lost. For example, I ran a test with a file of 1000 records, each 256 bytes wide; I overwrote all 1000 records, kept the file opened, and re-booted the system. When the system came back up, only the first 768 of the new records were actually in the file; the remaining 232 records were still the old records from the time before I did my writes. (Note that 768 = 3*256; I'm not sure if there's any significance to this but I suspect that there is.) What can you do about this? Well, the simplest solution seems to be to call the FSETMODE intrinsic with the second parameter set to 2. This means (according to the manual) "force your program to wait until the physical write operation is completed (the record is posted)", and this is what it seems to do. Of course, this causes each logical write to generate a physical I/O -- a great deal of overhead -- but it protects your data. Alternatively, you can call FCONTROL mode 2 or FCONTROL mode 6 after each write or once every several writes (FCONTROL mode 2 is faster and may work well in cases where you're not appending and thus need not post the EOF); this is more work for you as a programmer than just calling FSETMODE, but it may be more efficient because you can do the FCONTROLs once every several records, thus decreasing the overhead of the extra disc I/O (but increasing the amount of data you may lose in case of a system failure). A FEW WORDS ABOUT PERFORMANCE TESTS The performance guidelines I've talked about (such as "FREADs of files that aren't in memory are faster than mapped file accesses" or "FCONTROLs mode 2 are faster than FCONTROLs mode 6") are strictly based on experience (my own or Stan Sieler's -- see his "MPE XL and Performance: Not Incompatible" paper). This experience may be inapplicable to your particular application, inapplicable to your version of the operating system, or perhaps just plain mistaken; I strongly encourage you to run your own performance tests to figure out how fast various file access methods work for you. Unfortunately, file system performance measurement on MPE/XL is substantially more difficult than on MPE/V because of MPE/XL's immense caching capabilities. It is almost guaranteed that, if you run a test twice in a row, you will get completely different results -- the first time your data was quite likely out on disc, but the second time it had just been read into memory and was therefore quite probably still in memory. Unlike MPE/V, there are no :STOPCACHE commands that you can use to make sure that this doesn't happen. There are two key things you can do to detect possible bias due to a file's presence in memory and to avoid such bias: * To find out how much of a file is in memory, do the following: - Go into DEBUG. - Enter "MAP filename" to open the file as mapped; this will output a line such as: 1 MYFILE.MYGROUP.MYACCT 1234.0 Bytes = ... - The 1234.0 in the above line is the virtual memory address of the file -- type =VAINFO(1234.0, "PAGES_IN_MEM")*#16,# The value output will be the number of sectors of the file that are currently in memory (the #16 is there because there are 16 sectors per 4,096-byte page). - Close the file by saying "UNMAP n", where n is the first number output by the MAP command (in this example, 1). * Getting the file out of memory is a tougher proposition. My experience has been that the only way of doing this is to cause enough memory pressure to get the file's pages to be discarded (after they have, of course, been flushed to disc). One way of doing this is to read a very large file into memory; SL.PUB.SYS (22 megabytes on my system), NL.PUB.SYS (15 megabytes), and XL.PUB.SYS (6 megabytes) are good candidates. Just say: FILE S=SL.PUB.SYS;LOCK COPY *S,TESTFILE;YES PURGE TESTFILE This will read all of SL.PUB.SYS into memory, which on my 925/LX is enough to flush any other files I may already have in memory. All you rich people out there with 128 megabytes of unused memory may need more than just this file, but you can always tell if the flushing succeeded by using DEBUG's VAINFO function discussed above -- if it tells you that your file has only 0 pages in memory, you know that you've flushed it out. Given these precautions, you should be able to do your own performance tests (on an otherwise idle system, of course). Beware, though -- at least one key test I know of yielded completely different results on MPE/XL 1.1 and 1.2 -- much of the file system's performance characteristics seem to be quite MPE-version-dependent. ODDS AND ENDS Finally a few miscellaneous features which couldn't fit in anywhere else: * DEBUG/XL's MAP command makes the debugger a powerful data file editor even more convenient than the old DISKED on MPE/V. (Anything would be more convenient than DISKED. Do you remember how, when you asked for it to display octal and ASCII on the same line it would display 8 words of the data in octal and then the first 8 bytes of the data in ASCII, completely ignoring the last 8 bytes?) In DEBUG/XL, you can say MAP filename WRITEACCESS DEBUG will output for you the "file index number" (used to close the file with the UNMAP command), the filename, the file's visual address, and the file size, e.g. 1 MYFILE.MYGROUP.MYACCT 1237.0 Bytes = 7560 (For this example, we're assuming that you're in CM debug, so the numbers are output in octal; in NM debug, the output and default input would be in hex.) You can then display and modify data with addresses 1237.0 through 1237.7557 -- all the bytes in the file; thus, you could say DV 1237.200,10 to display the 10 (octal) 32-bit words starting with byte 200 (octal) of the file -- the MV command will let you modify data. Note that DV and MV expect byte addresses, not record numbers and offsets within records; you have to do the calculation yourself (for instance, if each file's records are #256 [decimal] bytes long, record #10 occupies bytes #2560 through #2815). The MAP command also provides you with one of the few ways to easily see (and edit) a file's user labels (:FCOPY, for instance, doesn't let you display their contents, and neither does the :PRINT command). When MAP gives you an address whose second word is not 0 (1237.1400, for instance, rather than 1237.0), this means that the file has user labels (in this case, %1400/%400 = 3 labels). User label 0 starts at 1237.0, user label 1 starts at 1237.400, user label 2 starts at 1237.1000, and data record 0 starts at 1237.1400. (User labels are always %400 = #256 bytes long.) Thus, you can use DV and MV to modify the file's user labels; also, remember that data record 0 now starts at a byte address other than 0 (in the example, %1400) -- keep this in mind when calculating the byte address of a particular byte in a particular record. If your file's records are #80 (= %120) bytes long, then, say, byte 6 of record 4 will be at location 1094 (= %1400+4*%120+6). * If you do a :DISCFREE A (which shows you how many bee space chunks of each size there are on your disc), beware! You'll often see several large free chunks on LDEV 1 even though you're running out of disc space (or at least of contiguous disc space). On MPE/XL (unlike MPE/V), transient space (analogous to MPE/V's virtual memory) is treated as free disc space; however at least 17% of the system disc (or more if you configure it that way) is reserved for transient space. Thus, you could have a huge chunk of free space on your system disc and still have it completely unusable for new disc files because it's reserved for transient space. :DISCFREE B tells you how much space is reserved for transient space, so its output shouldn't be too confusing; however, :DISCFREE A's output can be quite misleading if you don't keep the transient space issue in mind. This should probably not be overwhelmingly important, since contiguous space is less important on MPE/XL than on MPE/V, and you should therefore run :DISCFREE B more often than :DISCFREE A; however, I got bit by this thing myself when I was doing research for this paper, so I decided to mention it. CONCLUSION The MPE/XL file system is different from the MPE/V file system in many respects but is also similar to it in many respects. This paper was largely dedicated to the differences (since they're more interesting), but there are very many similarities as well, largely dictated by the requirement of complete (well, almost complete) compatibility -- a requirement that HP rigidly enforced on itself, and, I must say ,very much lived up to. Many of the old and unpleasant limitations of the MPE/V file system have been lifted; a few remain in place (such as the 3-level directory structure and a few other, relatively minor, problems); a few new ones have probably been added, but the user community hasn't discovered them yet and probably won' t for some time. (Who would have thought, in 1972, that people would be running into the 2,097,120-sector limit on file size?) Performance and disc space are still potential problems, and will be for a long time to come as long as CPU power and disc storage cost money. I would like to thank Jason Goertz, Bob Green, Randy Medd, David Merit, Ross Scroggs, and, especially, Steve Cooper and Stan Sieler for their reviewing the paper and for their many excellent comments and suggestions. I would also like to refer the interested reader to Stan Sieler's "MPE XL and Performance: Not Incompatible" paper, published in the SCRUG '89 Proceedings, and, of course, to the Beyond RISC! book from Software Research Northwest (by S.Cooper, J.Goertz, S.Levine, J.Mosher, S.Sieler, and J.Van Damme edited by W.Holt).