c - 在 C 中超过 1024x1024 矩阵时,总线错误/核心转储
问题描述
在 N > 1024 上运行此代码时,我收到总线错误/核心转储错误。我正在使用远程 HPC 和 gcc/8.1。这是一个矩阵乘法 NxN。我不明白错误来自哪里。特别是为什么较小的 N 没有任何问题。我之前有其他代码运行到 2^20。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>
#define N 2048
float *A[N], *B[N];
int i, j, k, count = 0;
float** matrix_create(int n){
float** M = malloc(n * sizeof(float*));
for (i = 0; i < n; i++)
M[i] = (float*)malloc(n * sizeof(float));
return M;
}
float** add(float* M1[], float* M2[], int n){
float** M3 = matrix_create(n);
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
M3[i][j] = M1[i][j] + M2[i][j];
return M3;
}
float** sub(float* M1[], float* M2[], int n){
float** M3 = matrix_create(n);
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
M3[i][j] = M1[i][j] - M2[i][j];
return M3;
}
void print(float* M[], int n){
for (i = 0; i < n; i++){
for (j = 0; j < n; j++)
printf("%f\t", M[i][j] );
printf("\n");
}
}
float** strassen_multiply(float* A[], float* B[], int n){
if(n == 1 ){
float** C = matrix_create(n);
C[0][0] = A[0][0] * B[0][0];
return C;
}
count++;
float** C = matrix_create(n);
int k = n/2;
/** Creating sub matrecies**/
float** A11 = matrix_create(k);
float** A12 = matrix_create(k);
float** A21 = matrix_create(k);
float** A22 = matrix_create(k);
float** B11 = matrix_create(k);
float** B12 = matrix_create(k);
float** B21 = matrix_create(k);
float** B22 = matrix_create(k);
/**Dividing the Data Matrecies A & B**/
for(i = 0; i < k; i++)
for(j = 0; j < k; j++){
A11[i][j] = A[i][j];
A12[i][j] = A[i][k+j];
A21[i][j] = A[k+i][j];
A22[i][j] = A[k+i][k+j];
B11[i][j] = B[i][j];
B12[i][j] = B[i][k+j];
B21[i][j] = B[k+i][j];
B22[i][j] = B[k+i][k+j];
}
float** P1 = strassen_multiply(A11, sub(B12, B22, k), k);
float** P2 = strassen_multiply(add(A11, A12, k), B22, k);
float** P3 = strassen_multiply(add(A21, A22, k), B11, k);
float** P4 = strassen_multiply(A22, sub(B21, B11, k), k);
float** P5 = strassen_multiply(add(A11, A22, k), add(B11, B22, k), k);
float** P6 = strassen_multiply(sub(A12, A22, k), add(B21, B22, k), k);
float** P7 = strassen_multiply(sub(A11, A21, k), add(B11, B12, k), k);
float** C11 = sub(add(add(P5, P4, k), P6, k), P2, k);
float** C12 = add(P1, P2, k);
float** C21 = add(P3, P4, k);
float** C22 = sub(sub(add(P5, P1, k), P3, k), P7, k);
for(i = 0; i < k; i++)
for(j = 0; j < k; j++){
C[i][j] = C11[i][j];
C[i][j+k] = C12[i][j];
C[k+i][j] = C21[i][j];
C[k+i][k+j] = C22[i][j];
}
for(i = 0; i < k; i++){
free( A11[i]);
free( A12[i]);
free( A21[i]);
free( A22[i]);
free( B11[i]);
free( B12[i]);
free( B21[i]);
free( B22[i]);
free( P1[i]);
free( P2[i]);
free( P3[i]);
free( P4[i]);
free( P5[i]);
free( P6[i]);
free( P7[i]);
free( C11[i]);
free( C12[i]);
free( C21[i]);
free( C22[i]);
}
free( A11);
free( A12);
free( A21);
free( A22);
free( B11);
free( B12);
free( B21);
free( B22);
free( P1);
free( P2);
free( P3);
free( P4);
free( P5);
free( P6);
free( P7);
free( C11);
free( C12);
free( C21);
free( C22);
return C;
}
int main(){
int i,j, k;
struct timeval begin, end;
for (i = 0; i < N; i++)
A[i] = (float*)malloc(N * sizeof(float));
for (i = 0; i < N; i++)
B[i] = (float*)malloc(N * sizeof(float));
for (i = 0; i < N; i++)
for (j = 0; j < N; j++){
A[i][j] = -1+2*((float)rand())/RAND_MAX;
B[i][j] = -1+2*((float)rand())/RAND_MAX;
}
float** C = matrix_create(N);
gettimeofday(&begin, 0);
C = strassen_multiply(A, B, N);
gettimeofday(&end, 0);
long seconds = end.tv_sec - begin.tv_sec;
long microseconds = end.tv_usec - begin.tv_usec;
double elapsed = seconds + microseconds*1e-6;
printf("number of recursion: %d\n\n", count);
printf("Total wall time: %f\n", elapsed);
}
解决方案
从聊天中转移一些评论。
诊断
您没有检查您的内存是否已成功分配。你不知道是否一切正常。
您从两个 2048x2048
float
矩阵开始。然后,您的strassen_multiply()
函数 (1) 创建 8 个矩阵,每个矩阵的大小只有一半(就行数和列数而言),加载它们,然后连续递归 7 次。这些递归中的每一个都会创建大量矩阵——我还没有坐下来计算所需的总空间,但它会是相当可观的。您确实需要检查您的内存分配是否有效。可能是您的 64 位机器有足够的空间,这不是问题(两个初始矩阵需要 32 MiB 的数据,这可能没问题)。你有这样的电话
float** P1 = strassen_multiply(A11, sub(B12, B22, k), k); float** P2 = strassen_multiply(add(A11, A12, k), B22, k);
sub()
您无法释放对and的嵌套调用返回的矩阵add()
。你不能不释放那段记忆。因此,您正在泄漏大量内存。你需要一个函数来释放你的矩阵——并且可以说是一个记录矩阵大小的矩阵结构类型,因为你需要函数中的大小来释放矩阵。您可以通过检查返回的空指针来检查内存是否已分配
malloc()
。在大多数系统上,这是可靠的。在 Linux 上,它具有 OOM(内存不足)管理器,并且往往会返回一个非空指针,然后当您尝试使用它告诉您可用但实际上不可用的内存时会崩溃。我认为这是非常不受欢迎的行为,但是……如果您未能分配其中一个行,请不要忘记释放该矩阵中任何先前分配的行。你不能使用全局矩阵;你必须从函数返回矩阵,并且你有递归函数,所以全局矩阵不起作用。您需要将矩阵(都是方阵)转换为如下结构:
struct Matrix { int size; float **data; };
你现有的两个全局指针数组
float
应该被替换——否则,你需要特殊的代码来释放分配给它们的内存。在
main()
你有:float** C = matrix_create(N); … C = strassen_multiply(A, B, N);
所以你泄漏了一个全尺寸的矩阵。
返回矩阵的函数将返回一个矩阵结构,而接受两个矩阵参数的函数将采用两个指向(常量)矩阵结构的指针作为参数。概述的矩阵结构非常小,返回指向矩阵结构的指针没有任何好处。
在您当前的代码中
main()
,您应该有:float **A = matrix_create(N); float **B = matrix_create(N);
您在 main() 中的矩阵
C
应使用以下命令创建:float **C = strassen_multiply(A, B, N);
矩阵
C
从来都不是全局的。matrix_create()
像现在一样使用。请记住在调用add()
or的函数中释放返回值sub()
,这也意味着您需要将这些中间结果保存在局部变量中,以便您可以释放它们。您正在为数组索引使用全局变量
i
,j
。k
所有的地狱都会崩溃。数组索引必须是局部变量,特别是如果您使用递归。这意味着您必须在每个函数中声明循环变量。你应该写
for (int i = 0; i < n; i++)
或每个循环的等效项。这将比使用全局变量更有效;它还使您的代码更有可能正确。就目前而言,您没有丝毫机会使代码正确。
处方
将这些点放在一起会产生如下代码:
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#ifndef N
#define N 128
#endif
typedef struct Matrix
{
int size;
float **data;
} Matrix;
static int count = 0;
static size_t cnt_create = 0;
static size_t cnt_destroy = 0;
static size_t cnt_add = 0;
static size_t cnt_sub = 0;
static void err_nomemory(const char *file, const char *func, int line, size_t size)
{
fprintf(stderr, "%s:%s():%d: out of memory attempting to allocate %zu bytes "
"(%d: %s)\n", file, func, line, size, errno, strerror(errno));
exit(EXIT_FAILURE);
}
static void matrix_destroy(Matrix *M)
{
cnt_destroy++;
for (int i = 0; i < M->size; i++)
free(M->data[i]);
free(M->data);
}
static Matrix matrix_create(int n)
{
cnt_create++;
Matrix M = { .size = n, .data = malloc(n * sizeof(float *)) };
if (M.data == NULL)
err_nomemory(__FILE__, __func__, __LINE__, n * sizeof(float *));
for (int i = 0; i < n; i++)
{
if ((M.data[i] = (float *)malloc(n * sizeof(float))) == NULL)
err_nomemory(__FILE__, __func__, __LINE__, n * sizeof(float));
}
return M;
}
static Matrix add(const Matrix *M1, const Matrix *M2)
{
cnt_add++;
assert(M1->size == M2->size);
Matrix M3 = matrix_create(M1->size);
for (int i = 0; i < M1->size; i++)
{
for (int j = 0; j < M1->size; j++)
M3.data[i][j] = M1->data[i][j] + M2->data[i][j];
}
return M3;
}
static Matrix sub(const Matrix *M1, const Matrix *M2)
{
cnt_sub++;
assert(M1->size == M2->size);
Matrix M3 = matrix_create(M1->size);
for (int i = 0; i < M1->size; i++)
{
for (int j = 0; j < M1->size; j++)
M3.data[i][j] = M1->data[i][j] - M2->data[i][j];
}
return M3;
}
static void matrix_print(const char *tag, const Matrix *M)
{
printf("%s (%dx%d):\n", tag, M->size, M->size);
if (M->size > 128)
{
printf("Printing suppressed - matrix too large\n");
return;
}
char buffer[32];
int len = snprintf(buffer, sizeof(buffer), "%d", M->size);
for (int i = 0; i < M->size; i++)
{
printf("[%*d]: ", len, i);
const char *pad = "";
for (int j = 0; j < M->size; j++)
{
printf("%s%f", pad, M->data[i][j]);
pad = "\t";
}
printf("\n");
}
}
static Matrix strassen_multiply(const Matrix *A, const Matrix *B)
{
assert(A->size == B->size);
if (A->size == 1)
{
Matrix C = matrix_create(A->size);
C.data[0][0] = A->data[0][0] * B->data[0][0];
return C;
}
count++;
Matrix C = matrix_create(A->size);
int k = A->size / 2;
/** Creating sub matrices**/
Matrix A11 = matrix_create(k);
Matrix A12 = matrix_create(k);
Matrix A21 = matrix_create(k);
Matrix A22 = matrix_create(k);
Matrix B11 = matrix_create(k);
Matrix B12 = matrix_create(k);
Matrix B21 = matrix_create(k);
Matrix B22 = matrix_create(k);
/** Dividing the Data Matrices A & B **/
for (int i = 0; i < k; i++)
{
for (int j = 0; j < k; j++)
{
A11.data[i][j] = A->data[i + 0][j + 0];
A12.data[i][j] = A->data[i + 0][k + j];
A21.data[i][j] = A->data[k + i][j + 0];
A22.data[i][j] = A->data[k + i][k + j];
B11.data[i][j] = B->data[i + 0][j + 0];
B12.data[i][j] = B->data[i + 0][k + j];
B21.data[i][j] = B->data[k + i][j + 0];
B22.data[i][j] = B->data[k + i][k + j];
}
}
Matrix T1 = sub(&B12, &B22);
Matrix P1 = strassen_multiply(&A11, &T1);
matrix_destroy(&T1);
Matrix T2 = add(&A11, &A12);
Matrix P2 = strassen_multiply(&T2, &B22);
matrix_destroy(&T2);
Matrix T3 = add(&A21, &A22);
Matrix P3 = strassen_multiply(&T3, &B11);
matrix_destroy(&T3);
Matrix T4 = sub(&B21, &B11);
Matrix P4 = strassen_multiply(&A22, &T4);
matrix_destroy(&T4);
Matrix T5A = add(&A11, &A22);
Matrix T5B = add(&B11, &B22);
Matrix P5 = strassen_multiply(&T5A, &T5B);
matrix_destroy(&T5A);
matrix_destroy(&T5B);
Matrix T6A = sub(&A12, &A22);
Matrix T6B = add(&B21, &B22);
Matrix P6 = strassen_multiply(&T6A, &T6B);
matrix_destroy(&T6A);
matrix_destroy(&T6B);
Matrix T7A = sub(&A11, &A21);
Matrix T7B = add(&B11, &B12);
Matrix P7 = strassen_multiply(&T7A, &T7B);
matrix_destroy(&T7A);
matrix_destroy(&T7B);
matrix_destroy(&A11);
matrix_destroy(&A12);
matrix_destroy(&A21);
matrix_destroy(&A22);
matrix_destroy(&B11);
matrix_destroy(&B12);
matrix_destroy(&B21);
matrix_destroy(&B22);
Matrix C1A = add(&P5, &P4);
Matrix C1B = add(&C1A, &P6);
Matrix C11 = sub(&C1B, &P2);
Matrix C12 = add(&P1, &P2);
Matrix C21 = add(&P3, &P4);
Matrix C2A = add(&P5, &P1);
Matrix C2B = sub(&C2A, &P3);
Matrix C22 = sub(&C2B, &P7);
matrix_destroy(&C1A);
matrix_destroy(&C1B);
matrix_destroy(&C2A);
matrix_destroy(&C2B);
matrix_destroy(&P1);
matrix_destroy(&P2);
matrix_destroy(&P3);
matrix_destroy(&P4);
matrix_destroy(&P5);
matrix_destroy(&P6);
matrix_destroy(&P7);
for (int i = 0; i < k; i++)
{
for (int j = 0; j < k; j++)
{
C.data[i + 0][j + 0] = C11.data[i][j];
C.data[i + 0][j + k] = C12.data[i][j];
C.data[k + i][j + 0] = C21.data[i][j];
C.data[k + i][k + j] = C22.data[i][j];
}
}
matrix_destroy(&C11);
matrix_destroy(&C12);
matrix_destroy(&C21);
matrix_destroy(&C22);
return C;
}
int main(void)
{
struct timeval begin, end;
Matrix A = matrix_create(N);
Matrix B = matrix_create(N);
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
A.data[i][j] = -1.0 + 2.0 * ((double)rand()) / RAND_MAX;
B.data[i][j] = -1.0 + 2.0 * ((double)rand()) / RAND_MAX;
}
}
gettimeofday(&begin, 0);
Matrix C = strassen_multiply(&A, &B);
gettimeofday(&end, 0);
matrix_print("A", &A);
matrix_print("B", &B);
matrix_print("C", &C);
matrix_destroy(&A);
matrix_destroy(&B);
matrix_destroy(&C);
long seconds = end.tv_sec - begin.tv_sec;
long microseconds = end.tv_usec - begin.tv_usec;
double elapsed = seconds + microseconds * 1e-6;
printf("Number of non-minimal recursive calls: %d\n", count);
printf("Number of matrices created: %zu\n", cnt_create);
printf("Number of matrices destroyed: %zu\n", cnt_destroy);
printf("Number of matrix additions: %zu\n", cnt_add);
printf("Number of matrix subtractions: %zu\n", cnt_sub);
printf("Total wall time: %f\n", elapsed);
return 0;
}
这通过调用一个简单退出的函数来检测内存分配错误,而不是释放任何成功分配的内存并返回给调用者。
代码可以用2 的-DN=256
任何其他幂进行编译。目前尚不清楚如果大小不是 2 的幂会发生什么。
表现
各种大小的一些采样时间和其他统计信息:
N=8
Number of non-minimal recursive calls: 57
Number of matrices created: 1884
Number of matrices destroyed: 1884
Number of matrix additions: 627
Number of matrix subtractions: 399
Total wall time: 0.000480
N=16
Number of non-minimal recursive calls: 400
Number of matrices created: 13203
Number of matrices destroyed: 13203
Number of matrix additions: 4400
Number of matrix subtractions: 2800
Total wall time: 0.003723
N=32
Number of non-minimal recursive calls: 2801
Number of matrices created: 92436
Number of matrices destroyed: 92436
Number of matrix additions: 30811
Number of matrix subtractions: 19607
Total wall time: 0.025097
N=64
Number of non-minimal recursive calls: 19608
Number of matrices created: 647067
Number of matrices destroyed: 647067
Number of matrix additions: 215688
Number of matrix subtractions: 137256
Total wall time: 0.161971
N=128
Number of non-minimal recursive calls: 137257
Number of matrices created: 4529484
Number of matrices destroyed: 4529484
Number of matrix additions: 1509827
Number of matrix subtractions: 960799
Total wall time: 1.164555
N=256
Number of non-minimal recursive calls: 960800
Number of matrices created: 31706403
Number of matrices destroyed: 31706403
Number of matrix additions: 10568800
Number of matrix subtractions: 6725600
Total wall time: 7.632881
N=512
Number of non-minimal recursive calls: 6725601
Number of matrices created: 221944836
Number of matrices destroyed: 221944836
Number of matrix additions: 73981611
Number of matrix subtractions: 47079207
Total wall time: 53.730002
N=1024
Number of non-minimal recursive calls: 47079208
Number of matrices created: 1553613867
Number of matrices destroyed: 1553613867
Number of matrix additions: 517871288
Number of matrix subtractions: 329554456
Total wall time: 373.596480
N=2048
Number of non-minimal recursive calls: 329554457
Number of matrices created: 10875297084
Number of matrices destroyed: 10875297084
Number of matrix additions: 3625099027
Number of matrix subtractions: 2306881199
Total wall time: 2737.750096
请注意,创建的矩阵数量与销毁的数量相同;这让人放心。还要注意,有大量的矩阵被创建和销毁。
然而,将要相乘的矩阵的大小加倍并不是三次时间;它比 O(N³) 好,而朴素算法是 O(N³)。
提高性能
提高代码速度的一种方法是特殊情况下的 2x2 矩阵乘法。实施后,结果如下:
N=16
Number of large multiplications: 57
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 343
Number of matrices created: 1884
Number of matrices destroyed: 1884
Number of matrix additions: 627
Number of matrix subtractions: 399
Total wall time: 0.001045
N=32
Number of large multiplications: 400
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 2401
Number of matrices created: 13203
Number of matrices destroyed: 13203
Number of matrix additions: 4400
Number of matrix subtractions: 2800
Total wall time: 0.006532
N=64
Number of large multiplications: 2801
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 16807
Number of matrices created: 92436
Number of matrices destroyed: 92436
Number of matrix additions: 30811
Number of matrix subtractions: 19607
Total wall time: 0.038640
N=128
Number of large multiplications: 19608
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 117649
Number of matrices created: 647067
Number of matrices destroyed: 647067
Number of matrix additions: 215688
Number of matrix subtractions: 137256
Total wall time: 0.263008
N=256
Number of large multiplications: 137257
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 823543
Number of matrices created: 4529484
Number of matrices destroyed: 4529484
Number of matrix additions: 1509827
Number of matrix subtractions: 960799
Total wall time: 1.796228
N=512
Number of large multiplications: 960800
Number of 1x1 multiplications: 0
Number of 2x2 multiplications: 5764801
Number of matrices created: 31706403
Number of matrices destroyed: 31706403
Number of matrix additions: 10568800
Number of matrix subtractions: 6725600
Total wall time: 12.383302
作为比较,使用 1x1 和 2x2 特殊情况创建和销毁的矩阵数量为:
N 1x1 2x2
16 13,203 1,884
32 92,436 13,203
64 647,067 92,436
128 4,528,484 647,067
256 31,706,403 4,529,484
512 221,944,836 31,706,403
观察为乘以 NxN 矩阵的 1x1 最小情况创建的矩阵数量与使用 2Nx2N 矩阵的 2x2 最小情况相同。它还提供了相当显着的加速(比较 N=512 和 1x1 为 53.73 秒,而 N=512 和 2x2 为 12.38 秒)。很多原始成本是创建 1x1 矩阵以相乘。
其他建议
只有在大量使用子矩阵时才应复制子矩阵——以提高缓存局部性。否则,“子矩阵”就不是一个变量,而是一个概念。这意味着当您执行任何矩阵运算时,您应该传递一些描述索引范围和索引步幅的对象,例如矩阵乘法。这样你就不会创建那些子矩阵。一般来说,要使这段代码合理,还有很多工作要做。
这将使矩阵结构更加复杂,但也会从根本上提高性能。您最终可能会matrix_create()
返回 a Matrix *
,并且该结构将包含额外的元素:int tl_r; int tl_c; int br_r; int br_c;
(左上的行和列,左下的行和列)。您将有另一个函数将矩阵拆分为 4 个四分之一矩阵,这些矩阵都将引用未拆分矩阵的数据,但子矩阵的左上角和右下角坐标具有不同的值。如果您继续使用指向浮点组织数组的当前指针数组,则不需要记录“步幅”(原始数组中每行的宽度,这也是高度,因为这仅处理方阵) . 您必须小心内存管理。结果数组将被重新创建。您不会发布季度矩阵中的数据——只会发布那些重新创建的数据。
为什么将指针数组用于方数组?这是无缘无故的大量开销。只需创建一个 N*N 浮点数组!然后你就可以开始简化所有这些疯狂的内存管理了。
尽管需要谨慎,但其中有一些正义。我仍然认为您将使用结构,但数据元素将float *
代替float **
,并且您将计算数组索引 ( row * width + col
) 而不是使用两个下标。如果您放弃结构,则可以改用“可变长度数组”(VLA)表示法。需要小心。数组仍将是动态分配的。
进一步的实验和建议
我已经尝试过 4x4 和 8x8 的特殊情况,由于减少了内存管理开销(更少的矩阵分配和破坏),两者都提供了相当大的好处。将具有不同最小尺寸的 1024x1024 矩阵相乘得到:
Size Time #Matrices
1x1 6m 32s 1,553,613,867
2x2 1m 31s 221,944,836
4x4 23s 31,706,403
8x8 7s 4,529,484
我还编写了一个执行直接原始矩阵乘法(O(N³) 算法 - 使用与我用于 NxN 的 8x8 乘法相同的代码)的版本,它比 Strassen 算法快很多,主要是因为有几乎不需要内存管理。
Size Time
128x128 3ms
256x256 25ms
512x512 280ms
1024x1024 1,802ms
2048x2048 84,686ms
4096x4096 850,860ms
请注意,1024 处的 1.80s 和 2048 处的 84.7s 之间的时间乘积大于三次关系(或多或少适用于其他情况的 8 倍)——我没有调查原因。
我认为从这里加快速度的关键不是复制矩阵——使用 Unslander Monica 的建议。我注意到您可能不需要我之前建议的 4 个坐标;2个坐标和大小就足够了(因为矩阵都是正方形的)。这减少了复制和内存分配——这将对性能产生重大影响。
我不认为施特拉森被证明是失败的。我认为这表明您(我们)正在使用的幼稚内存管理不是要走的路。但我也认为,除非您的矩阵大于 1024x1024,否则天真的乘法算法可能足够快。如果您必须处理大数组,那么 Strassen 算法可能是有益的。需要更多的编码和测试!
推荐阅读
- node.js - WebStorm 找不到节点解释器
- spring-boot - Spring Boot with H2 每次 web 应用启动时都会运行 data.sql,这正常吗?
- parsing - LR 解析表中有哪些列?
- asp.net-core - Blazor/API 路由问题 - 入门
- xcode - 如何在此类中使用可观察对象?
- javascript - 我不知道为什么我的函数无法读取引号数据
- python - 如何将最后六张最新图像制作成 GIF 动画?
- python-3.x - 如何在不同的应用程序更新数据库后刷新表单字段和视图?姜戈,蟒蛇
- git - Github 操作:Git diff master...HEAD 致命:没有合并基础
- javascript - 在渲染时将默认道具绑定到组件