From 7b9c723f729d55c32932c51de74fd18fe28f4429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Soriano?= Date: Sun, 23 Aug 2020 17:59:25 +0200 Subject: [PATCH] Improve the performance of cluster chain allocation (#68) 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). --- source/kernel/bank2/fat.mac | 113 +++++++++++++++++++++++++++++++----- source/kernel/bank2/rw.mac | 6 ++ source/kernel/bank2/val.mac | 10 +++- source/kernel/kvar.mac | 2 + 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/source/kernel/bank2/fat.mac b/source/kernel/bank2/fat.mac index 6f3d8e56..952a3898 100644 --- a/source/kernel/bank2/fat.mac +++ b/source/kernel/bank2/fat.mac @@ -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 @@ -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 ; ; @@ -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 @@ -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 @@ -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 @@ -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 end ; diff --git a/source/kernel/bank2/rw.mac b/source/kernel/bank2/rw.mac index 3d04b522..3f738bbc 100644 --- a/source/kernel/bank2/rw.mac +++ b/source/kernel/bank2/rw.mac @@ -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. ; diff --git a/source/kernel/bank2/val.mac b/source/kernel/bank2/val.mac index fd22d1f9..def23ff8 100644 --- a/source/kernel/bank2/val.mac +++ b/source/kernel/bank2/val.mac @@ -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 @@ -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 diff --git a/source/kernel/kvar.mac b/source/kernel/kvar.mac index e3fa150d..9900876d 100644 --- a/source/kernel/kvar.mac +++ b/source/kernel/kvar.mac @@ -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