【问题标题】:will a call to malloc() in a loop in C affects performance?在 C 的循环中调用 malloc() 会影响性能吗?
【发布时间】:2020-07-15 10:14:14
【问题描述】:

我有一个要求,我必须通过处理给定的动态数组来创建一个新数组。我不知道我的新数组大小是多少。但最大大小是旧数组的 2 倍。

背景: 在函数内部,它应该遍历数组中的每个元素,并根据数量在新数组中添加/删除一个或两个元素。

实施: 我是如何实现上述要求的,我创建了一个链表并根据检查条件添加一两个新节点。

伪代码:

node_t *createNode() {
  node_t *temp;
  temp = malloc(sizeof(node_t));
  if (temp == NULL) {
      fputs("Error: Failed to allocate memory for temp.\n", stderr);
    exit(1);
  }
  temp->next = NULL;
  return temp;
}

/*
 * Function: addNode
 * -----------------
 *   add node to a existing linked list at end
 *
 *   head: original linked list to add additional node at end
 *   newVal: value of the additional node
 *
 *   return: new linked list that have an additional node
 */
node_t *addNode(node_t *head, double newVal) {
  node_t *temp;
  node_t *p;
  // create additional node
  temp = createNode();
  temp->val = newVal;

  if (head == NULL) {
    head = temp;
  } else {
    p = head;
    while (p->next != NULL) {
      p = p->next;
    }
    p->next = temp;
  }
  return head;
}

/*
 * Function: lastNodeDeletion
 * --------------------------
 *   delete last node of the linked list
 *
 *   head: linked list
 */
void lastNodeDeletion(node_t *head) {
  node_t *toDelLast;
  node_t *preNode;
    if (head == NULL) {
        printf("There is no element in the list.");
    } else {
      toDelLast = head;
        preNode = head;
        /* Traverse to the last node of the list */
        while (toDelLast->next != NULL) {
            preNode = toDelLast;
            toDelLast = toDelLast->next;
        }
        if (toDelLast == head) {
          /* If there is only one item in the list, remove it */
            head = NULL;
        } else {
            /* Disconnects the link of second last node with last node */
            preNode->next = NULL;
        }
        /* Delete the last node */
        free(toDelLast);
    }
}

/*
 * Function: calculateLower
 * ------------------------
 *   find the data set of lower tube curve
 *
 *   reference: reference data curve
 *   tubeSize: data array specifying tube size that includes:
 *             tubeSize[0], x -- half width of rectangle
 *             tubeSize[1], y -- half height of rectangle
 *             tubeSize[2], baseX -- base of relative value is x direction
 *             tubeSize[3], baseY -- base of relative value is y direction
 *             tubeSize[4], ratio -- ratio y / x
 *
 *   return : data set defining lower curve of the tube
 */
struct data calculateLower(struct data reference, double *tubeSize) {
  int i;
  struct data ref_norm;
/*
struct data contains two pointers double *x, double *y, int n;
*/
  struct data lower;
  node_t *lx = NULL;
  node_t *ly = NULL;

  // ===== 1. add corner points of the rectangle =====
  double m0, m1; // slopes before and after point i of reference curve
  double s0, s1; // sign of slopes of reference curve: 1 - increasing, 0 - constant, -1 - decreasing
  double mx, my;
  int b;
  double xLen;
  double yLen;

  // Normalize data.
  mx = fabs(mean(reference.x, reference.n));
  my = fabs(mean(reference.y, reference.n));
  ref_norm = normalizeData(reference, mx, my);
  if equ(mx, 0.0) {
    xLen = tubeSize[0];
  } else {
    xLen = tubeSize[0] / mx;
  }
  if equ(my, 0.0) {
    yLen = tubeSize[1];
  } else {
    yLen = tubeSize[1] / my;
  }
  // ----- 1.1 Start: rectangle with center (x,y) = (reference.x[0], reference.y[0]) -----
  // ignore identical point at the beginning
  b = 0;
  while ((b+1 < ref_norm.n) && equ(ref_norm.x[b], ref_norm.x[b+1]) && (equ(ref_norm.y[b], ref_norm.y[b+1])))
    b = b+1;

  // add down left point
  lx = addNode(lx, (ref_norm.x[b] - xLen));
  ly = addNode(ly, (ref_norm.y[b] - yLen));

  if (b+1 < ref_norm.n) {
      // slopes of reference curve (initialization)
      s0 = sign(ref_norm.y[b+1] - ref_norm.y[b]);
      if (!equ(ref_norm.x[b+1], ref_norm.x[b])) {
          m0 = (ref_norm.y[b+1] - ref_norm.y[b]) / (ref_norm.x[b+1] - ref_norm.x[b]);
      } else {
          m0 = (s0 > 0) ? 1e+15 : -1e+15;
      }
      if equ(s0, 1) {
          // add down right point
          lx = addNode(lx, (ref_norm.x[b] + xLen));
          ly = addNode(ly, (ref_norm.y[b] - yLen));
      }

      // ----- 1.2 Iteration: rectangle with center (x,y) = (reference.x[i], reference.y[i]) -----
      for (i = b+1; i < ref_norm.n-1; i++) {
          // ignore identical points
          if (equ(ref_norm.x[i], ref_norm.x[i+1]) && equ(ref_norm.y[i], ref_norm.y[i+1]))
              continue;

          // slopes of reference curve
          s1 = sign(ref_norm.y[i+1] - ref_norm.y[i]);
          if (!equ(ref_norm.x[i+1], ref_norm.x[i])) {
              m1 = (ref_norm.y[i+1] - ref_norm.y[i]) / (ref_norm.x[i+1] - ref_norm.x[i]);
          } else {
              m1 = (s1 > 0) ? (1e+15) : (-1e+15);
          }

          // add no point for equal slopes of reference curve
          if (!equ(m0, m1)) {
              if (!equ(s0, -1) && !equ(s1, -1)) {
                  // add down right point
                  lx = addNode(lx, (ref_norm.x[i] + xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
              } else if (!equ(s0, 1) && !equ(s1, 1)) {
                  // add down left point
                  lx = addNode(lx, (ref_norm.x[i] - xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
              } else if (equ(s0, -1) && equ(s1, 1)) {
                  // add down left point
                  lx = addNode(lx, (ref_norm.x[i] - xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
                  // add down right point
                  lx = addNode(lx, (ref_norm.x[i] + xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
              } else if (equ(s0, 1) && equ(s1, -1)) {
                  // add down right point
                  lx = addNode(lx, (ref_norm.x[i] + xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
                  // add down left point
                  lx = addNode(lx, (ref_norm.x[i] - xLen));
                  ly = addNode(ly, (ref_norm.y[i] - yLen));
              }

              int len = listLen(ly);
              double lastY = getNth(ly, len-1);
              // remove the last added points in case of zero slope of tube curve
              if equ((ref_norm.y[i+1] - yLen), lastY) {
                  if (equ(s0 * s1, -1) && equ(getNth(ly, len-3), lastY)) {
                      // remove two points, if two points were added at last
                      // ((len-1) - 2 >= 0, because start point + two added points)
                      lastNodeDeletion(lx);
                      lastNodeDeletion(ly);
                      lastNodeDeletion(lx);
                      lastNodeDeletion(ly);
                  } else if (!equ(s0 * s1, -1) && equ(getNth(ly, len-2), lastY)) {
                      // remove one point, if one point was added at last
                      // ((len-1) - 1 >= 0, because start point + one added point)
                      lastNodeDeletion(lx);
                      lastNodeDeletion(ly);
                  }
              }
          }
          s0 = s1;
          m0 = m1;
      }
      // ----- 1.3. End: Rectangle with center (x,y) = (reference.x[reference.n - 1], reference.y[reference.n - 1]) -----
      if equ(s0, -1) {
          // add down left point
          lx = addNode(lx, (ref_norm.x[ref_norm.n-1] - xLen));
          ly = addNode(ly, (ref_norm.y[ref_norm.n-1] - yLen));
      }
  }
  // add down right point
  lx = addNode(lx, (ref_norm.x[ref_norm.n-1] + xLen));
  ly = addNode(ly, (ref_norm.y[ref_norm.n-1] - yLen));

  // ===== 2. Remove points and add intersection points in case of backward order =====
  int lisLen = listLen(ly);
  double *tempLX = malloc(lisLen * sizeof(double));
  if (tempLX == NULL) {
      fputs("Error: Failed to allocate memory for tempLX.\n", stderr);
      exit(1);
  }
  double *tempLY = malloc(lisLen * sizeof(double));
  if (tempLY == NULL) {
      fputs("Error: Failed to allocate memory for tempLY.\n", stderr);
      exit(1);
  }

  tempLX = getListValues(lx);
  tempLY = getListValues(ly);

  lower = removeLoop(tempLX, tempLY, lisLen, -1);

  return denormalizeData(lower, mx, my);
}

它适用于小例子。但是当一个包含 50 万个值的数组作为输入传递给这个函数时,性能会急剧下降。

当指针属于 50 万个数据点的数组时,性能会下降。

对于上述问题是否有任何替代解决方案,或者是否需要进行任何其他修改以提高性能?

谢谢

【问题讨论】:

  • 伪代码没有说明问题,它甚至没有调用该代码中的 malloc。
  • 即使是这样,您会使用什么替代的动态内存分配方式?
  • 你说的是“函数”。哪个是“the”功能?您的问题非常缺乏重点和清晰度。
  • calculateLower() 是函数
  • @rak 功能相当广泛。你不能试着用一个例子来简化你的问题吗?我认为没有必要展示你所拥有的程序的一半。无论如何我无法理解你的问题。我想其他人也有同样的问题。只需创建一个可重现的最小示例并表达您的担忧。

标签: c linked-list malloc dynamic-arrays


【解决方案1】:

我认为您的链表代码没有什么特别低效的地方,但是有几个原因导致链表方法在性能方面可能并不理想。其中最主要的是

  1. 在每次调用的基础上,动态内存分配相当昂贵。使用链表,您的代码执行的分配数量与问题大小成线性关系。

  2. 链表涉及维护元数据,这会占用更多内存,从而减少访问数据的局部性。

  3. 按节点(或按节点组)分配可能会固有地减少数据局部性,因为不同分配的块不一定彼此相邻。

如果您对所需的内存量有一个好的、不太离谱的上限——就像你做的那样——那么我强烈考虑将你需要的最大值分配为单个块(只需一个 malloc() 调用而不是多个调用),将其用作数组,并且可以选择在知道您真正需要多少后使用 realloc() 缩小分配。

节省成百上千的malloc() 电话是肯定的胜利,但不清楚有多少。同样,直接访问数据并改善访问的局部性是肯定的胜利(同样不确定的幅度)。此外,即使您最后不缩小分配,您也可能会发现这样使用的内存更少。

但是,与任何性能问题一样,您应该测试使用分析器来确定程序的哪些部分花费的时间最多。编译器非常擅长生成快速代码,而人类不擅长通过代码检查来识别瓶颈。不要冒险花费数小时或数天来改进对性能而言并不重要的领域。首先找出您需要改进的部分,然后再进行改进。

更新:

再看一遍,我确实发现您的链表代码非常效率低下:每次添加节点时,您都​​会遍历整个列表以添加最后。这使得构建列表规模为 O(n2)。尽管我的 cmets 关于链表的使用,但您可以通过任一方式消除所有这些

  1. 在头部而不是尾部添加新元素,或者
  2. 维护一个指向每个列表尾部的指针,这样就可以在不遍历列表的情况下添加到末尾。

类似的适用于函数lastNodeDeletion()。随着列表变长,遍历整个列表以查找最后一个节点会变得很昂贵。我不清楚你的程序最终这样做了多少次,但如果结果证明这会减慢你的速度(请参阅以前的 cmets 关于分析),那么你可以考虑不仅跟踪列表尾部,还可以考虑加倍- 链表,这样就可以不遍历链表从末尾删除。 (但请注意,从数组的 and 中“删除”只是意味着更新您正在使用的元素数量,这需要恒定的时间来删除任何长度的尾部。)或者,如果通过在列表的头部添加您还可以将您的要求更改为从头部删除,那么这也将消除遍历整个列表来执行删除的需要。

【讨论】:

    【解决方案2】:

    对于小例子来说效果很好。但是当一个包含 50 万个值的数组作为输入传递给这个函数时,性能会急剧下降。

    这是有道理的,memory allocation is an expensive process,它肯定会影响程序的性能,当你更频繁地使用它时,这一点会变得很明显,即在一个广泛的循环中。

    对于上述问题是否有任何替代解决方案,或者是否需要进行任何其他修改以提高性能?

    如果你想使用堆内存,没有更快的方法,你必须使用内存分配,我看不出你会如何在仍然使用内存分配的同时显着提高性能,尽管你可以通过分配更大的内存来改善它一次块,从而避免在每次迭代中分配,或者如果您知道所需的总大小,甚至可以一次性分配所有需要的内存。

    使用堆栈内存肯定更快,并且是一个不错的选择,但在您的情况下,由于您正在处理大量数据,这可能是不可能的,the stack size is very limited

    这就是说,断言你的程序效率低下的最好方法是test it yourself,当然内存管理不是你唯一可以优化的东西。您还可以使用允许您分析程序执行时间的工具,例如,在 Linux 系统中,Callgrind

    【讨论】:

    • 我看不出你会如何在仍然使用内存分配的同时显着提高性能。 不是我的 DV,而是在池中分配节点是提高性能的明显候选者,如果个人分配调用被证明是一个瓶颈。
    • @chqrlie DV 来自 P__J__,尽管他有不发表任何评论的坏习惯。我同意一次性分配更大的块甚至整个块会在一定程度上提高性能,但不是特别好。
    • @anastaciu:查看 John Bollinger 的最新更新:重复添加到链表的末尾并从链表的末尾删除似乎是对性能不佳的更好解释。
    【解决方案3】:

    扩展@John 的回答:当您查看mallocs(又名addNode())时

    calculateLower() {
        addNode();
        addNode();
    
        for (b+1 ... ref_norm.n-1) {
            addNode();
            addNode();
        }
    
        addNode();
        addNode();
    }
    

    有两个独立的列表,每个列表在开始和结束时最多有两个分配,每次迭代最多有两个分配。这使得每个列表最多分配2 + (ref_norm.n - 1 - (b + 1)) + 2

    从链表切换到数组(加上指向当前末尾的索引/指针)有两个优点

    • 它减少了分配的数量
    • 它将addNode()lastNodeDeletion()的复杂度从O(n)降低到O(1)

    【讨论】:

      猜你喜欢
      • 2017-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-31
      • 2011-10-11
      • 1970-01-01
      相关资源
      最近更新 更多