48d552bf3a
- единый ленивый буфер BUFSIZ=512 на чтение и запись с автопереключением направления (_F_DIROUT, _file_sync: запись сбрасывается write()-ом, readahead откатывается lseek-ом) - статическая таблица OPEN_MAX=8 слотов вместо malloc для FILE; _fclosall через atexit — exit() сбрасывает несброшенную запись - fread/fwrite: мелкое через буфер (memcpy), блоки >= BUFSIZ — мимо буфера одним syscall; горячие пути fgetc/fputc и сканер строк fgets (LDI до '\n') — на asm, SDCC на эти цепочки генерит ~90 инструкций с IX-фреймом - новое: ungetc (1 байт через hold, работает и на stdin), fprintf/vfprintf (vsprintf+fwrite), fflush(NULL) = все потоки - фиксы stdio-review: fwrite ставит _F_ERROR при короткой записи (issue 3), fgets(n=1) возвращает пустую строку (issue 4) - замер (MAME, HDD, 100 КБ): небуферизованная оценка ~144 с → fgetc 5 с (×29), fgets ~1 с; дизайн и отвергнутые варианты — docs/file-buffering-design.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.6 KiB
C
85 lines
3.6 KiB
C
/*
|
||
* fgets — прочитать строку (до n-1 символов, '\n' сохраняется) из
|
||
* потока в buf с завершающим NUL. Возвращает buf или NULL, если EOF
|
||
* наступил до первого сохранённого символа. n=1 по стандарту —
|
||
* валидный вызов: ничего не читает, возвращает пустую строку.
|
||
*
|
||
* Быстрый путь: пока строка лежит в readahead-буфере, она копируется
|
||
* блоком asm-сканером (LDI + сравнение с '\n', ~50 тактов/байт) —
|
||
* fgetc-на-символ был в ~10 раз медленнее (замер fbench). Медленные
|
||
* случаи (консоль, hold от ungetc, режим записи, дозаполнение буфера,
|
||
* EOF) уходят посимвольно через fgetc.
|
||
*/
|
||
|
||
#include "_file.h"
|
||
|
||
/* Копировать из src в dst до max байт, остановившись ПОСЛЕ '\n'.
|
||
* Возврат: число скопированных байт | 0x8000, если '\n' найден.
|
||
* max: 1..512 (бит 15 счётчика свободен). */
|
||
static uint16_t fgets_scan(uint8_t *dst, const uint8_t *src,
|
||
uint16_t max) __naked
|
||
{
|
||
(void)dst; (void)src; (void)max;
|
||
__asm
|
||
;; HL = dst, DE = src, max на стеке (callee чистит).
|
||
pop iy ; адрес возврата
|
||
pop bc ; BC = max (> 0)
|
||
ex de, hl ; HL = src, DE = dst
|
||
push bc ; max понадобится для подсчёта
|
||
_fgs_loop:
|
||
ld a, (hl)
|
||
cp a, #0x0A ; сравнить ДО ldi: LDI не трогает Z,
|
||
ldi ; но выставляет P/V по BC ((DE)<-(HL))
|
||
jr Z, _fgs_found ; Z — от cp
|
||
jp PE, _fgs_loop ; P/V — от LDI: BC != 0
|
||
;; max исчерпан, '\n' нет: count = max
|
||
pop de
|
||
jp (iy)
|
||
_fgs_found:
|
||
;; count = max - BC, бит 15 = флаг '\n' (count <= 512 — безопасно)
|
||
pop hl ; HL = max
|
||
ld a, l
|
||
sub a, c
|
||
ld e, a
|
||
ld a, h
|
||
sbc a, b
|
||
or a, #0x80
|
||
ld d, a
|
||
jp (iy)
|
||
__endasm;
|
||
}
|
||
|
||
char *fgets(char *buf, int n, FILE *fp)
|
||
{
|
||
if (!buf || n < 1 || !fp) return NULL;
|
||
uint8_t *dst = (uint8_t *)buf;
|
||
int space = n - 1;
|
||
|
||
while (space > 0) {
|
||
if (fp->level && !(fp->flags & _F_DIROUT) && fp->hold == EOF) {
|
||
/* Быстрый блок: сканировать readahead до '\n'. */
|
||
uint16_t take = fp->level;
|
||
if (take > (uint16_t)space) take = (uint16_t)space;
|
||
uint16_t got = fgets_scan(dst, fp->curp, take);
|
||
uint16_t cnt = got & 0x7FFF;
|
||
fp->curp += cnt;
|
||
fp->level -= cnt;
|
||
dst += cnt;
|
||
space -= (int)cnt;
|
||
if (got & 0x8000) break; /* строка закончена */
|
||
continue; /* буфер иссяк — дозаполнить */
|
||
}
|
||
/* Медленные случаи + refill/EOF — посимвольно. */
|
||
int c = fgetc(fp);
|
||
if (c == EOF) {
|
||
if (dst == (uint8_t *)buf) return NULL;
|
||
break;
|
||
}
|
||
*dst++ = (uint8_t)c;
|
||
space--;
|
||
if (c == '\n') break;
|
||
}
|
||
*dst = '\0';
|
||
return buf;
|
||
}
|