我正在尝试加速C中的一些常规数据处理。我已经编写了几种形式的子程序:
double *do_something(double *arr_in, ...) {
double *arr_out;
arr_out = malloc(...)
for (...) {
do the something on arr_in and put into arr_out
}
return arr_out;
}
我喜欢这种风格,因为它易于阅读和使用,但我经常将其称为:
array = do_something(array,...);
如果我改为使用void子函数,它是否会产生更快的代码(并且可能防止内存泄漏):
void do_something(double *arr_in, ...) {
for (...) {
arr_in = do that something;
}
return;
}
更新1: 我在可执行文件上运行了valgrind --leak-check = full,看起来使用第一种方法没有内存泄漏。但是,可执行文件链接到一个库,该库包含我使用此表单创建的所有子例程,因此它可能无法捕获库中的泄漏。
我很好奇我将如何编写包装来释放内存以及**真正做什么,或指针指针是什么,所以我避免使用**路径(那也许我做错了,因为它没有在我的mac上编译。)
这是一个当前子程序:
double *cos_taper(double *arr_in, int npts)
{
int i;
double *arr_out;
double cos_taper[npts];
int M;
M = floor( ((npts - 2) / 10) + .5);
arr_out = malloc(npts*sizeof(arr_out));
for (i=0; i<npts; i++) {
if (i<M) {
cos_taper[i] = .5 * (1-cos(i * PI / (M + 1)));
}
else if (i<npts - M - 2) {
cos_taper[i] = 1;
}
else if (i<npts) {
cos_taper[i] = .5 * (1-cos((npts - i - 1) * PI / (M + 1)));
}
arr_out[i] = arr_in[i] * cos_taper[i];
}
return arr_out;
}
根据我在这里得到的建议,听起来更好的方法是:
void *cos_taper(double *arr_in, double *arr_out, int npts)
{
int i;
double cos_taper[npts];
int M;
M = floor( ((npts - 2) / 10) + .5);
for (i=0; i<npts; i++) {
if (i<M) {
cos_taper[i] = .5 * (1-cos(i * PI / (M + 1)));
}
else if (i<npts - M - 2) {
cos_taper[i] = 1;
}
else if (i<npts) {
cos_taper[i] = .5 * (1-cos((npts - i - 1) * PI / (M + 1)));
}
arr_out[i] = arr_in[i] * cos_taper[i];
}
return
}
呼叫:
int main() {
int npts;
double *data, *cos_tapered;
data = malloc(sizeof(data) * npts);
cos_tapered = malloc(sizeof(cos_tapered) * npts);
//fill data
cos_taper(data, cos_tapered, npts);
free(data);
...
free(cos_tapered);
...
return 0;
}
答案 0 :(得分:5)
相对于您正在进行的处理,malloc可能很昂贵,具体取决于它是什么。不要将自己局限于就地处理,只需使用两个参数,in和out,并将分配留给调用者。这为调用者提供了重用内存的选项,而无需为每个调用分配新数组。
答案 1 :(得分:1)
如果没有其他指向原始内存分配的指针,第一次调用很容易泄漏内存 - 因为您可能已经知道,因为您要问。
是的,如果您可以在没有内存分配的情况下合理地编写被调用函数的第二个版本,它可能会更快,因为内存分配需要时间。如果你只是修改被调用的函数,因此它有预先分配的输入和输出数组,它可能只是将内存分配成本转移到调用函数。
但是第一版的纪律使用很好;该函数分配空间,只要你跟踪传入的原始空间和传回的新空间并且能够释放两者,就没有问题。
你可以通过以下方式让自己陷入“同样”的问题:
xyz = realloc(xyz, newsize);
如果xyz是指向已分配内存的唯一指针,那么在分配失败时会泄漏内存,因为您刚刚使用空指针破坏了xyz。如果有另一个指针用于释放原始空间,这个成语并不重要 - 但要小心它。
自从写完这个答案的原始版本以来,我没有完全消解问题中的其他信息。
答案 2 :(得分:1)
如果您可以在适当的位置进行操作,那么这样做可能有助于防止错误(至少与内存相关)并且至少会在执行malloc()
操作所需的时间内更快。函数的实际返回类型可能不会以任何方式影响速度。
答案 3 :(得分:1)
双重本身的返回在执行时间方面不会花费太多。
每次进入函数时,更重要的是内存分配。如果您可以按照建议预先分配或存储结果,那么应该可以大大提高速度。
要考虑的另一件事是你是否真的需要double提供的所有精度(相对于float类型)。在许多情况下,花车要快得多。
答案 4 :(得分:1)
如果他们愿意,我会选择让调用者分配内存,但也可以选择让操作完成,或者让你进行分配。
对于无法完成的操作,您可以手动检查调用者是否为您提供了相同的输入和输出位置,并自行复制输入。然后使用该副本作为输入进行处理。这使它看起来适合函数调用者。
例如,假设您要创建一个函数,该函数对一个索引数组进行混洗,例如output[i] == input[ input[i] ]
(一个愚蠢的函数,是真的,但这个函数非常重要)。
#include <stdlib.h>
#include <string.h>
int shuffle(size_t const * input, size_t const size, size_t ** p_output)
{
int retval = 0;
size_t i;
char in_place = 0;
char cleanup_output = 0;
if (size == 0)
{
return 0; // nothing to do
}
// make sure we can read our input and write our output
else if (input == NULL || p_output == NULL)
{
return -2; // illegal input
}
// allocate memory for the output
else if (*p_output == NULL)
{
*p_output = malloc(size * sizeof(size_t));
if (*p_output == NULL) return -1; // memory allocation problem
cleanup_output = 1; // free this memory if we run into errors
}
// use a copy of our input, since the algorithm doesn't operate in place.
// and the input and output overlap at least partially
else if (*p_output - size < input && input < *p_output + size)
{
size_t * const input_copy = malloc(size * sizeof(size_t));
if (input_copy == NULL) return -1; // memory allocation problem
memcpy( input_copy, input, size * sizeof(size_t));
input = input_copy;
in_place = 1;
}
// shuffle
for (i = 0; i < size; i++)
{
if (input[i] >= size)
{
retval = -2; // illegal input
break;
}
(*p_output)[i] = input[ input[i] ];
}
// cleanup
if (in_place)
{
free((size_t *) input);
}
if (retval != 0 && cleanup_output)
{
free(*p_output);
*p_output = NULL;
}
return retval;
}
这使您的函数更加健壮 - 函数调用者可以为输出分配内存(如果他们想要保持输入),或者让输出出现在与输入相同的位置,或者为内存分配内存输出。如果他们从其他地方获得输入和输出位置,并且不确定它们是否是不同的,那么这尤其好。他们不必了解函数的工作原理。
// caller allocated memory
my_allocated_mem = malloc( my_array_size * sizeof(size_t) );
if(my_allocated_mem == NULL) { /*... */ }
shuffle( my_array, my_array_size, &my_allocated_mem );
// function allocated memory
my_allocated_mem = NULL;
shuffle( my_array, my_array_size, &my_allocated_mem );
// in place calculation
shuffle( my_array, my_array_size, &my_array);
// (naughty user isn't checking the function for error values, but you get the idea...)
您可以看到使用here的完整示例。
由于C没有异常,因此使用函数的返回值来报告错误并通过函数指针返回计算值是相当标准的。
答案 5 :(得分:1)
我刚刚运行了你的代码(在修复了一些小错误之后)。然后我拿了几个stackshots。那些说malloc
会成为你罪魁祸首的人是对的。几乎所有你的时间花在那里。与此相比,其余代码并不是很重要。这是代码:
#include <math.h>
#include <stdlib.h>
const double PI = 3.1415926535897932384626433832795;
void cos_taper(double *arr_in, double *arr_out, int npts){
int i;
// double taper[npts];
double* taper = (double*)malloc(sizeof(double) * npts);
int M;
M = (int)floor( ((npts - 2) / 10) + .5);
for (i=0; i<npts; i++){
if (i<M) {
taper[i] = .5 * (1-cos(i * PI / (M + 1)));
}
else if (i<npts - M - 2) {
taper[i] = 1;
}
else if (i<npts) {
taper[i] = .5 * (1-cos((npts - i - 1) * PI / (M + 1)));
}
arr_out[i] = arr_in[i] * taper[i];
}
free(taper);
return;
}
void doit(){
int i;
int npts = 100;
double *data, *cos_tapered;
data = (double*)malloc(sizeof(double) * npts);
cos_tapered = (double*)malloc(sizeof(double) * npts);
//fill data
for (i = 0; i < npts; i++) data[i] = 1;
cos_taper(data, cos_tapered, npts);
free(data);
free(cos_tapered);
}
int main(int argc, char* argv[]){
int i;
for (i = 0; i < 1000000; i++){
doit();
}
return 0;
}
编辑:我计算了上面的代码,在我的机器上占用了22us(大多数在malloc
)。然后我修改它只在外面做一次mallocs。这将时间减少到了5.0us,这主要是在cos
函数中。然后我从Debug转换为Release版本,将时间减少到3.7us(显然在cos
函数中显然更多)。因此,如果你真的想要快速完成它,我建议使用stackshots找出你最常做的事情,然后看看你是否可以避免这样做。
答案 6 :(得分:0)
在你的功能中
void do_something(double *arr_in, ...) { for (...) { arr_in = do_that_something; } }
这是不正确的,因为一旦do_something
函数超出范围,你就没有参数引用来传回数组..它应该看起来像这样
void do_something(double **arr_in, ...) { for (...) { *arr_in = do_that_something; } } /* ** Would be called like this: ** do_something(&array, ...); */
坚持第一个例子,因为它更容易阅读。如果对malloc
的调用失败并继续使用NULL指针处理,则需要在第一个示例中添加错误检查...
希望这有帮助, 最好的祝福, 汤姆。
答案 7 :(得分:0)
如果你没有使用malloc,你可以节省很少的时间,但如果你在紧密的循环中调用do_something,这可能会很快加起来并产生明显的差异。你也可以节省少量的时间而不必返回双*但是,如果经常调用do_something,这可以加起来。
至于处理本身,因为两种情况都在双*
上运行,所以没有区别由于您未在建议的方法中使用动态内存分配,因此不再存在内存泄漏的可能性。
答案 8 :(得分:0)
您还可以选择将第二个参数作为out参数传递。例如
int do_something (double * in , double * out) {
/*
* Do stuff here
*/
if (out is modified)
return 1;
return 0;
}
或类似的没有回报。
答案 9 :(得分:0)
我建议如果你在一个子函数中分配内存,你要么创建一个相应的包装器进行清理,释放分配的内存,要么让它明显地表明该函数正在分配内存,以防止遗忘释放记忆。
关于内存占用,第二种方法将使用更少的内存,但只有在函数不修改初始数组的大小时才有效。根据用途,这并不总是正确的。
关于速度,第二种方法理论上应该更快,因为在函数调用结束时(do_something
)将一个较少的指针压入堆栈,但是单个指针的差异最小,除非有重复使用,在这种情况下仔细考虑内联应该已经是一个考虑因素。因此,除非您实际测量函数调用的开销是一个问题(通过分析),否则我不会在没有充分理由(内存占用或分析)的情况下烦恼这样的微优化。
答案 10 :(得分:0)
函数的类型决定了函数与调用它的代码中的位置之间的接口,也就是说选择中可能存在重要的代码设计问题。作为一项规则,这些比速度更值得考虑(如果速度问题不是内存泄漏那么大,应用程序通过颠簸遭受DOS ...)
第二种类型几乎表明了改变数组的能力。第一个是模棱两可的:也许你总是会改变数组,也许你总会提供一个新的分配结果,也许你的代码有时会做一个,有时会做另一个。自由带来了一个困难的雷区,确保代码是正确的。如果你走这条路,那么通过你的代码自由地散布assert()
s,以断言关于指针的新鲜度和共享性的不变量,在调试时可能会有充足的利益。
答案 11 :(得分:0)
嗯,你开始谈论速度的问题,我不相信这个问题真的得到了解答。首先要说的是,参与参数传递似乎不是加速事情的更好方法......
我同意其他答案:使用malloc的第一个提议是内存泄漏的高速公路(并且反正可能更慢),你提出的另一个提案要好得多。根据ergosys评论中的建议,您可以轻松地增强它并获得良好的C代码。
现在有了一些数学,你仍然可以变得更好。
首先,不需要使用double和floor调用来计算整数。 你得到相同的M没有地板,也没有加0.5只写M =(nbelts-2)/ 10; (提示:整数除法截断为整数)。
如果您还注意到您总是有M&lt; nbelt - M - 2&lt; nbelt(嗯,你当然已经知道了)你可以通过将循环分成三个独立的部分来避免测试循环内的限制。在数组中与out数组相同的情况下,仍然可以对此进行优化。
你的功能可能会变成这样:
void cos_taper(double *arr_in, double *arr_out, int npts)
{
int i;
int M;
M = (npts - 2) / 10;
if (arr_out == arr_in) {
for (i=0; i<M; i++) {
arr_out[i] *= .5 * (1-cos(i * PI / (M + 1)));
}
for (i = npts - M - 2; i<npts; i++) {
arr_out[i] *= .5 * (1-cos((npts - i - 1) * PI / (M + 1)));
}
}
else {
for (i=0; i<M; i++) {
arr_out[i] = arr_in[i] * (.5 * (1-cos(i * PI / (M + 1))));
}
for (; i<npts - M - 2; i++) {
arr_out[i] = arr_in[i];
}
for (; i<npts; i++) {
arr_out[i] = arr_in[i] * (.5 * (1-cos((npts - i - 1) * PI / (M + 1))));
}
}
}
这肯定不是它的结束,并且更多的想法可能会有更多的优化,例如像(.5 * (1-cos(i * PI / (M + 1))));
这样的表达式看起来像是可以获得相对较少的值(取决于nbelt的大小,因为它是一个函数对于i和nbelt,不同结果的数量是平方律,但cos应该再次减少该数量,因为它是周期性的)。但一切都取决于您需要的性能水平。