I want to read a file, 4 lines by 4 (it's a fastq file, with DNA sequences).
When I read the file one line by one or two by two, there's no issues, but when I read 3 or 4 lines at once, my code crashes (kernel appeared to have died on jupyter notebook). (Uncommenting the last part, or any 3 out of the 4 getline()
.
I tried with a double array of char (char**) to store the lines, with the same issue.
Any idea what can be the cause ?
Using Python 3.7.3, Cython 0.29, all other libraries updated. File being read is about 1.3GB, machine has 8GB, ubuntu 16.04. Code adapted from https://gist.github.com/pydemo/0b85bd5d1c017f6873422e02aeb9618a
%%cython
from libc.stdio cimport FILE, fopen, fclose, getline
def fastq_reader(early_stop=10):
cdef const char* fname = b'/path/to/file'
cdef FILE* cfile
cfile = fopen(fname, "rb")
cdef:
char * line_0 = NULL
char * line_1 = NULL
char * line_2 = NULL
char * line_3 = NULL
size_t seed = 0
ssize_t length_line
unsigned long long line_nb = 0
while True:
length_line = getline(&line_0, &seed, cfile)
if length_line < 0: break
length_line = getline(&line_1, &seed, cfile)
if length_line < 0: break
# length_line = getline(&line_2, &seed, cfile)
# if length_line < 0: break
# length_line = getline(&line_3, &seed, cfile)
# if length_line < 0: break
line_nb += 4
if line_nb > early_stop:
break
fclose(cfile)
return line_nb
fastq_reader(early_stop=20000)
The underlying problem was my misunderstanding of getline()
getline() c reference
To store lines in different variables, an associated n
is necessary for each line pointer *lineptr
.
If *lineptr is set to NULL and *n is set 0 before the call, then getline() will allocate a buffer for storing the line.
Alternatively, before calling getline(), *lineptr can contain a pointer to a malloc(3)-allocated buffer *n bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc(3), updating *lineptr and *n as necessary.
The n
(or seed
in my code) will hold the size of the buffer allocated for the pointer, where getline() puts the incoming line. As I set the same buffer variable for different pointers, getline was given the wrong information of the size of the char* line_xxx.
As fastq files are usually in this shape:
@read_id_usually_short
CTATACCACCAAGGCTGGAAATTGTAAAACACACCGCCTGACATATCAATAAGGTGTCAAATTCCCTTTTCTCTAGCTTTCGTACT_very_long
+
-///.)/.-/)//-//..-*...-.&%&.--%#(++*/.//////,/*//+(.///..,%&-#&)..,)/.,.._same_length_as_line_2
There was no error for one or two getline() with the same buffer length, as the buffer was too small and getline sized-up the pointers.
But when using 3 or 4 getlines(), the call to length_line = getline(&line_2, &seed, cfile)
was asked to store a char* of length 2 ('+\n'), while getting (the wrong information) that the pointer line_2
was already big enough (size of line_1).
So the (simple) solution is
%%cython
from libc.stdio cimport FILE, fopen, fclose, getline
def fastq_reader(early_stop=10):
cdef const char* fname = b'/path/to/file'
cdef FILE* cfile
cfile = fopen(fname, "rb")
cdef:
char * line_0 = NULL
char * line_1 = NULL
char * line_2 = NULL
char * line_3 = NULL
# One variable for each line pointer
size_t n_0 = 0
size_t n_1 = 0
size_t n_2 = 0
size_t n_3 = 0
ssize_t length_line
unsigned long long line_nb = 0
while True:
# Reading the same file (same cfile), but line_x and n_x by pairs)
length_line = getline(&line_0, &n_0, cfile)
if length_line < 0: break
length_line = getline(&line_1, &n_1, cfile)
if length_line < 0: break
length_line = getline(&line_2, &n_2, cfile)
if length_line < 0: break
length_line = getline(&line_3, &n_3, cfile)
if length_line < 0: break
line_nb += 4
if line_nb > early_stop:
break
fclose(cfile)
return line_nb
fastq_reader(early_stop=20000)
Thanks for pointing out my mistake.