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)