[英]C - Matrices as pass by value?
我正在為C項目設計矩陣處理函數。 我正在考慮通過值或通過引用傳遞矩陣。 我創建了一個按值和按引用傳遞基准的基准,並且兩者在gcc中的優化標記-O0和-O2上似乎表現相同。 鑒於我的基准測試可能給出不正確的結果,我想知道什么是僅使用C將矩陣傳入和傳出函數調用的最有效方法。
#include <stdio.h>
#include <time.h>
// Compiled on OSX 10.6.8 using: cc -o matrix matrix.c -std=c99 -O2
typedef struct {
float m0;
float m1;
float m2;
float m3;
float m4;
float m5;
float m6;
float m7;
float m8;
float m9;
float m10;
float m11;
float m12;
float m13;
float m14;
float m15;
} Matrix;
// ================================================
// Pass By Value
// ------------------------------------------------
Matrix PassByValue (Matrix a, Matrix b) {
Matrix matrix;
matrix.m0 = a.m0 * b.m0 + a.m4 * b.m1 + a.m8 * b.m2 + a.m12 * b.m3;
matrix.m1 = a.m1 * b.m0 + a.m5 * b.m1 + a.m9 * b.m2 + a.m13 * b.m3;
matrix.m2 = a.m2 * b.m0 + a.m6 * b.m1 + a.m10 * b.m2 + a.m14 * b.m3;
matrix.m3 = a.m3 * b.m0 + a.m7 * b.m1 + a.m11 * b.m2 + a.m15 * b.m3;
matrix.m4 = a.m0 * b.m4 + a.m4 * b.m5 + a.m8 * b.m6 + a.m12 * b.m7;
matrix.m5 = a.m1 * b.m4 + a.m5 * b.m5 + a.m9 * b.m6 + a.m13 * b.m7;
matrix.m6 = a.m2 * b.m4 + a.m6 * b.m5 + a.m10 * b.m6 + a.m14 * b.m7;
matrix.m7 = a.m3 * b.m4 + a.m7 * b.m5 + a.m11 * b.m6 + a.m15 * b.m7;
matrix.m8 = a.m0 * b.m8 + a.m4 * b.m9 + a.m8 * b.m10 + a.m12 * b.m11;
matrix.m9 = a.m1 * b.m8 + a.m5 * b.m9 + a.m9 * b.m10 + a.m13 * b.m11;
matrix.m10 = a.m2 * b.m8 + a.m6 * b.m9 + a.m10 * b.m10 + a.m14 * b.m11;
matrix.m11 = a.m3 * b.m8 + a.m7 * b.m9 + a.m11 * b.m10 + a.m15 * b.m11;
matrix.m12 = a.m0 * b.m12 + a.m4 * b.m13 + a.m8 * b.m14 + a.m12 * b.m15;
matrix.m13 = a.m1 * b.m12 + a.m5 * b.m13 + a.m9 * b.m14 + a.m13 * b.m15;
matrix.m14 = a.m2 * b.m12 + a.m6 * b.m13 + a.m10 * b.m14 + a.m14 * b.m15;
matrix.m15 = a.m3 * b.m12 + a.m7 * b.m13 + a.m11 * b.m14 + a.m15 * b.m15;
return matrix;
}
// ================================================
// Pass By Reference
// ------------------------------------------------
void PassByReference (Matrix* matrix, Matrix* a, Matrix* b) {
if (!matrix) return;
if (!a) return;
if (!b) return;
matrix->m0 = a->m0 * b->m0 + a->m4 * b->m1 + a->m8 * b->m2 + a->m12 * b->m3;
matrix->m1 = a->m1 * b->m0 + a->m5 * b->m1 + a->m9 * b->m2 + a->m13 * b->m3;
matrix->m2 = a->m2 * b->m0 + a->m6 * b->m1 + a->m10 * b->m2 + a->m14 * b->m3;
matrix->m3 = a->m3 * b->m0 + a->m7 * b->m1 + a->m11 * b->m2 + a->m15 * b->m3;
matrix->m4 = a->m0 * b->m4 + a->m4 * b->m5 + a->m8 * b->m6 + a->m12 * b->m7;
matrix->m5 = a->m1 * b->m4 + a->m5 * b->m5 + a->m9 * b->m6 + a->m13 * b->m7;
matrix->m6 = a->m2 * b->m4 + a->m6 * b->m5 + a->m10 * b->m6 + a->m14 * b->m7;
matrix->m7 = a->m3 * b->m4 + a->m7 * b->m5 + a->m11 * b->m6 + a->m15 * b->m7;
matrix->m8 = a->m0 * b->m8 + a->m4 * b->m9 + a->m8 * b->m10 + a->m12 * b->m11;
matrix->m9 = a->m1 * b->m8 + a->m5 * b->m9 + a->m9 * b->m10 + a->m13 * b->m11;
matrix->m10 = a->m2 * b->m8 + a->m6 * b->m9 + a->m10 * b->m10 + a->m14 * b->m11;
matrix->m11 = a->m3 * b->m8 + a->m7 * b->m9 + a->m11 * b->m10 + a->m15 * b->m11;
matrix->m12 = a->m0 * b->m12 + a->m4 * b->m13 + a->m8 * b->m14 + a->m12 * b->m15;
matrix->m13 = a->m1 * b->m12 + a->m5 * b->m13 + a->m9 * b->m14 + a->m13 * b->m15;
matrix->m14 = a->m2 * b->m12 + a->m6 * b->m13 + a->m10 * b->m14 + a->m14 * b->m15;
matrix->m15 = a->m3 * b->m12 + a->m7 * b->m13 + a->m11 * b->m14 + a->m15 * b->m15;
}
// ================================================
// Benchmark
// ------------------------------------------------
#define LOOPS 100000
int main () {
Matrix result;
Matrix a;
Matrix b;
clock_t begin;
clock_t end;
int index;
// ------------------------------------------
// Pass By Reference
// ------------------------------------------
begin = clock();
for (index = 0; index < LOOPS; index++) {
PassByReference(&result,&a,&b);
a.m0 += index;
b.m0 += index;
}
end = clock();
printf("Pass By Ref: %f\n",(double)(end - begin) / CLOCKS_PER_SEC);
// ------------------------------------------
// Pass By Value
// ------------------------------------------
begin = clock();
for (index = 0; index < LOOPS; index++) {
result = PassByValue(a,b);
a.m0 += index;
b.m0 += index;
}
end = clock();
printf("Pass By Val: %f\n",(double)(end - begin) / CLOCKS_PER_SEC);
// The following line along with the above
// additions in the loops hopefully prevent
// the matrices from being optimized into
// nothing.
printf("%0.1f\n",result.m0);
return 0;
}
結果:
Pass By Ref: 0.489226
Pass By Val: 0.488882
從有效的C ++:
優先使用按引用傳遞給const而不是按值傳遞,它通常更有效,並且可以避免切片問題。 該規則不適用於內置類型以及STL迭代器和函數對象類型。 對於他們來說,按值傳遞通常是合適的。
我了解您使用C而不是C ++進行編程,但是我認為該規則仍然適用。 您的這兩個示例表現非常接近的原因可能是該結構僅包含浮點數,並且在按值傳遞時對其進行復制的開銷很小。
但是,就像有效C ++的作者所說的那樣
一些編譯器拒絕將僅由雙精度組成的對象放入寄存器,即使他們很樂意定期將裸雙精度對象放置在寄存器中。 當發生這種情況時,最好通過引用傳遞此類對象,因為編譯器肯定會將指針放入寄存器中。” Unsubscribe-lgm-thur
在您的情況下,也許機器不介意將結構放入寄存器中,但是很難確定何時在其他機器上運行程序。 由於它們的性能非常接近,因此我建議通過引用。
您在這里有2個競爭利益:
傳遞一個按值傳遞的struct,將其鍵入為數據存儲類並通過x86調用約定推入堆棧,這比被卡在寄存器中的by ref調用要慢一些。
這幾乎完全由一堆指針取消引用來平衡...
分開並分別剖析每個零件
如果您試圖使此類代碼更快,則可能能夠以某種SIMD代碼,AltiVec,SSE或OpenCL編寫更快的實現,具體取決於
無論如何,32個浮點值將不適合寄存器。 編譯器將被迫將數據從內存推入堆棧,這只是內存的另一部分。 根據數據訪問的數量,復制數據而不是取消引用指針的速度可能會更慢。
我建議對任何非標量數據使用帶有const修飾符的按引用傳遞。 編譯器的工作是針對特定平台優化代碼。
從技術上講,我們在C語言中只有“按值傳遞”。您應該將矩陣指針(按值)傳遞給函數。 它將減少“復制”到函數中的數據,從而提高效率。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.