6

我试图弄清楚访问 C 中的指针需要多少个时钟周期或总指令。我想我不知道如何计算出例如 p->x = d->a + f->b

我会假设每个指针有两个负载,只是猜测指针会有一个负载,而值会有一个负载。所以在这个操作中,指针解析将是一个比实际加法更大的因素,就试图加速这段代码而言,对吧?

这可能取决于实现的编译器和架构,但我是否走在正确的轨道上?

我看过一些代码,其中使用的每个值,比如 3 个加法,都来自一个

 f2->sum = p1->p2->p3->x + p1->p2->p3->a + p1->p2->p3->m

结构类型,我试图定义这是多么糟糕

4

4 回答 4

8

这取决于手头的架构。

一些架构可以在不首先将指令加载到寄存器的情况下为指令引用/取消引用内存,而其他架构则不能。一些体系结构没有计算偏移量的指令概念以供您取消引用,并且会让您加载内存地址,将偏移量添加到它,然后允许您取消引用内存位置。我敢肯定芯片到芯片会有更多的差异。

一旦你通过了这些,每条指令也会根据架构花费不同的时间。不过老实说,这是一个非常非常小的开销。

对于取消引用一系列项目的直接问题,速度缓慢的原因是,您在取消引用链中走得越远,引用的局部性可能就越差。这意味着更多的缓存未命中,这意味着对主内存(或磁盘!)的更多命中以获取数据。与 CPU 相比,主内存非常慢。

于 2010-04-30T20:42:19.310 回答
2

取决于你在做什么,一个简单的指针取消引用y = *z;在哪里

int x = 1;
int* z = &x;
int y;

可能会在 x86 上组装成这样的东西:

mov eax, [z]
mov eax, [eax]
mov [y], eax

并且y = x仍然需要内存取消引用:

mov eax, [x]
mov [y], eax

将指令移动到内存大约需要 2-4 个周期 IIRC。

但是,如果您从完全随机的位置加载内存,则会导致大量页面错误,从而浪费数百个时钟周期。

于 2010-04-30T20:43:47.023 回答
2

VisualStudio 等一些 IDE 允许您查看与源代码一起生成的程序集。

如何使用 Visual C++ 查看代码背后的程序集?

然后,您可以查看您的确切架构和实现它的样子。

如果您使用的是 GDB(linux、mac),请使用disassemble

(gdb) disas 0x32c4 0x32e4
Dump of assembler code from 0x32c4 to 0x32e4:
0x32c4 <main+204>:      addil 0,dp
0x32c8 <main+208>:      ldw 0x22c(sr0,r1),r26
0x32cc <main+212>:      ldil 0x3000,r31
0x32d0 <main+216>:      ble 0x3f8(sr4,r31)
0x32d4 <main+220>:      ldo 0(r31),rp
0x32d8 <main+224>:      addil -0x800,dp
0x32dc <main+228>:      ldo 0x588(r1),r26
0x32e0 <main+232>:      ldil 0x3000,r31
End of assembler dump.
于 2010-04-30T20:45:54.913 回答
1

p1->p2->p3在可能的情况下,编译器将通过将重复使用的基本位置保存在寄存器中(例如在您的示例中)来为您消除开销。

但是,有时编译器无法确定哪些指针可能会为您的函数中使用的其他指针起别名——这意味着它必须回退到一个非常保守的位置,并经常从指针重新加载值。

这就是 C99 的restrict关键字可以提供帮助的地方。当某些指针永远不会被函数范围内的其他指针所别名时,它可以让您通知编译器,从而可以改进优化。


例如,使用这个函数:

struct xyz {
    int val1;
    int val2;
    int val3;
};

struct abc {
    struct xyz *p2;
};

int foo(struct abc *p1)
{
    int sum;

    sum = p1->p2->val1 + p1->p2->val2 + p1->p2->val3;

    return sum;
}

在具有优化级别的 gcc 4.3.2 下-O1,它编译为以下 x86 代码:

foo:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    movl    (%eax), %edx
    movl    4(%edx), %eax
    addl    (%edx), %eax
    addl    8(%edx), %eax
    popl    %ebp
    ret

正如你所看到的,它只引用p1一次——它将值保存p1->p2%edx寄存器中并使用它三次从该结构中获取三个值。

于 2010-05-01T05:05:05.343 回答