Performance and Compiler Options
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), which defines the meaning of a program and none of its speed; against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, whose compiler options are described in the Programming Guide; and against GnuCOBOL 3.2 (28 July 2023, the latest release), with which the programs on it were compiled with cobc -x -Wall and run on 2026-10-20. The timings below are from one throwaway Linux container and show relative sizes, not figures for any other machine. z/OS options are described from the Programming Guide and the Performance Tuning Guide and are labelled "z/OS; not run here".
A nightly batch run is judged by how long it takes and how much CPU it uses, and most of the gains come from a small number of decisions: choosing a numeric representation suited to the arithmetic, letting the compiler optimise, reading and writing files in large blocks, and not re-doing work inside loops. The page lists those decisions, says what each costs in safety, and explains how to measure before changing anything. The numeric formats are explained on USAGE and Numeric Representation, the options are listed on Compiling, Linking and Language Environment, the table techniques on Searching and Sorting Tables, and the run-time checks that cost time but find defects on Debugging and Troubleshooting.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022) mentions performance only in passing. A separate volume on optimising COBOL by Vickler was not used for this page, and its claims (for example that COBOL has an XOR operator, or a "parallel OR") are not reproduced: they are not in the standard or in the Enterprise COBOL or GnuCOBOL manuals, so everything here comes from those manuals and from measurements shown.
Measure first
An optimisation that is not measured is a guess. Three levels of measurement are in use:
-
The job step. The job log (the
IEF374Iline and the step’s CPU time in the job statistics on z/OS;timeon a Unix shell) gives elapsed and CPU time for a step. It is the number the operations team sees and the first one to compare before and after a change. -
Language Environment storage and options reports. The run-time options
RPTOPTS(ON)andRPTSTG(ON)make LE print the options in effect and a storage report at the end of the run, which tell whether the heap or stack are being enlarged repeatedly (an expensive operation) and what the right initial sizes would be. (z/OS; not run here) -
SMF records. The system writes accounting records (type 30 for jobs and steps) with CPU and I/O counts; installations use them for charge-back and trend analysis. They are the source for "this step used 20 percent more CPU after the release".
Compare the same input before and after, run each three times, and change one thing at a time.
The cost of numeric USAGE
The same addition costs very different amounts depending on how the data is stored. The test program adds 3 to a nine-digit signed total twenty million times; only the USAGE of the total changes between runs.
* BENCH - 20 million additions; only USAGE varies.
IDENTIFICATION DIVISION.
PROGRAM-ID. BENCH.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-I PIC 9(9) COMP-5.
01 WS-TOTAL PIC S9(9) DISPLAY VALUE 0.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 20000000
ADD 3 TO WS-TOTAL
END-PERFORM
DISPLAY "TOTAL " WS-TOTAL
STOP RUN.
Compiled with cobc -x -Wall -O2 (no warnings) once for each of the USAGE values, with the time command around the run:
USAGE of WS-TOTAL |
Time (real) | Note |
|---|---|---|
|
0.89 s |
Every addition converts the digits to a number, adds and converts back. |
|
0.05 s |
Much cheaper than zoned decimal here; the arithmetic works on the packed digits directly. |
|
0.31 s |
The result is cut to nine decimal digits after every addition. |
|
0.005 s |
The binary item overflows only at the size of the binary field, as |
|
0.001 s |
No truncation to the picture; the C compiler reduces the loop to little more than the count. |
The ranking is the useful result, not the numbers. The COMP-5 loop is so short that the C compiler could optimise most of it away; a real program with I/O between the additions will not see ratios that large. The same run with -O0 for the DISPLAY case took 0.93 s and with -O2 -fno-trunc 0.90 s, so the optimisation level alone barely changed a program dominated by zoned-decimal conversions. For COMP and -fno-trunc the printed total has a different width (+0060000000 instead of +060000000) because the displayed value is the one allowed by the binary field, which is the visible side effect of turning truncation off.
On z/OS the same general ranking holds: arithmetic is fastest on binary items sized to fit a register (a PICTURE of nine digits or fewer in a fullword) and on packed decimal when the arithmetic uses decimal instructions, and it is slowest on zoned decimal that has to be unpacked; mixed usages in one expression add conversions. The practical rules are these:
-
Use
COMP-3(packed decimal) for money and for fields that are read from or written to files in that format; the arithmetic is exact and, with the right options, fast. -
Use binary (
COMP/BINARY) for subscripts, counters and indexes, and size them to a halfword (up to 4 digits), a fullword (up to 9) or a doubleword (up to 18). -
Keep
DISPLAYfor what people read or what the file format demands, and move to a numeric usage for the computation. -
Do not mix: a
COMPUTEwith a zoned operand, a packed one and a binary one converts at each step.
Options that trade safety for speed
| Option | What it changes | Cost or risk |
|---|---|---|
|
|
|
|
|
With bad data in the file |
|
Generates code to check subscripts, indexes and reference modifications at run time. |
Every checked reference costs a compare; use it for testing and where input is untrusted. |
|
Checks numeric fields for valid contents when they are used. |
The most expensive of the checks; use it during testing, to find the |
|
Warns about data items used before they are initialised. |
A compile-time check; it costs nothing at run time. |
|
|
Compile time and debuggability, not correctness; production builds normally use |
|
|
A module built with a high |
|
With |
Applies only when the files meet its rules; the sort work is faster, the code for the programs' own record processing is the same. |
|
A file whose |
Without it a file may be written with one record per block, which wastes space and I/O. |
Source: Enterprise COBOL for z/OS 6.5 Programming Guide: TRUNC; Programming Guide: NUMPROC; Programming Guide: SSRANGE; Programming Guide: NUMCHECK; Programming Guide: INITCHECK; Programming Guide: OPTIMIZE; Programming Guide: ARCH; Programming Guide: TUNE; Programming Guide: BLOCK0; Language Reference: BLOCK CONTAINS clause
GnuCOBOL: -O2, -fno-trunc and friends
GnuCOBOL passes its optimisation choice to the C compiler: -O, -O2, -O3 and -Os enable optimisation and -O0 disables it. -fno-trunc allows numeric overflow of binary fields as in the table above, and -fbinary-truncate is the default behaviour. -fno-ec=name turns an individual run-time check off. A production build is normally cobc -x -O2 without -debug, and a test build is -debug with no optimisation; the per-statement run-time checks of -debug cost far more than the optimisation level saves.
Tables, loops and files
-
Indexes versus subscripts. An index (
INDEXED BY) holds a displacement, a subscript holds an occurrence number that the compiler multiplies by the entry length at every reference. For tables searched in a loop, indexes (or binary subscripts) are faster. See Tables and OCCURS. -
SEARCH ALLoverSEARCH. A binary search on a table kept in key order examines about log2 n entries instead of n/2 on average. It requires the table to be in sequence on theKEY; sort it once at load time. -
INITIALIZEin loops.INITIALIZEof a large group generates moves of every elementary item according to its class; inside a loop executed per record this may dominate the program. Initialise once, or move low-values to a group that is known to need only that, and keep the choice documented. -
Reference modification and
STRING. ASTRINGorINSPECTover a long field per record is costly; limit it to the used part of the field. -
Files. Large blocks (use
BLOCK CONTAINS 0and a block size near half a track on disk), enough buffers (theBUFNOsubparameter of theDDstatement; the VSAMBUFND/BUFNIequivalents for VSAM files, see Indexed Files and VSAM), and sequential access instead of random access whenever the process can sort its input first. -
CALLcost. Static calls are faster than dynamic calls; the trade-off is explained on Subprograms: CALL and Linkage.
Common mistakes
-
Optimising without measuring. The slow part of a nightly run is usually a file, a sort or a database call, not a
COMPUTE. -
Turning on
NUMPROC(PFD)orTRUNC(BIN)for speed without testing the data. The run is faster and may be wrong; see the table above. -
Shipping the test build.
SSRANGE,NUMCHECKandTESTall slow the program. -
Leaving
BLOCK CONTAINSout. The data set then depends on the JCL default and the same program writes small blocks. -
Believing a micro-benchmark. The table above ranks usages; it does not predict a real job.