C - Матрицы как передать по значению?
Я разрабатываю функции обработки матрицы для C-проекта. Я рассматриваю либо передачу матриц по значению или по ссылке. Я создал бенчмарк, передающий матрицы по значению и по ссылке, и оба, кажется, выполняют то же самое с флагами оптимизации -O0 и -O2 в gcc. Учитывая, что мой тест может давать неправильные результаты, я хотел бы знать, каков наиболее эффективный способ передачи матриц в вызовы функций и из них с использованием только 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
4 ответа
У вас есть 2 конкурирующих интересов здесь:
передавая структуру по значению, это типизируется как класс хранения данных и помещается в стек в соответствии с соглашением о вызовах x86, это немного медленнее, чем вызов по ref, который застревает в регистре.
это почти точно сбалансировано кучей разыменований указателей...
отделить и профилировать каждую часть отдельно
если вы пытаетесь сделать этот вид кода быстрее, вы сможете писать более быстрые реализации в каком-то виде кода SIMD, AltiVec, SSE или OpenCL в зависимости от
32 значения с плавающей запятой не будут вписываться в регистры в любом случае. Компилятор будет вынужден перенести данные из памяти в стек, который является просто другой частью памяти. В зависимости от количества обращений к данным копирование данных может быть даже медленнее, чем разыменование указателей.
Я бы предложил использовать передачу по ссылке с модификатором const для любых нескалярных данных. Задача компилятора - оптимизировать ваш код для конкретных платформ.
Из Эффективного C++:
Предпочитайте передачу по ссылке на константу перед передачей по значению, обычно она более эффективна и позволяет избежать проблемы срезов. Правило не применяется к встроенным типам и итераторам STL и типам объектов функций. Для них обычно подходит передача по значению.
Я понимаю, что вы программируете на C вместо C++, но я думаю, что это правило все еще применяется. Причина, по которой ваш пример этих двух функций выполняется очень близко, может заключаться в том, что структура содержит только float и является недорогой для копирования, поскольку она передается по значению.
Однако, как сказал автор Effective C++,
некоторые компиляторы отказываются помещать объекты, состоящие только из двойников, в регистр, хотя они с радостью регулярно размещают там голые двойники. Когда такое происходит, вам лучше передать такие объекты по ссылке, потому что компиляторы, безусловно, будут помещать указатели в регистры ".Unsubscribe-lgm-thur
В вашем случае, возможно, машина не против поместить структуру в регистр, но трудно сказать, когда вы запускаете свою программу на других машинах. Поскольку их производительность действительно близка, я бы проголосовал за передачу по ссылке.
Технически, у нас есть только "передача по значению" в C. Вы должны передать матричные указатели (по значению) в функцию. Это уменьшит объем данных, "скопированных" в функцию, и, следовательно, станет более эффективным.