【问题标题】:C - Speed of Copying Data Structure Entirely vs PartsC - 完全复制数据结构与部分复制数据结构的速度
【发布时间】:2014-09-01 18:42:41
【问题描述】:

我正在研究 Kruse、Leung 和 Tondo 的“C 中的数据结构和程序设计”。第 2 章第 2 节介绍了一个简单的数据结构(列表),稍后要求读者为该结构编写两个不同的复制函数。有问题的练习是 E2,内容如下:

编写将一个列表复制到另一个列表的函数(作为文本中定义的结构类型)。使用以下方法: (a) 复制整个结构; (b) 使用循环仅复制条目。哪个版本更容易写?哪个版本通常会运行得更快,为什么?

我的问题出现在本练习的最后一部分:“哪个版本通常运行得更快,为什么?”。我知道复制整个结构更快,我的理解表明这是因为可以避免循环的开销。但是,当我运行每个条目时,我惊讶地发现复制整个结构不仅比复制每个条目要快,而且大约快 10 倍

我希望有人能够向我解释为什么会这样,或者至少将我引导到可以帮助我理解的来源。我试着通读了我写的两个函数的汇编,但是我对汇编的理解在这一点上是非常基础的。

谢谢!

相关代码:

#define MAXLIST 200 //maximum size of lists

extern void Error(const char *);

typedef struct coord_tag {
    int row;    //x
    int col;    //y
} Coord_type;

typedef Coord_type Entry_type;

typedef struct list_tag {
    int count;
    Entry_type entry[MAXLIST];
} List_type;

void Copy(List_type *lt, List_type *lf) //list "to" and list "from"
{
    if (lt == NULL || lf == NULL) {
        Error("list uninitialized");
    } else {
        *lt = *lf;
    }
}

void Copy2(List_type *lt, List_type *lf)
{
    if (lt == NULL || lf == NULL) {
        Error("list uninitialized");
    } else {
        int i;

        lt->count = lf->count;
        for (i = 0; i < lf->count; i++) {
            lt->entry[i] = lf->entry[i];
        }
    }
}

【问题讨论】:

  • 查看生成的程序集。
  • 如果你想帮助理解程序集,你应该发布它;)
  • 这是如何编译的?
  • 试试和memcpy比较一下,第一个副本相当于memcpy。有for循环的时候编译器可能不知道那些入口肯定在一个连续的内存位置,必须经过for循环,但是有汇编指令可以整块内存拷贝。
  • 两个建议:1. 使用不同级别的优化重新运行您的测试(在非 Windows 系统上,这通常通过 -O1-O2-O3-Os 选项实现)。 2. 要更深入地了解幕后发生的事情,请将Copy2() 放入它自己的源文件中,并使用编译器标志-Os -S 进行编译。这将为您提供一个文件扩展名为“.s”的文件,其中包含汇编代码。优化标志不是绝对必要的,但根据我的经验,它可以显着提高汇编代码的可读性。

标签: c optimization data-structures


【解决方案1】:

您会惊讶于直接记忆副本的速度有多快!在汇编中,有专门用于快速内存复制的指令。 (例如 REP MOVSB)让我们看看第二个副本在每次循环迭代中引入的所有新中断:

  • 我++
    • 缓存i的原始值
    • 在内存中增加 i
    • 最终返回i的原始值
  • lf->条目[i]
    • 检索 lf 的值
    • 检索 i 的值
    • 将 i 加上入口的偏移量添加到 lf
    • 在该地址检索值
  • lt->条目[i]
    • 检索 lt 的值
    • 检索 i 的值
    • 将 i 加上入口的偏移量添加到 lt
  • 计数
    • 检索 lf 的值
    • 检索 lf->count 的值
    • 检索 i 的值
    • 比较 i 和 lf->count

您可以想象为什么这会比不间断的内存复制慢 10 倍。

【讨论】:

  • 如果应用了任何优化,iltlf 的值应始终驻留在寄存器中。所以“检索......的值”的操作是一个noop。我不认为,OP 应用了优化,虽然......
  • 我认为这取决于您的编译器。现在使用 gcc,您应该在函数的参数上设置 __restrict,以便编译器知道它不必每次都检查参数的值。 stackoverflow.com/questions/1965487/…
  • 我提到的三个变量都是函数中的局部变量。指针是按值传递的,所以它们永远不需要重新加载。
  • 不,你说得对,它可以将 i、lt 和 lf 保存在寄存器中。我只是说 lt 和 lf 上的 __restrict 关键字可以告诉优化器,它可以假设这些指针后面的数据在调用期间不会在幕后发生变化。例如,这允许优化器缓存 lf->count。
  • @ldgabbay 谢谢,这正是我想要的。我试图在机器级别分解我脑海中的步骤,但没有把它带到足够远的地方来实现 for 循环中涉及的所有操作。
猜你喜欢
  • 1970-01-01
  • 2019-10-14
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 2012-11-12
  • 1970-01-01
  • 2013-08-20
相关资源
最近更新 更多