Improve performance of readLine by using fgets

This drops compatibility with pure CR line endings of old Mac systems
This commit is contained in:
def 2015-07-24 00:29:09 +02:00
commit 3943fba34b
2 changed files with 29 additions and 60 deletions

View file

@ -2603,17 +2603,17 @@ when not defined(JS): #and not defined(NimrodVM):
proc readLine*(f: File): TaintedString {.tags: [ReadIOEffect], benign.} proc readLine*(f: File): TaintedString {.tags: [ReadIOEffect], benign.}
## reads a line of text from the file `f`. May throw an IO exception. ## reads a line of text from the file `f`. May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline
## ``CRLF``. The newline character(s) are not part of the returned string. ## character(s) are not part of the returned string.
proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect], proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect],
benign.} benign.}
## reads a line of text from the file `f` into `line`. `line` must not be ## reads a line of text from the file `f` into `line`. `line` must not be
## ``nil``! May throw an IO exception. ## ``nil``! May throw an IO exception.
## A line of text may be delimited by ``CR``, ``LF`` or ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline
## ``CRLF``. The newline character(s) are not part of the returned string. ## character(s) are not part of the returned string. Returns ``false``
## Returns ``false`` if the end of the file has been reached, ``true`` ## if the end of the file has been reached, ``true`` otherwise. If
## otherwise. If ``false`` is returned `line` contains no new data. ## ``false`` is returned `line` contains no new data.
proc writeLn*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, proc writeLn*[Ty](f: File, x: varargs[Ty, `$`]) {.inline,
tags: [WriteIOEffect], benign, deprecated.} tags: [WriteIOEffect], benign, deprecated.}

View file

@ -31,26 +31,6 @@ proc fprintf(f: File, frmt: cstring) {.importc: "fprintf",
proc strlen(c: cstring): int {. proc strlen(c: cstring): int {.
importc: "strlen", header: "<string.h>", tags: [].} importc: "strlen", header: "<string.h>", tags: [].}
when defined(posix):
proc getc_unlocked(stream: File): cint {.importc: "getc_unlocked",
header: "<stdio.h>", tags: [ReadIOEffect].}
proc flockfile(stream: File) {.importc: "flockfile", header: "<stdio.h>",
tags: [ReadIOEffect].}
proc funlockfile(stream: File) {.importc: "funlockfile", header: "<stdio.h>",
tags: [ReadIOEffect].}
elif false:
# doesn't work on Windows yet:
proc getc_unlocked(stream: File): cint {.importc: "_fgetc_nolock",
header: "<stdio.h>", tags: [ReadIOEffect].}
proc flockfile(stream: File) {.importc: "_lock_file", header: "<stdio.h>",
tags: [ReadIOEffect].}
proc funlockfile(stream: File) {.importc: "_unlock_file", header: "<stdio.h>",
tags: [ReadIOEffect].}
# C routine that is used here: # C routine that is used here:
proc fread(buf: pointer, size, n: int, f: File): int {. proc fread(buf: pointer, size, n: int, f: File): int {.
importc: "fread", header: "<stdio.h>", tags: [ReadIOEffect].} importc: "fread", header: "<stdio.h>", tags: [ReadIOEffect].}
@ -86,40 +66,29 @@ const
proc raiseEIO(msg: string) {.noinline, noreturn.} = proc raiseEIO(msg: string) {.noinline, noreturn.} =
sysFatal(IOError, msg) sysFatal(IOError, msg)
when declared(getc_unlocked): proc readLine(f: File, line: var TaintedString): bool =
proc readLine(f: File, line: var TaintedString): bool = template returnUntil(p: int) =
setLen(line.string, 0) # reuse the buffer! line.string[p] = '\0'
flockfile(f) line.string.setLen(p)
return true
var pos = 0
# Use the currently reserved space for a first try
var space = cast[PGenericSeq](line.string).space
while true: while true:
var c = getc_unlocked(f) if fgets(addr line.string[pos], space, f) == nil:
if c < 0'i32: line.string.setLen(0)
if line.len > 0: break return false
else: return false # This will cut the string short when it contains \0
if c == 10'i32: break # LF let last = pos + cstring(addr line.string[pos]).len-1
if c == 13'i32: # CR if line.string[last] == '\l':
c = getc_unlocked(f) # is the next char LF? if last > 0 and line.string[last-1] == '\c':
if c != 10'i32: ungetc(c, f) # no, put the character back returnUntil(last-1)
break returnUntil(last)
add line.string, chr(int(c)) pos = last+1
result = true space = 128 # Read in 128 bytes at a time
funlockfile(f) line.string.setLen(pos+space)
else:
proc readLine(f: File, line: var TaintedString): bool =
# of course this could be optimized a bit; but IO is slow anyway...
# and it was difficult to get this CORRECT with Ansi C's methods
setLen(line.string, 0) # reuse the buffer!
while true:
var c = fgetc(f)
if c < 0'i32:
if line.len > 0: break
else: return false
if c == 10'i32: break # LF
if c == 13'i32: # CR
c = fgetc(f) # is the next char LF?
if c != 10'i32: ungetc(c, f) # no, put the character back
break
add line.string, chr(int(c))
result = true
proc readLine(f: File): TaintedString = proc readLine(f: File): TaintedString =
result = TaintedString(newStringOfCap(80)) result = TaintedString(newStringOfCap(80))