Reading A Complete Cylinder With One I/O

Now that we can read from a DASD device we can look at some advanced channel programming for reading. Frist we will begin with reading all the records in a cylinder at once. When reading a dataset sequentially a big performance boost could be realized by reading a cylinder at a time. This discussion applies to a physical 3350 (not an emulated 3350 on Hercules).

A platter in a 3350 turned at a rate of 6,000 rpm. Doing a little math we can determine that it takes 16.7 ms (0.000167 seconds) for the platter to make one complete revolution. When we issue a Seek for a specific record we have to wait for that record to reach the read/write heads. The length of the wait for the record to arrive depends on where it is when we issue the Search. If we are very lucky and the record is exactly in the right place our wait time is zero. If we are very unlucky and our record has just bearly passed the r/w heads we have to wait for the platter to go around a full turn – 16.7 ms. If we read a lot of records it averages out over time to (Max-Min)/2 or 16.7 / 2 which equals 8.4 ms. This delay time is called latency.

The time it takes to read a single record is the total of the Seek Time (the time it takes to position the heads if they are not on the correct cylinder) + Latency + Read Time. Seek time for a 3350 is anywhere from 10 ms to seek from one track to the next to a maximum of 50 ms to seek from the lowest to the highest cylinder.

A typical read of a 4K block could take 25 ms Seek + 8.4 ms Latency + 4.2 ms to read the block for a total of 37.6 ms. Now we can calculate the time required to read a full cylinder of 4k blocks reading them one at a time.  If there are no other datasets being accessed on the dasd device we only have the Seek delay for the first seek to the cylinder. To read a full cylinder of 4K blocks (120 blocks total) would take 25 ms Seek + (120 * (8.4 ms + 4.2 ms)) = 1537 ms.

If we read a complete cylinder with one I/O command we can cut out the latency for all except the first block.  We also guarentee that only one Seek is required.  The time to read a complete cylinder of 4k blocks is: 25 ms Seek + (30 * 16.7 ms) + 8.4 ms = 524 ms.

This is quite an improvement in time – almost 3 times faster.

Now that we know why it might be desirable to read an entire cylinder with one I/O request we can write a program to do exactly this. You can find the complete program as EXCP03 in the downloads.

CCWSRCH DC X'31',AL3(IOBSRCH),X'40',X'00',AL2(5)
CCWTIC   DC    X'08',AL3(CCWSRCH),X'40',X'00',AL2(0)   BLK CYL
CCWREAD  DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)     1   1
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)     2   1
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)     3   1
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)     4   1
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)     5   2
                                :
                                :
                                :
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)  115   29
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)  116   39
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)  117   30
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)  118   30
         DC    X'86',AL3(*-*),X'40',X'00',AL2(4096)  119   30
         DC    X'86',AL3(*-*),X'00',X'00',AL2(4096)  120   30

First we starrt with a lot of CCWs. Since there are four 4K blocks per track and 30 tracks per cylinder we need 120 read CCWs. Also note that instead of the X’06’ Read Data command we are using X’86’ Read Data Multi-Track. This will cause automatic head switching at the end of each track. We have Command Chaining set for every CCW except the last read.

         L     R3,=F'4096'        4K                               
         SLR   R2,R2              CLEAR FOR MULTIPLY               
         M     R2,=F'4'           4 BLOCKS/TRACK                   
         M     R2,=F'30'          30 TRACKS/CYL                    
*                                                                  
         GETMAIN R,LV=(R3)                                         
*                                                                  
         LA    R2,CCWREAD                                          
         LA    R3,120             120 BLOCKS/CYL                   
*                                                                  
OPEN030  DS    0H                                                  
         STCM  R1,B'0111',1(R2)   SAVE BUFFER ADDRESS INTO READ CCW
         A     R1,=F'4096'        NEXT BUFFER                      
         LA    R2,8(,R2)          NEXT CCW                         
         BCT   R3,OPEN030         LOOP BACK

We also need 120 4K buffers which is 480K that we obtain with a GETMAIN. We can then set up a loop to plug the buffer addresses into the CCWs.

         L     R1,IOBCSW          GET CSW ADDRESS           
         LA    R2,CCWREAD         FIRST READ CCW            
         SR    R1,R2                                        
         CLI   IOBECBAD,X'7F'     ALL READS COMPLETE        
         BE    READ020                                      
         S     R1,=F'8'           BACK UP FOR INCOMPLETE CCW
READ020  DS    0H

When the I/O completes we need to determine how many records were read in case we encountered an End of File before the end of the cylinder. We can determine this by subtracting the address of the first Read CCW from the address in the CSW and dividing by 8. We do need one additional step where we check to see if all CCWs completed normally and subtract out the length of the CCW that did not complete.

          C     R1,=F'0'           CHECK FOR NEG VALUE     
          BH    READ030              - NO READS SUCCESSFUL 
          SLR   R1,R1                                      
 READ030  DS    0H                                         
          SRL   R1,3               DIVIDE BY 8

We also need to check for a negative value that would indicate the Channel Program ended before the first Read CCW. In this case we set the read count to zero. Finally we divide by 8 (the number of bytes in a CCW) to get the number of Read CCWs that completed successfully.
If we can read an entire cylinder with a single I/O request it will take 25 ms Seek + 8.4 ms Latency + 501 ms to read the data for a total of 534.4 ms. We can see that this is about 3 times faster and this is if the heads don’t move. If other dataset are being accessed on the dasd drive our Seek time would greatly increase.

Here are the results when we read a dataset containing 120 records.

I/O REQUEST                                                          
   COMPLETION CODE = 7F                                              
   CSW = 095EC0 DEV STAT = 0C CHAN STAT = 00 RESIDUAL = 0000 (     0)
   --- DEVICE STATUS  = CE DE                                        
   --- CHANNEL STATUS =                                              
   SENSE = 0000                                                      
   SEEK = 00000001AE000001                                           
   BYTES READ = 1000 ( 4,096)                                        
   BLOCKS READ = (   120)

Here are the results when the dataset only contains 111 records.

I/O REQUEST                                                           
   COMPLETION CODE = 41                                               
   CSW = 095E80 DEV STAT = 0D CHAN STAT = 40 RESIDUAL = 1000 ( 4,096) 
   --- DEVICE STATUS  = CE DE UE                                      
   --- CHANNEL STATUS = IL                                            
   SENSE = 0000                                                       
   SEEK = 00000001AE000001                                            
   BLOCKS READ = (   111)

Finally here are the results when the dataset is empty (contains only an EOF record).

I/O REQUEST                                                           
   COMPLETION CODE = 41                                               
   CSW = 095B08 DEV STAT = 0D CHAN STAT = 40 RESIDUAL = 1000 ( 4,096) 
   --- DEVICE STATUS  = CE DE UE                                      
   --- CHANNEL STATUS = IL                                            
   SENSE = 0000                                                       
   SEEK = 00000001AE000001                                            
   BLOCKS READ = (     0)