C പോലുള്ള ഒരു താഴ്ന്ന നില ഭാഷ ഉപയോഗിച്ച് ലഭിക്കുന്ന ധാരണയുടെ പരസ്പരമാറ്റം സവിശേഷതകളുടെ അഭാവമാണ്. പ്രദർശന ആവശ്യത്തിനായി കോഡ് എഴുതുമ്പോൾ, ഇത് യഥാർത്ഥത്തിൽ ഒരു പ്രശ്നമല്ല. എന്നാൽ നിങ്ങൾക്ക് ഒരു യഥാർഥ അപ്ലിക്കേഷൻ എഴുതേണ്ടിവരുമ്പോൾ, അത് ഒരു പ്രശ്നമാണ്.
C-യിൽ ഇല്ലാത്ത ശ്രദ്ധേയമായ സവിശേഷതകളിൽ ഒന്ന് കംപൈൽ-ടൈം ജനറിക്സ് ആണ്.
കംപൈൽ ടൈം ജനറിക്സ് എന്നത് താഴെ പറയുന്ന നിയന്ത്രണങ്ങളെ ആശ്രയിച്ചിരിക്കുന്ന ഒരു തരം കോഡ് ആവർത്തനമാണ്:
- ഒരേ ട്രെയ്റ്റ് നടപ്പിലാക്കുന്ന ഒന്നിലധികം ക്ലാസുകൾ നിലവിലുണ്ട്
- നടപ്പിലാക്കിയ ട്രെയ്റ്റ് വഴി മാത്രം ആ ക്ലാസുകളെ ആശ്രയിക്കുന്ന കോഡ് നിലവിലുണ്ട്
ഈ ലേഖനത്തിൽ, C-യിൽ ജനറിക്സ് പുനർനിർമ്മിക്കാനുള്ള ഒരു മാർഗം, പ്രത്യേകിച്ച് എല്ലാ എലമെന്റ്-വൈസ് മാട്രിക്സ് പ്രവർത്തനങ്ങളും ആശ്രയിക്കുന്ന ഒരു ഓപ്പറേഷൻ ട്രെയ്റ്റുമായി ബന്ധപ്പെട്ട്, ഞാൻ നിങ്ങളെ കാണിക്കാൻ ആഗ്രഹിക്കുന്നു. C-യിലെ ഏക അന്തർനിർമ്മിത കോഡ് ആവർത്തന രീതിയായ മാക്രോകൾ ഉപയോഗിച്ചാണ് ഞങ്ങൾ ഇത് ചെയ്യാൻ പോകുന്നത്.
സി മാക്രോകൾ എന്താണ്?
സി മാക്രോകൾ #define ഡയറക്ടീവ് ഉപയോഗിച്ചാണ് സൃഷ്ടിക്കുന്നത്. ഏറ്റവും ലളിതമായി അവ ഇങ്ങനെ ഉപയോഗിക്കാം:
#define AN_IMPORTANT_NUMBER 42
ഇത് AN_IMPORTANT_NUMBER എന്നതിന്റെ എല്ലാ സംഭവങ്ങളും 42 എന്ന അക്ഷരസാങ്കേതിക മൂല്യം കൊണ്ട് മാറ്റിസ്ഥാപിക്കുന്നു. നിങ്ങൾ ചിന്തിക്കാം: ഈ ഗ്ലോബൽ നിർവ്വചനത്തിൽ നിന്ന് ഇത് എങ്ങനെ വ്യത്യസ്തമാണ്?
const int AN_IMPORTANT_NUMBER = 42;
int main() { ... }
പ്രായോഗികമായി, വളരെയധികം വ്യത്യാസമില്ല. എന്നാൽ അത് വളരെ ലളിതമായതുകൊണ്ടാണ്. നമ്മൾ ഒരു മാക്രോ ഫംഗ്ഷൻ എഴുതിയാൽ എന്താകും?
#define SQUARED_MACRO(x) x*x
int square_function(int x) { return x*x; }
ഇവ രണ്ടിനും ഇടയിൽ എന്താണ് വ്യത്യാസം? മാക്രോകൾ കുറച്ചുകൂടി സങ്കീർണ്ണമായ ഫൈൻഡ്-ആൻഡ്-റീപ്ലേസ് ആയി ചിന്തിക്കുമ്പോൾ ഇത് വ്യക്തമാകും. ഇത് സി പ്രീപ്രോസസർ കണക്കാക്കുകയും കംപൈൽ സമയത്ത് ഒരു കോഡ് ഭാഗം മറ്റൊരു കോഡ് ഭാഗം കൊണ്ട് മാറ്റിസ്ഥാപിക്കുകയും ചെയ്യുന്നു. ഉദാഹരണത്തിന്:
int x = SQUARED_MACRO(2);
എന്നത് ഇങ്ങനെയുള്ള കോഡായി വികസിപ്പിക്കപ്പെടുന്നു:
int x = 2*2;
ഫംഗ്ഷൻ ഒരു കോൾ ഇൻസ്ട്രക്ഷൻ ഒഴികെ മറ്റൊന്നിലേക്കും വികസിപ്പിക്കപ്പെടുന്നില്ല. മാക്രോയ്ക്ക് രണ്ട് ഗുണങ്ങളുണ്ട്. ഒന്നാമതായി, ഫംഗ്ഷൻ കോളിന്റെ ഓവർഹെഡ് ഇതിനില്ല. രണ്ടാമതായി, ഇത് ടൈപ്പ് ചെയ്തിട്ടില്ല, അതിനാൽ SQUARED_MACRO ഗുണനം പിന്തുണയ്ക്കുന്ന ഏത് തരത്തിലും ഉപയോഗിക്കാം.
ഈ ഗുണങ്ങൾ നൽകുന്ന മാക്രോകളുടെ സ്വഭാവം തന്നെ നിരവധി അപകടകരമായ കുഴികളും സൃഷ്ടിക്കുന്നു. ചില രസകരമായ കേസുകളിൽ SQUARED_MACRO എന്തായി വികസിക്കുന്നുവെന്ന് നോക്കാം.
SQUARED_MACRO(2 + 3)
2 + 3*2 + 3
നമുക്ക് 25 വേണമായിരുന്നു, പക്ഷേ 11 കിട്ടി. നല്ലതല്ല. എന്നിരുന്നാലും നമുക്ക് ഇത് എളുപ്പത്തിൽ പരിഹരിക്കാം.
#define SQUARED_MACRO (x)*(x)
മറ്റൊരു ഉദാഹരണം:
int x = 1, sum = 0;
while (x < 10) { sum += SQUARED_MACRO(x++); }
എന്നത് ഇങ്ങനെ വികസിക്കപ്പെടുന്നു:
int x = 1, sum = 0;
while (x < 10) { sum += (x++)*(x++); }
ഒരു തവണയല്ല, രണ്ട് തവണ x ഇൻക്രിമെന്റ് ചെയ്യപ്പെടുന്നതായി നാം കാണുന്നു! അതിനു മുകളിൽ, ഇത് x*x അല്ല, x*(x+1) കണക്കാക്കുന്നു.
അതിനാൽ ഈ ലളിതമായ മാക്രോ ചില ഇൻപുട്ടുകൾക്ക് പൂർണ്ണമായ അസംബന്ധം പുറപ്പെടുവിക്കുന്നുവെന്ന് നാം കാണുന്നു, അവ കംപൈലർ സാധുവായി കാണുന്നു. മാക്രോകൾ കോഡ് ഡ്യൂപ്ലിക്കേറ്റ് ചെയ്യുന്നു എന്ന വസ്തുത ഈ അപകടകരമായ പെരുമാറ്റങ്ങൾക്ക് കാരണമാകുമെങ്കിലും, അത് നമ്മെ കൂടുതൽ കാര്യക്ഷമമാക്കാനും അനുവദിക്കുന്നു.
ലൈബ്രറിയുടെ ലക്ഷ്യങ്ങൾ
ലൈബ്രറി മിനിമലിസ്റ്റ് (ഹെഡർ ഓൺലി) ആയിരിക്കണം, പക്ഷേ പൂർണ്ണമായും. രണ്ട് മാട്രിക്സുകൾ യും യും തമ്മിലുള്ള പലതരം പ്രവർത്തനങ്ങൾക്കായി ഞങ്ങൾ ഫംഗ്ഷനുകൾ എഴുതും.
ആവശ്യമായ ഫംഗ്ഷനുകൾ ഞാൻ താഴെ പറയുന്നവയാക്കി തരംതിരിച്ചു.
- അലോക്കേഷൻ തരം: ഫലം ഒരു ബഫറിൽ വയ്ക്കണോ , അതോ ആദ്യ ആർഗ്യുമെന്റിൽ എഴുതണോ ?
- ലൂപ്പ് തരം: ഞങ്ങൾ ഒരു ഡോട്ട് പ്രോഡക്റ്റ് തരം ലൂപ്പ് ചെയ്യുകയാണോ, അതോ എലമെന്റ്വൈസ് ഓപ്പറേഷൻ? രണ്ടാമത്തെ ആർഗ്യുമെന്റ് ട്രാൻസ്പോസ് ചെയ്യണോ ?
- എലമെന്റ്വൈസ് ഓപ്പറേഷൻ തരം: ഗുണിക്കുകയാണോ? കൂട്ടുകയാണോ? കുറയ്ക്കുകയാണോ?
ഡോട്ട് ഒഴികെയുള്ള എല്ലാ ഫംഗ്ഷനുകളും ഓപ്പറേഷനിൽ ജനറിക് ആണെന്ന് ശ്രദ്ധിക്കുക.
ബോയിലർപ്ലേറ്റ്
ആരംഭിക്കുന്നതിന് മുമ്പ്, നമ്മുടെ മാട്രിക്സ് തരം നിർവ്വചിക്കേണ്ടതുണ്ട്. ഞാൻ mfloat എന്നൊരു തരവും നിർവ്വചിക്കാൻ പോകുന്നു, അത് പിന്നീട് ഏതെങ്കിലും ഫ്ലോട്ട് തരത്തിന് പകരം വയ്ക്കാം.
#define DEBUG 1
typedef double mfloat;
typedef struct {
mfloat *buf;
int rows;
int cols;
} matrix;
ഘടകങ്ങൾ buf-ൽ വരി-പ്രധാന ക്രമത്തിൽ സംഭരിച്ചിരിക്കുന്നുവെന്ന് നമ്മൾ അനുമാനിക്കും. ഇപ്പോൾ, ലൈബ്രറിയുടെ ബാക്കി ഭാഗങ്ങളിൽ ഉപയോഗിക്കാൻ ചില അടിസ്ഥാന ഗെറ്റർ-സെറ്റർ ഫങ്ഷനുകൾ വേണം, അതിൽ അതിരുകൾ പരിശോധിക്കുന്നത് ഓപ്ഷണലാണ്.
static inline mfloat matrix_get(matrix m, int row, int col) {
if (DEBUG)
if (!(row >= 0 && col >= 0 && row < m.rows && col < m.cols)) {
fprintf(
stderr,
"matrix_get: സൂചിക അതിരുകൾ കവിഞ്ഞു (%d, %d) മാട്രിക്സ് "
"വലുപ്പത്തിന് (%d, %d)\n",
row, col, m.rows, m.cols);
exit(1);
}
return m.buf[row * m.cols + col];
}
static inline void matrix_set(matrix m, int row, int col,
mfloat val) {
if (DEBUG)
if (!(row >= 0 && col >= 0 && row < m.rows && col < m.cols)) {
fprintf(
stderr,
"matrix_set: സൂചിക അതിരുകൾ കവിഞ്ഞു (%d, %d) മാട്രിക്സ് "
"വലുപ്പത്തിന് (%d, %d)\n",
row, col, m.rows, m.cols);
exit(1);
}
m.buf[row * m.cols + col] = val;
}
മാട്രിക്സ് കൺസോളിൽ പ്രിന്റ് ചെയ്യാൻ ഒരു മാർഗ്ഗവും വേണം
static inline void matrix_print(matrix m) {
for (int i = 0; i < m.rows; i++) {
printf("[ ");
for (int j = 0; j < m.cols; j++) {
// ശാസ്ത്രീയ നൊട്ടേഷൻ, 4 ദശാംശ സ്ഥാനങ്ങളിലേക്ക് റൗണ്ട് ചെയ്തത്
printf("%.4e", matrix_get(m, i, j));
printf(" ");
}
printf("]\n");
}
printf("\n");
}
ഹീപ്പിൽ നിന്ന് മാട്രിക്സുകൾ അലോക്കേറ്റ് ചെയ്യാനും മോചിപ്പിക്കാനുമുള്ള ഒരു മാർഗ്ഗവും വേണം
matrix matrix_new(int rows, int cols) {
double *buf = calloc(rows * cols, sizeof(double));
if (buf == NULL) {
printf("matrix_new: calloc പരാജയപ്പെട്ടു.");
exit(1);
}
return (matrix){
.buf = buf,
.rows = rows,
.cols = cols,
};
}
ഡോട്ട് ഗുണനം
ഡോട്ട് ഗുണനത്തിൽ നിന്ന് തുടങ്ങാം. രണ്ട് മാട്രിക്സുകളും സമചതുരമാണെങ്കിൽ മാത്രമേ ആദ്യ ആർഗ്യുമെന്റിലേക്ക് ഔട്ട്പുട്ട് എഴുതാൻ കഴിയൂ, അത് നമുക്ക് അനുമാനിക്കാൻ കഴിയില്ലാത്തതിനാൽ ഞാൻ ഒരു ബഫർ-അലോക്കേറ്റ് പതിപ്പ് മാത്രമേ എഴുതുകയുള്ളൂ.
static inline void matrix_dot(matrix out, const matrix m1,
const matrix m2) {
if (DEBUG)
if (m1.cols != m2.rows) {
printf(
"matrix dot: dimension error (%d, %d) not compat w/ "
"(%d, %d)\n",
m1.rows, m1.cols, m2.rows, m2.cols);
exit(1);
}
for (int row = 0; row < m1.rows; row++) {
for (int col = 0; col < m2.cols; col++) {
double sum = 0.0;
for (int k = 0; k < m1.cols; k++) {
double x1 = matrix_get(m1, row, k);
double x2 = matrix_get(m2, k, col);
sum += x1 * x2;
}
matrix_set(out, row, col, sum);
}
}
}
## എലമെന്റ് വൈസ് ഓപ്പറേഷനുകൾ
ഓരോ എലമെന്റ് വൈസ് ഓപ്പറേഷനും ഇനിപ്പറയുന്നവ ചെയ്യുന്നു:
1. രണ്ട് മാട്രിക്സുകൾക്കും ഒരേ അളവുകൾ ഉണ്ടെന്ന് ഉറപ്പാക്കുക
2. ഓരോ അനുയോജ്യമായ എലമെന്റിലും ഓപ്പറേഷൻ പ്രവർത്തിപ്പിക്കുക
3. ഫലം ഔട്ട്പുട്ട് മാട്രിക്സിൽ ഇടുക
ഓരോ ഫംഗ്ഷനുകൾക്കും ലൂപ്പ് സമാനമായിരിക്കുമെന്ന് നമുക്ക് കാണാം, അതിനാൽ അതിനായി ഒരു മാക്രോ എഴുതാം
```c
#define MAT_ELEMENTWISE_LOOP \
for (int i = 0; i < m1.rows; i++) \
for (int j = 0; j < m1.cols; j++)
അതുപോലെ, അതിർത്തികൾ പരിശോധിക്കുന്നതിനുള്ള ഒരു ഫംഗ്ഷൻ, അതിർത്തികൾ പൊരുത്തപ്പെടുന്നില്ലെങ്കിൽ പാനിക് ചെയ്യുന്നു.
static inline void mat_bounds_check_elementwise(const matrix out,
const matrix m1,
const matrix m2) {
if (DEBUG)
if (m1.rows != m2.rows || m1.cols != m2.cols ||
out.rows != m1.rows || out.cols != m1.cols) {
fprintf(stderr,
"Incompatible dimensions for elementwise operation "
"(%d, %d) & (%d, %d) => (%d, %d) \n",
m1.rows, m1.cols, m2.rows, m2.cols, out.rows,
out.cols);
exit(1);
}
}
ഇപ്പോൾ, നമുക്ക് കൂട്ടുക, ഗുണിക്കുക, ഹരിക്കുക, കുറയ്ക്കുക എന്നിവ നടപ്പിലാക്കണം. യഥാർത്ഥ കണക്കുകൂട്ടലൊഴികെയുള്ള എല്ലാ കോഡും സമാനമായതിനാൽ, ഫംഗ്ഷൻ നിർവചിക്കുന്ന ഒരു മാക്രോയിലേക്ക് നമുക്ക് അത് സംഗ്രഹിക്കാം.
#define DEF_MAT_ELEMENTWISE_BUF(opname, op) \
static inline void matrix_##opname( \
matrix out, const matrix m1, const matrix m2) { \
mat_bounds_check_elementwise(out, m1, m2); \
MAT_ELEMENTWISE_LOOP { \
mfloat x = matrix_get(m1, i, j); \
mfloat y = matrix_get(m2, i, j); \
matrix_set(out, i, j, op); \
} \
}
##opnameഎന്നത്opnameന്റെ മൂല്യം ഫംഗ്ഷൻ നാമത്തിലേക്ക് ചേർക്കുന്നു.
മുന്നോട്ട് നോക്കുമ്പോൾ, എല്ലാ വ്യതിയാനങ്ങൾക്കും കൂട്ടുക, ഗുണിക്കുക, ഹരിക്കുക, കുറയ്ക്കുക എന്നീ ഫംഗ്ഷനുകൾ നിർവചിക്കേണ്ടതുണ്ടെന്ന് നമുക്കറിയാം, അതിനാൽ ഒരു നിശ്ചിത ഫംഗ്ഷൻ-നിർവചിക്കുന്ന-മാക്രോയ്ക്കായി അത് ചെയ്യുന്ന ഒരു മാക്രോ എഴുതാം
#define DEF_ALL_OPS(OP_MACRO) \
OP_MACRO(sub, (x - y)); \
OP_MACRO(add, (x + y)); \
OP_MACRO(div, (x / y)); \
OP_MACRO(mul, (x * y));
ഇപ്പോൾ നമുക്ക് യഥാർത്ഥത്തിൽ 4 ഫംഗ്ഷനുകൾ നിർവചിക്കാം!
DEF_ALL_OPS(DEF_MAT_ELEMENTWISE_BUF)
ബൂം! ഒരു വരിയിൽ, matrix_add, matrix_sub, matrix_div, matrix_mul എന്നീ ഫംഗ്ഷനുകൾ നമ്മൾ നിർവചിച്ചു.
ഇനി ഇൻ-പ്ലേസ് ഓപ്പറേഷനുകൾ നടപ്പിലാക്കാൻ ശ്രമിക്കാം.
static inline void mat_bounds_check_elementwise_ip(
matrix m1, const matrix m2) {
if (DEBUG)
if (m1.rows != m2.rows || m1.cols != m2.cols) {
fprintf(stderr,
"Incompatible dimensions for elementwise in-place "
"operation (%d, %d) & (%d, %d) \n",
m1.rows, m1.cols, m2.rows, m2.cols);
exit(1);
}
}
#define DEF_MAT_ELEMENTWISE_IP(opname, op) \
static inline void matrix_ip_##opname(matrix m1, \
const matrix m2) { \
mat_bounds_check_elementwise_ip(m1, m2); \
MAT_ELEMENTWISE_LOOP { \
mfloat x = matrix_get(m1, i, j); \
mfloat y = matrix_get(m2, i, j); \
matrix_set(m1, i, j, op); \
} \
}
ഈ പുതിയ മാക്രോ ഉപയോഗിച്ച്, നമുക്ക് ഇത് ചെയ്യാം
DEF_ALL_OPS(DEF_MAT_ELEMENTWISE_IP)
അങ്ങനെ matrix_ip_add, matrix_ip_mul മുതലായവ നിർവചിക്കപ്പെട്ടു. ട്രാൻസ്പോസ് ഓപ്പറേഷനുകൾക്കും ഇത് ചെയ്യാം, പക്ഷേ ഞാൻ അത് ഇവിടെ കാണിക്കുന്നില്ല.
യൂനറി പ്രവർത്തനങ്ങൾ
ചിലപ്പോൾ ഒരു മാട്രിക്സ് -ൽ ഒരു യൂനറി പ്രവർത്തനം നടത്താൻ നമുക്ക് ആവശ്യമായി വരും, ഉദാഹരണത്തിന് സ്കെയിലർ അല്ലെങ്കിൽ . പ്രവർത്തനത്തിന് മുകളിൽ ഒരു ജനറിക് യൂനറി ഫംഗ്ഷൻ നിർമ്മിക്കാം.
#define DEF_MAT_UNARY_IP(opname, op) \
static inline void matrix_ip_##opname(matrix m1) { \
MAT_ELEMENTWISE_LOOP { \
mfloat x = matrix_get(m1, i, j); \
matrix_set(m1, i, j, op); \
} \
}
DEF_MAT_UNARY_IP(square, (x * x))
DEF_MAT_UNARY_IP(negate, (-x))
DEF_MAT_UNARY_IP(sqrt, (sqrt(x)))
ഇപ്പോൾ matrix_ip_square(A), matrix_ip_negate(A), തുടങ്ങിയവ നിർവ്വചിക്കപ്പെട്ടിരിക്കുന്നു.
ഉപസംഹാരം
ഇങ്ങനെ, കുറഞ്ഞ പരിശ്രമത്തിൽ ഒരു മുഴുവൻ മെട്രിക്സ് ഓപ്പറേഷൻ ലൈബ്രറി നമ്മൾ “എഴുതി”. എന്നാൽ ഇത് ഒരു ചോദ്യം ഉയർത്തുന്നു: ഈ കോഡ് സുരക്ഷിതമാണോ?
നിങ്ങൾ നിർവ്വചിച്ച എല്ലാ മാക്രോകളും #undef ചെയ്താൽ, അതെ, ഇത് എല്ലാ ഫംഗ്ഷനുകളും സ്വയം എഴുതുന്നത് പോലെ തന്നെ സുരക്ഷിതമാണ്. മറുവശത്ത്, നിങ്ങളുടെ ലൈബ്രറിയുടെ പ്രവർത്തനശേഷിയുടെ ഭാഗമായി മാക്രോകൾ എക്സ്പോസ് ചെയ്താൽ, അത് സുരക്ഷിതമായിരിക്കണമെന്നില്ല. എന്നാൽ ചില കോഡ് സുരക്ഷിതമാണെന്നത് കൊണ്ട് അത് നിങ്ങൾ ചെയ്യേണ്ട ഒന്നാണെന്ന് അർത്ഥമാക്കുന്നില്ല. കോഡിൽ എന്തെങ്കിലും പ്രശ്നമുണ്ടെങ്കിൽ, അത് എന്താണ് വികസിപ്പിച്ചെടുത്തതെന്ന് കാണാൻ കഴിയാത്തതിനാൽ ഡീബഗ് ചെയ്യാൻ ബുദ്ധിമുട്ടുള്ളതാകാം. അതിനാൽ ഒരു പ്രൊഡക്ഷൻ പരിസ്ഥിതിയിൽ, മാക്രോകളുടെ ഉപയോഗം ശക്തമായി നിരുത്സാഹപ്പെടുത്തുന്നു. ഒരു വിദ്യാഭ്യാസ പരമ്പരയ്ക്ക് എളുപ്പവഴിയായിരുന്നതിനാൽ മാത്രമാണ് ഞാൻ ഇവിടെ ഇത് ഉപയോഗിച്ചത്.
എന്തെങ്കിലും ചോദ്യങ്ങളോ നിർദ്ദേശങ്ങളോ ഉണ്ടെങ്കിൽ, ഒരു കമന്റ് ഇടാനോ എനിക്ക് ഒരു ഇമെയിൽ അയക്കാനോ മടിക്കേണ്ടതില്ല. വായിച്ചതിന് നന്ദി.