提高my C list push_back函数的性能

提高my C list push_back函数的性能,c,performance,list,push-back,C,Performance,List,Push Back,我有我的C列表,我实现了push_back功能: bool_t push_back_clist(clist_ptr pList, void* item) { if(pList) { node_ptr pNode = new_node(item, pList->sizeof_item); if(!pNode) return FALSE; if(!pList->head) pList->hea

我有我的C列表,我实现了
push_back
功能:

bool_t push_back_clist(clist_ptr pList, void* item)
{
    if(pList)
    {
        node_ptr pNode = new_node(item, pList->sizeof_item);
        if(!pNode) return FALSE;

        if(!pList->head)
            pList->head = pList->tail = pNode;
        else
        {
            pList->tail->next = pNode;
            pNode->prev = pList->tail;
            pList->tail = pNode;
        }

        pList->size++;
        return TRUE;
    }

    return FALSE;
}

static node_ptr new_node(void* data, size_t sizeof_item)
{
    node_ptr pNode = (node_ptr) malloc(sizeof(node_t));
    if(!pNode) return NULL;

     pNode->data = malloc(sizeof_item);

     if(!pNode->data)
     {
         free(pNode);
         return NULL;
     }

     memcpy(pNode->data, data, sizeof_item);
     pNode->next = pNode->prev = NULL;
     return pNode;
}
它可以工作,但是当我将我的
push\u-back\u-clist
函数与
std::list.push\u-back
方法进行比较时,我注意到我的函数需要大约两倍的时间。为什么?如何提高我的功能性能?
谢谢。

您可以一次性分配数据和节点,以节省
malloc
呼叫的次数

char* mem = malloc(sizeof(node_t)+sizeof_item);
// Check alloc here...
node_ptr pNode = (node_ptr)mem;
pNode->data = mem+sizeof(node_t);

我认为您不应该像dasblinkenlight建议的那样使用单个分配,因为这是接口的隐式更改,而且很难记录:这样分配的项不能用于存储不同的数据和删除在其上找到的数据

对于可能的优化,我认为在您的版本中,控制流可能太复杂,并且会抑制某些优化。通过直接向分配功能提供
prev
next
字段,尝试只触摸一次新分配的项目。然后优化该分配功能的控制流程:

static node_ptr new_node(void* data, size_t sizeof_item, node_ptr prev, node_ptr next)
{
     void * cdata = malloc(sizeof_item);
     if(!cdata) return NULL;
     memcpy(cdata, data, sizeof_item);

     node_ptr pNode = malloc(sizeof *pNode);
     if(pNode)
       *pNode = (struct node){ .data = cdata, .prev = prev, .next = next, };
     return pNode;
}
只有当您有一个兼容C99的编译器时,它才能正常工作。如果没有,请重新考虑获得一个:)或将复合文字的使用更改为一系列赋值

一些吹毛求疵的地方:

  • C现在有自己的布尔类型(13年来),
    bool
    true
    false
    如果包含
    stdbool.h
    ,应该可以很好地工作
  • 不要使用
    malloc
  • typedef
    指针类型被C社区的许多人认为是糟糕的样式
编辑:通过使用不同的版本,我发现使用我的设置(x86_64、linux、gcc或clang)可以生成最好的汇编程序


正如人们在这里所说,您的列表速度慢了两倍,因为每次插入都要执行2个堆操作(分配)


从性能的角度来看,最好对节点+数据进行单一分配。此外,如果堆显著影响总体性能,则可以(并且应该)使用替代堆。这样做可以将性能提高100倍甚至更多,数量级。这并不是高估。

这可能会导致校准问题,具体取决于平台。在C++中,通过使列表成为模板类,只需两个成员,一个用于数据,一个用于列表指针,可以绕过这个问题。我不确定在C中是否有任何可移植的方法可以做到这一点。@Nemo如果您确保
sizeof(node_t)
是对齐大小的倍数,那么这应该会起作用。@Nemo在
node_t
中有两个指针,对齐问题是可能的,但不太可能:指针分配在机器字边界上,因此,使用两个指针时,数据也将分配到字边界。通过
char*
是无用的。直接分配给
pNode
,然后再分配
pNode->data=pNode+1
就可以了(假设
data
字段是
void*
),对于对齐问题,C11具有新的
max\u align\t
,因此您可以注意分配至少是
sizeof(max\u align\t)+sizeof_item
然后执行
pNode->data=(max_align_t*)pNode+1
没有冒犯,但是您展示了对OOP的极好的理论理解,以及与代码优化相关的实际问题的距离。堆分配(由标准的heapin多线程环境实现)比在那里完成的所有其他事情(如条件分支等)都要重得多,因此它们实际上都是无关紧要的。这就是为什么这个列表的实现几乎是标准的两倍慢的原因。@瓦尔多,我想你是指C++标准的,C没有一个:)如果你读的很好,我并不是说条件分支本身就是在花时间,“我说它会抑制优化。”JensGustedt谢谢你的回答,我必须使用C89。为什么C社区中的许多人认为指针类型的typedef是糟糕的样式?我认为这是因为这在某种程度上混淆了它的本质,并且对于
结构使用
typedef
和使用简单的
*
的好处是最小的。在名称中有明确指示的方式对大多数人来说可能是合适的。如何使用这个
替代堆
?我认为你完全高估了它可能带来的好处。Mondern
malloc
系统还不错。正如Galdkketa在这个问题中所注意到的,他观察到的两个因素与C++实现相比,不是百分之一的因素。2的因子正好等于在描述的场景中<代码> MalOC (或类似)操作的比率。因此,它强化了堆操作是瓶颈的假设。我说:以一个优化的堆为例,你会得到数百倍。@valdo你应该解释一下
optimized/alternative heap
的意思,因为没有示例或引用,它只是一个词。@gliderkite:我说的是高性能的分配器,比如tcmalloc、ptmalloc、囤积,不久前我自己也开发了一个(用于Windows,但经过一些努力可能会在任何平台上使用)。
node_ptr new_node1(void* data, size_t sizeof_item, node_ptr next, node_ptr prev)
{
  node_ptr ret = NULL;
  void * nData = malloc(sizeof_item);
  if (!nData) return ret;
  struct node const nNode = {
    /* memcpy is unavoidable since nothing is known about the type of
       the data. But we can save a register by using the return value
       of memcpy. */
    memcpy(nData, data, sizeof_item),
    next,
    prev
  };
  /* Allocate the return value last so it may stay in the same return
     register */
  ret = malloc(sizeof *ret);
  if (!ret) return ret;
  /* Assignment is better than memcpy since this can just use "ret" as
     target address with offsets. */
  *ret = nNode;
  return ret;
}