优化(C 语言):多次函数调用 vs 一次函数调用
Optimization (in C language): Many function calls vs One function call
例如,我想创建一个将节点添加到列表的函数 (insertNode
)。每次我想添加一个节点时调用 insertNode
还是将所有节点存储在一个数组中,然后调用 insertNode
一次,通过将数组作为参数传递并让函数完成剩下的工作?
代码示例:
typedef struct Data {
int *myArray; //the array where all integers are stored
int firstAvailablePos; //the first free position of myArray
} Data;
insertNode(Data *data, int newNum) {
(data->myArray)[data->firstAvailablePos] = newNum;
(data->firstAvailablePos)++;
}
alt_insertNode(Data *data, int *array, int arraySize) {
int i;
for(i = 0; i < arraySize; i++)
(data->myarray)[i] = array[i];
}
而在main
中的两个选项是:
许多函数调用
while (...) {
...
insertNode(data, newNum);
}
一个函数调用
anArraySize = 0;
while (...) {
...
anArray[i] = newNum;
anArraySize++;
...
}
alt_insertNode(data, anArray, anArraySize);
这取决于您程序中的许多因素。具体来说,节点是按某种算法排序还是顺序不重要?如果是这样,传递数组节点或单独插入不会有太大区别(排序块或信息和将节点插入排序位置应该是等效的。)
您可能还会考虑在程序中的什么位置需要将节点插入您的列表中。如果您的列表使用动态内存,则有必要在需要时沿线的某个时间插入一个节点。
这取决于底层列表实现的结构。如果这是一个数组并且 insertNode 追加它,它会被复制到内存中的一个新位置,那里有更多 space 可用,以便新元素也适合。新元素也会得到 memcpyd。这很快,因为它发生在内核中而不是在您的用户 space 程序中。
另一方面,如果你有类似链表的东西,其中存储了指向数组形式的其他列表的指针。你甚至不必复制列表中的所有元素,而只需插入一个指向链表的新指针,该指针指向包含新元素的数组。那将是非常快的。
据我所知,最佳答案是:视情况而定。
这取决于调用代码的样子,但一般来说,如果可以内联,一个简短的函数调用将具有零开销。创建一个临时数组只是为了避免函数调用开销几乎肯定是一个错误 - 但在某些情况下 "batching" 这样的东西可以避免大量的每次插入开销并且可能有意义。
同时尝试和基准测试。
函数调用比迭代要花费一些时间。如果您通过调用 insertNode 函数插入一个节点,则需要进行一些迭代(取决于您要插入的位置)将该节点插入列表中,但是如果您要插入大量节点,则需要每次都调用该函数。这可能很费时间。
如果通过将某个节点放入数组中来插入节点,最后调用insertNode将数组的节点复制到列表中。这次 insertNode 的调用时间会减少,但交互次数会增加。这不会花费时间,就像函数调用一样。
这里有一点需要注意,您需要为数组提供额外的内存。
您的代码有问题:
您对函数定义使用了过时的语法。现代 C 代码不再支持隐式 return 类型,您应该将其指定为 void
.
您使用了无关的括号,使代码看起来很别扭。
函数insertNode
不检查myArray
指向的数组是否足够大。如果需要,您应该检查并重新分配数组。
函数 alt_insertNode
不检查可用空间,也不更新 firstAvailablePos
。
根据您的重新分配方案以及您的编译器允许优化的积极程度,批量插入值可能比一个一个地插入它们更有效,特别是如果您不分配中间数组malloc()
。对您的特定测试用例进行基准测试将告诉您哪个更有效。但是请注意,使代码尽可能简单具有重要价值。
这是一个更完整的实现,可用于 运行 测试:
typedef struct Data {
int *myArray; // the array where all integers are stored
size_t size; // the number of int that can be stored
size_t firstAvailablePos; // the first free position of myArray
} Data;
/* reallocating the array with a simple exponential growth */
int insertNode(Data *data, int newNum) {
if (data->firstAvailablePos == data->size) {
size_t newSize = (data->size < 32) ? 32 : data->size + data->size / 2;
int *array = realloc(myArray, newSize * sizeof(*array));
if (array == NULL)
return -1;
data->myArray = array;
data->size = newSize;
}
data->myArray[data->firstAvailablePos++] = newNum;
return 0;
}
int alt_insertNode(Data *data, int *array, size_t arraySize) {
if (data->firstAvailablePos + arraySize > data->size) {
size_t newSize = (data->size < 32) ? 32 : data->size + data->size / 2;
while (newSize < data->firstAvailablePos + arraySize) {
newSize += newSize / 2;
}
int *array = realloc(myArray, newSize * sizeof(*array));
if (array == NULL)
return -1;
data->myArray = array;
data->size = newSize;
}
memcpy(data->myArray + data->firstAvailablePos, array, arraySize * sizeof(*array));
data->firstAvailablePos += arraySize;
return 0;
}
例如,我想创建一个将节点添加到列表的函数 (insertNode
)。每次我想添加一个节点时调用 insertNode
还是将所有节点存储在一个数组中,然后调用 insertNode
一次,通过将数组作为参数传递并让函数完成剩下的工作?
代码示例:
typedef struct Data {
int *myArray; //the array where all integers are stored
int firstAvailablePos; //the first free position of myArray
} Data;
insertNode(Data *data, int newNum) {
(data->myArray)[data->firstAvailablePos] = newNum;
(data->firstAvailablePos)++;
}
alt_insertNode(Data *data, int *array, int arraySize) {
int i;
for(i = 0; i < arraySize; i++)
(data->myarray)[i] = array[i];
}
而在main
中的两个选项是:
许多函数调用
while (...) { ... insertNode(data, newNum); }
一个函数调用
anArraySize = 0; while (...) { ... anArray[i] = newNum; anArraySize++; ... } alt_insertNode(data, anArray, anArraySize);
这取决于您程序中的许多因素。具体来说,节点是按某种算法排序还是顺序不重要?如果是这样,传递数组节点或单独插入不会有太大区别(排序块或信息和将节点插入排序位置应该是等效的。)
您可能还会考虑在程序中的什么位置需要将节点插入您的列表中。如果您的列表使用动态内存,则有必要在需要时沿线的某个时间插入一个节点。
这取决于底层列表实现的结构。如果这是一个数组并且 insertNode 追加它,它会被复制到内存中的一个新位置,那里有更多 space 可用,以便新元素也适合。新元素也会得到 memcpyd。这很快,因为它发生在内核中而不是在您的用户 space 程序中。
另一方面,如果你有类似链表的东西,其中存储了指向数组形式的其他列表的指针。你甚至不必复制列表中的所有元素,而只需插入一个指向链表的新指针,该指针指向包含新元素的数组。那将是非常快的。
据我所知,最佳答案是:视情况而定。
这取决于调用代码的样子,但一般来说,如果可以内联,一个简短的函数调用将具有零开销。创建一个临时数组只是为了避免函数调用开销几乎肯定是一个错误 - 但在某些情况下 "batching" 这样的东西可以避免大量的每次插入开销并且可能有意义。
同时尝试和基准测试。
函数调用比迭代要花费一些时间。如果您通过调用 insertNode 函数插入一个节点,则需要进行一些迭代(取决于您要插入的位置)将该节点插入列表中,但是如果您要插入大量节点,则需要每次都调用该函数。这可能很费时间。
如果通过将某个节点放入数组中来插入节点,最后调用insertNode将数组的节点复制到列表中。这次 insertNode 的调用时间会减少,但交互次数会增加。这不会花费时间,就像函数调用一样。
这里有一点需要注意,您需要为数组提供额外的内存。
您的代码有问题:
您对函数定义使用了过时的语法。现代 C 代码不再支持隐式 return 类型,您应该将其指定为
void
.您使用了无关的括号,使代码看起来很别扭。
函数
insertNode
不检查myArray
指向的数组是否足够大。如果需要,您应该检查并重新分配数组。函数
alt_insertNode
不检查可用空间,也不更新firstAvailablePos
。
根据您的重新分配方案以及您的编译器允许优化的积极程度,批量插入值可能比一个一个地插入它们更有效,特别是如果您不分配中间数组malloc()
。对您的特定测试用例进行基准测试将告诉您哪个更有效。但是请注意,使代码尽可能简单具有重要价值。
这是一个更完整的实现,可用于 运行 测试:
typedef struct Data {
int *myArray; // the array where all integers are stored
size_t size; // the number of int that can be stored
size_t firstAvailablePos; // the first free position of myArray
} Data;
/* reallocating the array with a simple exponential growth */
int insertNode(Data *data, int newNum) {
if (data->firstAvailablePos == data->size) {
size_t newSize = (data->size < 32) ? 32 : data->size + data->size / 2;
int *array = realloc(myArray, newSize * sizeof(*array));
if (array == NULL)
return -1;
data->myArray = array;
data->size = newSize;
}
data->myArray[data->firstAvailablePos++] = newNum;
return 0;
}
int alt_insertNode(Data *data, int *array, size_t arraySize) {
if (data->firstAvailablePos + arraySize > data->size) {
size_t newSize = (data->size < 32) ? 32 : data->size + data->size / 2;
while (newSize < data->firstAvailablePos + arraySize) {
newSize += newSize / 2;
}
int *array = realloc(myArray, newSize * sizeof(*array));
if (array == NULL)
return -1;
data->myArray = array;
data->size = newSize;
}
memcpy(data->myArray + data->firstAvailablePos, array, arraySize * sizeof(*array));
data->firstAvailablePos += arraySize;
return 0;
}