Skip to content

Commit

Permalink
Improve the performance of cluster chain allocation (#68)
Browse files Browse the repository at this point in the history
Improve cluster allocation by caching the last allocated cluster number.

On FAT16 drives that have 64K clusters and are almost full, allocating
new clusters is a very slow process. The entire FAT is scanned every
time, and copying big files, a process that involves many allocations,
takes an absurd amount of time.

This commit introduces a mechanism that dramatically improves the
performance of the process:

- A new field named UD_ACLU is added to the unit descriptor.
  This field is initialized to 1 when the descriptor is created.

- Whenever a cluster chain allocation happens, the process of searching
  free clusters doesn't start at cluster 2, instead it starts at the
  value stored at UD_ACLU plus one.

- If the allocation process succeeds, UD_ACLU is updated to the number
  of the last cluster that has been allocated.

- When a cluster chain is freed, UD_ACLU is updated to the lowest of
  the cluster numbers that have been freed minus one, but ONLY if that
  new value is lower than the previous value.

This algorithm guarantees that the allocation process works as usual
(the lowest numbered free clusters are allocated first), but
much faster.

Additionally, UD_ACLU is initialized to 1 again if:

- The drive is written to by using the raw sector write RDABS or WRDRV
  function calls.

- The allocation of a cluster chain fails (typically with a "disk full"
  error).
  • Loading branch information
Konamiman committed Aug 23, 2020
1 parent 9b37daa commit 7b9c723
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 16 deletions.
113 changes: 99 additions & 14 deletions source/kernel/bank2/fat.mac
Expand Up @@ -633,15 +633,29 @@ endif
; Corrupts: AF,BC
;
;
push de ;Remember caller's DE

call _AL_CLUS

ld de,(FAT_CHAIN##)
jr z,alclus_end
ld de,1
alclus_end:
call ACLU_SET
or a ;Because ACLU_SET corrupts F

pop de
ret

_AL_CLUS:
ld (AL_FLAGS##),a ;Record zero flag
push de ;Remember caller's DE
;
push bc ;Initialise cluster chain to
ld de,0FFFFh ; empty.
ld (FAT_CHAIN##),de
push de ;Previous cluster -ve => first
ld de,1 ;Skip to start search loop
jr all_clu_loop_2 ; from cluster number 1.
call ACLU_GET ;Skip to start search loop
jr all_clu_loop_2 ; from the stored lower free cluster number.
;
all_clu_loop: push bc ;Remember cluster count
push de ;Remember previous cluster
Expand Down Expand Up @@ -707,8 +721,7 @@ endif
;
;
ld bc,(FAT_CHAIN##) ;Return cluster number of
pop de ; start of chain and a zero
xor a ; error code.
xor a ; start of chain and a zero error code.
ret
;
;
Expand All @@ -723,7 +736,6 @@ disk_full: pop de ;Disk full. Free all clusters
;; bit 7,d
;; call z,FR_CHAIN
;===== end mod FAT16
pop de
ld a,.DKFUL## ;Return with an error code.
or a
ret
Expand Down Expand Up @@ -869,13 +881,43 @@ zero_dsec_loop: ld (hl),c ; the data area of buffer
; Corrupts: AF,BC,DE
;
;
free_chain_lp: push de ;Save cluster number
call FAT_LOOKUP ;Get the next cluster number
pop bc ; in the chain and save for
push de ; later. Get cluster back.
ld d,b
ld e,c ;Set FAT entry for this
ld bc,0 ; cluster to 0 to free it.
;We'll use FAT_CHAIN to temporarliy store the number of the
;lowest cluster number freed.

call free_chain_lp

;Update UD_ACLU to the number of the lowest cluster that has been
;freed -1, but only if the new value would be smaller than the
;current value. This is to ensure that UD_ACLU is always either 1
;or the lower free cluster number available -1.

push hl ;Preserve address of unit descriptor
call ACLU_GET ;DE = current value of UD_ACLU
ld hl,(FAT_CHAIN##) ;HL = Lowest cluster number freed (new value+1)
ex de,hl ;HL = current value, DE = new value+1
dec de ;DE = new value
or a
sbc hl,de ;HL = current value - new value, Cy=1 if new > current
call nc,ACLU_SET
pop hl ;Restore address of unit descriptor
ret

frchlp3:
push hl ;Preserve address of unit descriptor
ld hl,(FAT_CHAIN##)
or a
sbc hl,de ;HL = last cluster freed - cluster to free
pop hl ;Restore address of unit descriptor
jr c,frchlp2 ;Cy = 1 means last cluster freed < cluster to free
free_chain_lp:
ld (FAT_CHAIN##),de
frchlp2:
push de ; Save cluster number
call FAT_LOOKUP ; Get the next cluster number in the chain
ex de,hl ; HL = next cluster number, DE = unit descriptor
ex (sp),hl ; HL = saved cluster number, (SP) = next cluster number
ex de,hl ; HL = unit descriptor, DE = saved cluster number
ld bc,0 ; Set FAT entry for this cluster to 0 to free it
call FAT_?SET
pop de ;Get next cluster number
ld a,d ; back and return if it is
Expand All @@ -885,7 +927,7 @@ free_chain_lp: push de ;Save cluster number
ld a,d
and e
inc a
jr nz,free_chain_lp ;Loop if not end of chain.
jr nz,frchlp3 ;Loop if not end of chain.
;; bit 7,d
;; jr z,free_chain_lp
;===== end mod FAT16
Expand Down Expand Up @@ -915,6 +957,49 @@ DTY_ADD:
ret nz
dec hl
ret
;
;
;------------------------------------------------------------------------------
;
PROC ACLU_SET
;
; Sets the UD_ACLU field of a unit descriptor.
;
; Entry: DE = Value to set
; HL = Address of unit descriptor
; Returns: None
; Corrupts: F
;
push hl
push de
ld de, UD_ACLU##
add hl,de
pop de
ld (hl),e
inc hl
ld (hl),d
pop hl
ret
;
;
;------------------------------------------------------------------------------
;
; Gets the UD_ACLU field of a unit descriptor.
;
; Entry: HL = Address of unit descriptor
; Returns: DE = Value
; Corrupts: F
;
ACLU_GET:
push hl
ld de,UD_ACLU##
add hl,de
ld e,(hl)
inc hl
ld d,(hl)
pop hl
ret

finish <FAT>
end
;
6 changes: 6 additions & 0 deletions source/kernel/bank2/rw.mac
Expand Up @@ -153,6 +153,12 @@ abs_common_doerr:
jr try_rw_val

abs_rw_dorw:
push de ;If writing, reset number of next cluster to allocate
bit RF_READ,(iy+@RW_FLAGS##) ;just in case we're messing up with FAT
ld de,1
pcall z,ACLU_SET
pop de

pcall FL_UD ;Flush and invalidate any
pcall INV_UD ; buffers for this drive.
;
Expand Down
10 changes: 8 additions & 2 deletions source/kernel/bank2/val.mac
Expand Up @@ -1852,7 +1852,7 @@ endif
; Assumes: UPB_DIRT=UPB_VOLID+6 UPB_ID=UPB_DIRT+1
; (UD_CMSK, UD_CSHFT, UD_RES, UD_NFAT, UD_ODE, UD_WDS
; UD_SFAT, UD_SDIR, UD_SDAT, UD_NCLU, UD_DIRT, UD_ID,
; and UD_MBYTE) must all be sequential.
; UD_MBYTE and UD_ACLU) must all be sequential.
;
;
call _NEW_UPB
Expand Down Expand Up @@ -2155,7 +2155,13 @@ upb_dos220:
;
ld a,(ix+UPB_MBYTE##) ;Copy MEDIA DESCRIPTOR BYTE
; from UPB to unit descriptor
new_upb_fat12: ld (hl),a
ld (hl),a

inc hl ;Initialize next cluster to check for allocation to 2
ld (hl),1 ;(it's stored as value-1)
inc hl
ld (hl),0

pop hl

;--- If drive is FAT16, calculate reduced cluster count
Expand Down
2 changes: 2 additions & 0 deletions source/kernel/kvar.mac
Expand Up @@ -192,6 +192,8 @@ size macro name
field 1,UD_DIRT ;Dirty disk flag
field 4,UD_ID ;Current volume ID
field 1,UD_MBYTE ;Media descriptor byte

field 2,UD_ACLU ;Next cluster to check for allocation -1
;
field 2,UD_CDIR ;First cluster of current directory
; -ve => root directory
Expand Down

0 comments on commit 7b9c723

Please sign in to comment.