Inside COBOL #83 (Making Speedware fast with COBOL)
by
Shawn Gordon
President
The Kompany

This is maybe a bit off track, but it does illustrate non-obvious ways to use COBOL in a mixed 3GL/4GL enivronment. I’ve used Speedware off and on for about 15 years now, and it is really my 4GL of choice. One of its strengths is that it treats everything as a database with the same syntax for accessing it, this allows you to swap out the underlying structure with relative ease. You could start with a flat file, change to a KSAM and then change to an Image or Allbase DBMS without ever having to change your code.

The downside to this methodology is that behind the scenes it is terribly inneffecient at large scale flat file IO. Some years ago I worked at a payroll company and when the end of the year came around we had to produce W2 forms. This was done by extracting the formatted data to a flat file and then FCOPYing it to tape and sending it out to be printed. There were tens of millions of records in these files. Using the standard IO in Speedware it took about 10 days to run. I thought this was insane so I set out to figure it out.

I decided to run a trace on what intrinsics Speedware was actually calling when it was writing to a file. Seems for each record it would FLOCK/FPOINT/FWRITE/FUNLOCK. Considering all we wanted to do was appended writes the overhead associated with the three extra intrinsics, and high overhead ones at that, was tremendous. I messed with every option in Speedware you can imagine to no avail, I could not get it to do just normal appended/exclusive access (Speedware might have fixed this by now). I then messed with file equations, also to no avail. Finally it occurred to me that Speedware has a very good and well documented ability to interface with other languages, so I realized that I could write my own file write routines in COBOL and just bypass Speedware altogether.

In the example below we have a subprogram that is loaded into an XL file which has three entry points. The entry points make it more straight forward to call the appropriate section of the code without having some switch in the calling sequence. You’ll note that we try to be intelligent about the options that are available so that you can write your code to always create a new file or append to an existing file. I also have READ access ability in these routines. I didn’t find much speed improvement by swapping that out, but it was added for completeness.

What you will be interested to note is that by replacing the native file access of Speedware with these COBOL routines, we dropped the execution time down to about 18 hours, which suddenly made it possible to run on the weekend and not destroy the performance of our machine, which was a 957 at the time.

There are a number of other cute things in here such as the use of macros and the coding of direct file intrinsics instead of using the native COBOL IO, which is also abstracted from the file system, but not in the style of Speedware. This makes the COBOL application about as fast as anything is going to be for file writes. We’ve covered pretty much all of these topics at one time or another, so I present this as an exercise in how to subvert things that frustrate you.

$CONTROL USLINIT, DYNAMIC, NOWARN, BOUNDS
IDENTIFICATION DIVISION.
PROGRAM-ID. PFILEIO.
*
*************************************************
* this series of subprograms is meant to be
* called from SPEEDWARE to do faster file io than
* the native speedware routines.  
*************************************************
*
DATE-WRITTEN. THU, JUL 17, 1997.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
     CONDITION-CODE IS CC.
DATA DIVISION.
WORKING-STORAGE SECTION.

01 FOPTIONS                PIC S9(4)  COMP VALUE 0.
01 AOPTIONS                PIC S9(4)  COMP VALUE 0.
01 ERR                     PIC S9(4)  COMP VALUE 0.
01 ERR-LEN                 PIC S9(4)  COMP VALUE 0.
01 REC                     PIC S9(4)  COMP VALUE 0.
01 EXT                     PIC S9(4)  COMP VALUE 32.
01 INITE                   PIC S9(4)  COMP VALUE 32.
01 OUT-BUFF                PIC X(80)  VALUE SPACES.
01 Z                       PIC X      VALUE SPACE.
*
01 ITEMNUM.
    03                      PIC S9(4)        COMP VALUE 19.
    03                      PIC S9(4)        COMP VALUE 0.
*
01 ITEM.
    03 EOF                  PIC S9(9)        COMP VALUE 0.
*
01 ITEMERR.
    03                      PIC S9(4)        COMP VALUE 0.
*
LINKAGE SECTION.
01 FILE-NAME               PIC X(28).
01 REC-SIZE                PIC S9(4)  COMP.
01 BLK-SIZE                PIC S9(4)  COMP.
* 1 = Create (append if there)
* 2 = New (purge if there)
* 3 = Read access
01 ACCESS-MODE             PIC S9(4)  COMP.
01 NUM-RECS                PIC S9(9)  COMP.
01 FNUM                    PIC S9(4)  COMP.
01 LS-STATUS               PIC S9(4)  COMP.
01 BUFF                    PIC X(5120).
PROCEDURE DIVISION.
$DEFINE %FOPEN=
        MOVE !1   TO FOPTIONS
        MOVE !2   TO AOPTIONS
        CALL INTRINSIC "FOPEN" USING FILE-NAME, FOPTIONS,
                                     AOPTIONS, REC, \\,
                                     \\, \\, BLK-SIZE,
                                     \\, NUM-RECS
                              GIVING FNUM
        IF CC < 0
           CALL INTRINSIC 'PRINTFILEINFO' USING FNUM
           CALL INTRINSIC 'FCHECK' USING FNUM, LS-STATUS
           CALL INTRINSIC 'FERRMSG' USING LS-STATUS, OUT-BUFF,
                                          ERR-LEN
           DISPLAY OUT-BUFF(1:ERR-LEN)
           DISPLAY 'Failed to FOPEN: ' FILE-NAME
           GOBACK
        END-IF#
*
$DEFINE %FCLOSE=
        CALL INTRINSIC "FCLOSE" USING FNUM, !1, 0
        IF CC < 0
           CALL INTRINSIC 'PRINTFILEINFO' USING FNUM
           CALL INTRINSIC 'FCHECK' USING FNUM, LS-STATUS
           CALL INTRINSIC 'FERRMSG' USING LS-STATUS, OUT-BUFF,
                                          ERR-LEN
           DISPLAY OUT-BUFF(1:ERR-LEN)
           DISPLAY 'Failed to FCLOSE!'
           DISPLAY 'Failed in FCLOSE - status = ' LS-STATUS
           GOBACK
        END-IF#
*
A1000-OPEN.
ENTRY "PFOPEN" USING FILE-NAME, REC-SIZE, BLK-SIZE, ACCESS-MODE,
                      NUM-RECS, FNUM, LS-STATUS.
     CALL INTRINSIC "FLABELINFO" USING FILE-NAME, 2,
                                       ERR, ITEMNUM,
                                       ITEM, ITEMERR.
     MULTIPLY REC-SIZE BY -1 GIVING REC.
     IF (FILE-NAME = SPACES) OR (REC-SIZE = 0) OR (BLK-SIZE = 0)
        OR (ACCESS-MODE = 0) OR (NUM-RECS = 0)
        MOVE 99                    TO LS-STATUS
        DISPLAY 'At least one parameter is missing - check'
        DISPLAY 'FILE = ' FILE-NAME
        DISPLAY 'REC-SIZE = ' REC-SIZE
        DISPLAY 'BLK-SIZE = ' BLK-SIZE
        DISPLAY 'MODE     = ' ACCESS-MODE
        DISPLAY 'NUM RECS = ' NUM-RECS
        GOBACK.
* do an FCLOSE after the open, then re-open to make sure the file
* exists in a standard form for the other routines.
     MOVE ZEROES                   TO LS-STATUS.
     IF ACCESS-MODE = 1
        IF ERR = 0
* File exists - open for append access
           %FOPEN(%5#,%3#)
        ELSE
* File needs to be created
           %FOPEN(%4#,%2#)
           %FCLOSE(%1#)
           %FOPEN(%5#,%1#)
        END-IF
        GOBACK.

     IF ACCESS-MODE = 2
* File exists - purge first
        IF ERR = 0
           %FOPEN(%5#,%1#)
           %FCLOSE(%4#)
        END-IF
*'Open new file'
        %FOPEN(%4#,%2#)
*'Save the new file'
        %FCLOSE(%1#)
*'Open the old file now'
        %FOPEN(%5#,%1#).

     IF ACCESS-MODE = 3
        IF ERR <> 0
           MOVE ERR                TO LS-STATUS
           GOBACK
        END-IF
        CALL INTRINSIC "FOPEN" USING FILE-NAME, %5, %1140
                              GIVING FNUM
        IF CC < 0
           CALL INTRINSIC 'PRINTFILEINFO' USING FNUM
           CALL INTRINSIC 'FCHECK' USING FNUM, LS-STATUS
           CALL INTRINSIC 'FERRMSG' USING LS-STATUS, OUT-BUFF,
                                          ERR-LEN
           DISPLAY OUT-BUFF(1:ERR-LEN)
           DISPLAY 'Failed to FOPEN: ' FILE-NAME.
     GOBACK.
*
A2000-WRITE.
ENTRY "PFWRITE" USING FNUM, REC-SIZE, BUFF, LS-STATUS.
     MULTIPLY REC-SIZE BY -1 GIVING REC.
     CALL INTRINSIC "FWRITE" USING FNUM, 
                                   BUFF(1:REC-SIZE), 
                                   REC, 0.

     IF CC <> 0
        CALL INTRINSIC 'PRINTFILEINFO' USING FNUM
        CALL INTRINSIC 'FCHECK' USING FNUM, LS-STATUS
        CALL INTRINSIC 'FERRMSG' USING LS-STATUS, OUT-BUFF,
                                       ERR-LEN
        DISPLAY OUT-BUFF(1:ERR-LEN)
        DISPLAY 'Failed in FWRITE - staus = ' LS-STATUS.
     GOBACK.
*
A2500-READ.
ENTRY "PFREAD" USING FNUM, REC-SIZE, BUFF, LS-STATUS.
     MULTIPLY REC-SIZE BY -1 GIVING REC.
     CALL INTRINSIC "FREAD" USING FNUM, BUFF(1:REC-SIZE), REC.
     IF CC > 0
        MOVE 9999                   TO LS-STATUS.
     IF CC < 0
        CALL INTRINSIC 'PRINTFILEINFO' USING FNUM
        CALL INTRINSIC 'FCHECK' USING FNUM, LS-STATUS
        CALL INTRINSIC 'FERRMSG' USING LS-STATUS, OUT-BUFF,
                                       ERR-LEN
        DISPLAY OUT-BUFF(1:ERR-LEN)
        DISPLAY 'Failed in FWRITE - staus = ' LS-STATUS.
     GOBACK.

A3000-CLOSE.
ENTRY "PFCLOSE" USING FNUM, LS-STATUS.
     %FCLOSE(%1#).
     GOBACK.