我想弄清楚在C中访问指针需要多少个时钟周期或总指令。我不认为我知道如何弄清楚例如,p-> x = d-> a + f - &GT,b
我会假设每个指针有两个加载,只是猜测指针会有一个加载,以及值的加载。所以在这个操作中,指针分辨率会比实际添加要大得多,只要试图加速这个代码,对吗?
这可能取决于所实现的编译器和架构,但我是否在正确的轨道上?
我已经看到了一些代码,其中每个值,例如,3个添加,来自
f2->sum = p1->p2->p3->x + p1->p2->p3->a + p1->p2->p3->m
结构类型,我试图定义它有多糟糕
答案 0 :(得分:8)
这取决于手头的架构。
某些体系结构可以为指令引用/取消引用内存,而无需先将其加载到寄存器中,而其他体系结构则不会。某些体系结构没有计算偏移量的指令概念,并且会使您加载内存地址,向其添加偏移量,然后允许您取消引用内存位置。我确信芯片到芯片的差异更大。
一旦超过这些,每条指令也需要不同的时间,具体取决于架构。说实话,这是一个非常非常小的开销。
对于您解除引用一系列项目的直接问题,这种情况会很慢,因为您可能会在解除引用链中的位置越来越差。这意味着更多的缓存未命中,这意味着更多的命中主内存(或磁盘!)来获取数据。与CPU相比,主内存非常慢。
答案 1 :(得分:2)
VisualStudio等一些IDE允许您查看与源代码一起生成的程序集。
How to view the assembly behind the code using 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.
答案 2 :(得分:1)
取决于你在做什么,一个简单的指针取消引用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。
虽然,如果你从完全随机的位置加载内存,你将导致很多页面错误,导致数百的时钟周期被浪费。
答案 3 :(得分:1)
在可能的情况下,编译器会通过在寄存器中保留重复使用的基址(例如,在您的示例中为p1->p2->p3
)来消除此开销。
但是,有时编译器无法确定哪些指针可能 alias 在函数中使用的其他指针 - 这意味着它必须回退到非常保守的位置,并经常从指针重新加载值
这是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
寄存器中,并使用它三次从该结构中获取三个值。