我有一些遗留代码需要理解,我偶然发现,在代码内部,非常经常访问同一结构。如果我事先保存了该结构的内容,然后访问本地副本而不是通过指针进行访问,会有所不同吗?
我已经通过在线汇编程序比较了一些测试代码,以查看它是否可以优化代码。使用https://godbolt.org/ ARM64 gcc8.2
完成该操作变种A
typedef struct STRUCT_D{
int myInt1IND;
int myInt2IND;
int myInt3IND;
int myInt4IND;
int myInt5IND;
int myInt6IND;
int myInt7IND;
int myInt8IND;
int myInt9IND;
} STRUCT_D;
typedef struct STRUCT_C{
STRUCT_D myStructInDIntINC;
} STRUCT_C;
typedef struct STRUCT_B{
STRUCT_C * myPointerB;
} STRUCT_B;
typedef struct STRUCT_A{
STRUCT_B * myPointerA;
} STRUCT_A;
int square(void) {
struct STRUCT_C myStructC;
struct STRUCT_B myStructB;
struct STRUCT_A myStructA;
struct STRUCT_A* startPointer;
myStructC.myStructInDIntINC.myInt1IND = 55;
myStructB.myPointerB = &myStructC;
myStructA.myPointerA = &myStructB;
startPointer = &myStructA;
int myresult =
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt1IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt2IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt3IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt4IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt5IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt6IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt7IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt8IND +
startPointer->myPointerA->myPointerB->myStructInDIntINC.myInt9IND;
return myresult;
}
变种B
typedef struct STRUCT_D{
int myInt1IND;
int myInt2IND;
int myInt3IND;
int myInt4IND;
int myInt5IND;
int myInt6IND;
int myInt7IND;
int myInt8IND;
int myInt9IND;
} STRUCT_D;
typedef struct STRUCT_C{
STRUCT_D myStructInDIntINC;
} STRUCT_C;
typedef struct STRUCT_B{
STRUCT_C * myPointerB;
} STRUCT_B;
typedef struct STRUCT_A{
STRUCT_B * myPointerA;
} STRUCT_A;
int square(void) {
struct STRUCT_C myStructC;
struct STRUCT_B myStructB;
struct STRUCT_A myStructA;
struct STRUCT_A* startPointer;
myStructC.myStructInDIntINC.myInt1IND = 55;
myStructB.myPointerB = &myStructC;
myStructA.myPointerA = &myStructB;
startPointer = &myStructA;
struct STRUCT_D myResultStruct = startPointer->myPointerA->myPointerB->myStructInDIntINC;
int myresult =
myResultStruct.myInt1IND + myResultStruct.myInt2IND + myResultStruct.myInt3IND +
myResultStruct.myInt4IND + myResultStruct.myInt5IND + myResultStruct.myInt6IND +
myResultStruct.myInt7IND + myResultStruct.myInt8IND + myResultStruct.myInt9IND;
return myresult;
}
我知道STRUCT_D尚未完全初始化,但对于本示例而言,则不相关。我的问题是变量B是否“更好”。当然,它的可读性更好,但是保存指针的上下文是否有意义。正如我在文件中所说的,同一指针在同一函数中被取消引用大约150次。我知道我知道..这个功能肯定应该重构。 :D
答案 0 :(得分:2)
不会有真正的区别,因为任何优化的编译器(gcc,clang)都会将此优化为堆栈变量和/或寄存器。
答案 1 :(得分:0)
将数据复制到本地可能有助于编译器证明没有其他通过其他指针进行的访问来读取或写入数据。
因此,出于相同的原因,您将使用int *restrict p
。如果使用void func(struct foo *restrict ptr)
,则向编译器承诺对ptr->member
的任何访问都是 not ,它将更改通过任何其他指针或从全局范围读取的值变量。
基于类型的别名分析已经可以提供很大帮助;例如,通过float*
进行的访问不会影响任何int
对象。 (除非您的程序包含严格混叠的UB;否则某些编译器允许您定义该行为,例如gcc -fno-strict-aliasing
。)
如果您不执行赋值操作或不读取其他指针(编译器必须假定可能指向结构的成员),则不会有什么不同:别名分析将成功并让编译器保留寄存器中的struct成员跨其他对内存的访问,就像本地访问一样。
(对于当地人来说,别名分析通常很容易,特别是如果他们甚至从未取得过住址,那么什么也不会指向他们。)
顺便说一句,允许编译器优化非volatile
/非_Atomic
内存访问的原因是,在另一个线程同时写入一个非原子对象是未定义的行为读或写它。
这样可以安全地假设除非您自己编写变量,否则变量不会更改,并且您不需要将内存中的值与C抽象机“同步”,除非您进行非内联时函数调用。 (对于某些未知函数可能具有指针的任何对象。对于局部变量(如循环计数器)通常不是这种情况,因此可以将它们保存在调用保留的寄存器中,而不用溢出/重装。)
但是声明局部变量以保存全局变量或指向数据的副本存在潜在的弊端:如果编译器最终没有将局部变量保存在整个函数的寄存器中,则可能最终不得不实际复制数据进入堆栈存储器,以便可以从那里重新读取。 (如果无法证明原始对象未更改。)
通常,在这种微优化级别上,可读性通常不受影响,但是如果您好奇的话,可以看看针对您所关心的某些平台的优化asm。如果发生很多不必要的存储/重新加载,请尝试使用本地语言。