Cython 在 python 和具有未知大小 char 数组的 c 库之间进行接口
Cython to interface between python and c-library with unknown size char array
我有一个 C 库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大的 char* 到 return 数据到任何调用它的地方。这在 C 中工作正常,但使用 python/Cython 我 运行 分配内存时出现问题。
库原型是:
int readWrapper(struct options opt, char *lineOut);
我的 pyx 文件:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
# Size of array
outSize = 50000
cdef char *line_output = <char *> malloc(outSize * sizeof(char))
memset(line_output, 1, outSize)
line_output[outSize] = 0
# Call reader
return_val = readWrapper(options, line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
return df
只要 line_output 保持在 outSize
的大小内,这就可以正常工作。但是有些文件更大,那么我该如何动态地执行此操作?
根据 DavidW 的建议进行编辑
reader 包装器类似于:
int readWrapper(struct options opt, char **lineOut)
{
// Open file for reading
fp = fopen(opt.filename, "r");
// Check for valid fp
if (fp == NULL)
{
printf("file pointer is null, aborting\n");
return (EXIT_FAILURE);
}
// Allocate memory
int ARRAY_SIZE = 5000;
*lineOut = NULL;
char *outLine = malloc(ARRAY_SIZE * sizeof (char));
if (outLine == NULL)
{
fprintf(stderr, "Memory allocation failed!");
return(EXIT_FAILURE);
}
// Create line and multi lines object
char line[255];
int numWritten = 0;
int memIncrease = 10000;
while (fp != feof)
{
// Read part of file
reader(fp, opt, line);
size_t num2Write = strlen(line);
if (ARRAY_SIZE < (numWritten + num2Write + 1))
{ // Won't fit so enlarge outLine
ARRAY_SIZE += memIncrease;
outLine = realloc(outLine, (sizeof *outLine * ARRAY_SIZE));
if (outLine == NULL)
{
fprintf(stderr, "Memory re-allocation failed!");
return(EXIT_FAILURE);
}
sprintf(outLine + numWritten, "%s", line);
numWritten += num2Write;
}
} // data block loop
*lineOut = outLine;
if (fp != NULL)
{
fclose(fp);
}
return (EXIT_SUCCESS);
}
新的pyx文件:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
cdef char *line_output = NULL
# Call reader
return_val = readWrapper(options, &line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
free(options.filename)
return df
现在效果很好,但是在包装器 (C) 和 python (pyx) 部分中使用任何 printf
或 fprintf(stdout,...)
语句会导致
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
BrokenPipeError: [Errno 32] Broken pipe
使用 python3 test.py | head
时。没有头部就不会显示错误。
最后,关于文件名及其分配的建议也不适合我。使用 options.filename = file
在 运行 时产生 TypeError: expected bytes, str found
但编译。有趣的是,只有当我 运行 python 代码这样调用包装器时才会发生这种情况:
python3 test.py | head
。没有管道和头部,BrokenPipeError 不存在。因此,这没什么大不了的,但想了解是什么原因造成的。
在对 BrokenPipeError
进行一些搜索后进行编辑
这个 BrokenPipeError 问题发生在头部而不是尾部。可以在此处找到对此“错误”的解释:
解决方案 pyx 文件:
与前面提到的 readWrapper.c 一起使用的最终 reader.pyx 文件。内存分配由 C 处理并由 pyx 代码清理(最后)。
from libc.stdlib cimport free
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
char *DAY;
options opt
int readWrapper(options opt, char **lineOut);
def pyreader(file, date, debug=0):
import logging
import sys
import errno
import pandas as pd
# Init return valus
a = pd.DataFrame()
cdef options options
cdef char *line_output = NULL
# logging
logging.basicConfig(stream=sys.stdout,
format='%(asctime)s:%(process)d:%(filename)s:%(lineno)s:pyreader: %(message)s',
datefmt='%Y%m%d_%H.%M.%S',
level=logging.DEBUG if debug > 0 else logging.INFO)
try:
# Check inputs
if file is None:
raise Exception("No valid filename provided")
if date is None:
raise Exception("No valid date provided")
# Get the filename
file_enc = file.encode("ascii")
options.filename = file_enc
# Get date
day_enc = date.encode('ascii')
options.DAY = day_enc
try:
# Call reader
return_val = readWrapper(options, &line_output)
if (return_val > 0):
logging.error("pyreadASTERIX2 failed with exitcode {}".format(return_val))
return a
except Exception:
logging.exception("Error occurred")
free(line_output)
return a
from io import StringIO
try:
data = StringIO(line_output.decode('UTF-8', 'strict'))
logging.debug("return_val: {} and size: {}".format(return_val, len(line_output.decode('UTF-8', 'strict'))))
a = pd.read_csv(data, delim_whitespace=True, header=None, dtype={'id':str})
if a.empty:
logging.error("failed to load {} not enough data to construct DataFrame".format(file))
return a
logging.debug("converted data into pd")
except Exception as e:
logging.exception("Exception occured while loading: {} into DataFrame".format(file))
return a
finally:
free(line_output)
logging.debug("Size of df: {}".format(len(a)))
# Success, return DataFrame
return a
except Exception:
logging.exception("pyreader returned with an exception:")
return a
您有两个基本选择:
提前想好怎么计算尺寸
size = calculateSize(...) # for example, by pre-reading the file
line_output = <char*>malloc(size)
return_val = readWrapper(options, line_output)
有readWrapper
负责分配内存。 C:
中有两个 commonly-used 模式
一个。 return 一个指针(可能使用 NULL
表示错误):
char* readWrapper(options opt)
b。传递一个 pointer-to-a-pointer 并更改它
// C
int readWrapper(options opt, char** str_out) {
// work out the length
*str_out = malloc(length);
// etc
}
# Cython
char* line_out
return_value = readWrapper(options, &line_out)
您需要确保您分配的所有字符串都已清理干净。 options.filename
仍有内存泄漏。对于 options.filename
,您最好通过 Cython 获取指向 file
内容的指针。只要 file
存在就有效,因此您不需要分配
options.filename = file
只需确保 options
不会超过 file
(即它不会被存储以供以后在 C 中的任何地方使用)。
一般
something = malloc(...)
try:
# code
finally:
free(something)
是保证clean-up的好模式。
我有一个 C 库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大的 char* 到 return 数据到任何调用它的地方。这在 C 中工作正常,但使用 python/Cython 我 运行 分配内存时出现问题。
库原型是:
int readWrapper(struct options opt, char *lineOut);
我的 pyx 文件:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
# Size of array
outSize = 50000
cdef char *line_output = <char *> malloc(outSize * sizeof(char))
memset(line_output, 1, outSize)
line_output[outSize] = 0
# Call reader
return_val = readWrapper(options, line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
return df
只要 line_output 保持在 outSize
的大小内,这就可以正常工作。但是有些文件更大,那么我该如何动态地执行此操作?
根据 DavidW 的建议进行编辑
reader 包装器类似于:
int readWrapper(struct options opt, char **lineOut)
{
// Open file for reading
fp = fopen(opt.filename, "r");
// Check for valid fp
if (fp == NULL)
{
printf("file pointer is null, aborting\n");
return (EXIT_FAILURE);
}
// Allocate memory
int ARRAY_SIZE = 5000;
*lineOut = NULL;
char *outLine = malloc(ARRAY_SIZE * sizeof (char));
if (outLine == NULL)
{
fprintf(stderr, "Memory allocation failed!");
return(EXIT_FAILURE);
}
// Create line and multi lines object
char line[255];
int numWritten = 0;
int memIncrease = 10000;
while (fp != feof)
{
// Read part of file
reader(fp, opt, line);
size_t num2Write = strlen(line);
if (ARRAY_SIZE < (numWritten + num2Write + 1))
{ // Won't fit so enlarge outLine
ARRAY_SIZE += memIncrease;
outLine = realloc(outLine, (sizeof *outLine * ARRAY_SIZE));
if (outLine == NULL)
{
fprintf(stderr, "Memory re-allocation failed!");
return(EXIT_FAILURE);
}
sprintf(outLine + numWritten, "%s", line);
numWritten += num2Write;
}
} // data block loop
*lineOut = outLine;
if (fp != NULL)
{
fclose(fp);
}
return (EXIT_SUCCESS);
}
新的pyx文件:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
cdef char *line_output = NULL
# Call reader
return_val = readWrapper(options, &line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
free(options.filename)
return df
现在效果很好,但是在包装器 (C) 和 python (pyx) 部分中使用任何 printf
或 fprintf(stdout,...)
语句会导致
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
BrokenPipeError: [Errno 32] Broken pipe
使用 python3 test.py | head
时。没有头部就不会显示错误。
最后,关于文件名及其分配的建议也不适合我。使用 options.filename = file
在 运行 时产生 TypeError: expected bytes, str found
但编译。有趣的是,只有当我 运行 python 代码这样调用包装器时才会发生这种情况:
python3 test.py | head
。没有管道和头部,BrokenPipeError 不存在。因此,这没什么大不了的,但想了解是什么原因造成的。
在对 BrokenPipeError
进行一些搜索后进行编辑这个 BrokenPipeError 问题发生在头部而不是尾部。可以在此处找到对此“错误”的解释:
解决方案 pyx 文件:
与前面提到的 readWrapper.c 一起使用的最终 reader.pyx 文件。内存分配由 C 处理并由 pyx 代码清理(最后)。
from libc.stdlib cimport free
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
char *DAY;
options opt
int readWrapper(options opt, char **lineOut);
def pyreader(file, date, debug=0):
import logging
import sys
import errno
import pandas as pd
# Init return valus
a = pd.DataFrame()
cdef options options
cdef char *line_output = NULL
# logging
logging.basicConfig(stream=sys.stdout,
format='%(asctime)s:%(process)d:%(filename)s:%(lineno)s:pyreader: %(message)s',
datefmt='%Y%m%d_%H.%M.%S',
level=logging.DEBUG if debug > 0 else logging.INFO)
try:
# Check inputs
if file is None:
raise Exception("No valid filename provided")
if date is None:
raise Exception("No valid date provided")
# Get the filename
file_enc = file.encode("ascii")
options.filename = file_enc
# Get date
day_enc = date.encode('ascii')
options.DAY = day_enc
try:
# Call reader
return_val = readWrapper(options, &line_output)
if (return_val > 0):
logging.error("pyreadASTERIX2 failed with exitcode {}".format(return_val))
return a
except Exception:
logging.exception("Error occurred")
free(line_output)
return a
from io import StringIO
try:
data = StringIO(line_output.decode('UTF-8', 'strict'))
logging.debug("return_val: {} and size: {}".format(return_val, len(line_output.decode('UTF-8', 'strict'))))
a = pd.read_csv(data, delim_whitespace=True, header=None, dtype={'id':str})
if a.empty:
logging.error("failed to load {} not enough data to construct DataFrame".format(file))
return a
logging.debug("converted data into pd")
except Exception as e:
logging.exception("Exception occured while loading: {} into DataFrame".format(file))
return a
finally:
free(line_output)
logging.debug("Size of df: {}".format(len(a)))
# Success, return DataFrame
return a
except Exception:
logging.exception("pyreader returned with an exception:")
return a
您有两个基本选择:
提前想好怎么计算尺寸
size = calculateSize(...) # for example, by pre-reading the file line_output = <char*>malloc(size) return_val = readWrapper(options, line_output)
有
中有两个 commonly-used 模式readWrapper
负责分配内存。 C:一个。 return 一个指针(可能使用
NULL
表示错误):char* readWrapper(options opt)
b。传递一个 pointer-to-a-pointer 并更改它
// C int readWrapper(options opt, char** str_out) { // work out the length *str_out = malloc(length); // etc } # Cython char* line_out return_value = readWrapper(options, &line_out)
您需要确保您分配的所有字符串都已清理干净。 options.filename
仍有内存泄漏。对于 options.filename
,您最好通过 Cython 获取指向 file
内容的指针。只要 file
存在就有效,因此您不需要分配
options.filename = file
只需确保 options
不会超过 file
(即它不会被存储以供以后在 C 中的任何地方使用)。
一般
something = malloc(...)
try:
# code
finally:
free(something)
是保证clean-up的好模式。