Что работоспособнее массив или вектор в C?

Я хотел бы узнать, который два работоспособнее относительно времени выполнения (не важно, если он тратит больше ресурсов, чем другие). Вышеупомянутое сравнение - на основании следующего:

Acceder de forma matriz[fila][columna];
Acceder de forma vector[totalcolumnas * fila + columna];

Или если существует другой лучший метод.

4
задан 12.01.2017, 07:24
0 ответов

Я женю 1: произвольный доступ

Мы будем подтверждать, что происходит с практическим простым случаем:

int matrizPila[10][10];
int **matrizDinamica;
int vectorPila[100];
int* vectorDinamico;

int funcMatrizPila(int fila, int columna)
{
  return matrizPila[fila][columna];
}

int funcMatrizDinamica(int fila, int columna)
{
  return matrizDinamica[fila][columna];
}

int funcVectorPila(int fila, int columna)
{
  return vectorPila[fila*10+columna];
}

int funcVectorDinamico(int fila, int columna, int numColumnas)
{
  return vectorDinamico[fila*numColumnas+columna];
}

Я приклеил этикетку на переменные как extern чтобы предотвращать warnings, составив.

Compilándo предыдущий код с gcc 6.3 (это компиляция C ++, хотя я не думаю, что ты дал много одной компиляции в C) результат - следующий:

funcMatrizPila(int, int):
    movsx   rdi, edi
    movsx   rsi, esi
    lea     rax, [rdi+rdi*4]
    lea     rax, [rsi+rax*2]
    mov     eax, DWORD PTR matrizPila[0+rax*4]
    ret
funcMatrizDinamica(int, int):
    mov     rax, QWORD PTR matrizDinamica[rip]
    movsx   rdi, edi
    movsx   rsi, esi
    mov     rax, QWORD PTR [rax+rdi*8]
    mov     eax, DWORD PTR [rax+rsi*4]
    ret
funcVectorPila(int, int):
    lea     eax, [rdi+rdi*4]
    lea     eax, [rsi+rax*2]
    cdqe
    mov     eax, DWORD PTR vectorPila[0+rax*4]
    ret
funcVectorDinamico(int, int, int):
    imul    edi, edx
    mov     rax, QWORD PTR vectorDinamico[rip]
    lea     edx, [rdi+rsi]
    movsx   rdx, edx
    mov     eax, DWORD PTR [rax+rdx*4]
    ret

Если мы анализируем вытекающий код, мы можем доставать какие-то заключения:

  • В случае matrizPila и vectorPila результат - практически тот же самый. Это, потому что в обоих случаях память смежная и она организована той же формы, потом операция, которая позволяет получать стоимость ячейки, - та же самая.
  • В случае matrizDinamica составитель должен решать два неадреса, чтобы получать стоимость (один, чтобы прыгать в линию, которая соответствует, и другой, чтобы прыгать в колонну для данной линии). Это переводится в более бедном результате относительно двух предыдущих случаев.
  • В случае vectorDinamico, составитель оказывается обязанным реализовывать еще один неадрес относительно vectorPila. Это переводится, как в предыдущем случае, в нижнем результате. Кроме того сейчас он должен реализовывать продукт ручной формы (fila*numColumnas -> imul edi,edx), что предполагает важное наказание.

Деталь, которая не видна в сборщике, но которая также преданная является местностью данных. Компьютеры склоняются к тому, чтобы обыскивать память, чтобы позволять быстрый доступ к данным. Это склоняется к тому, чтобы ускорять очень много операции, если данные склоняются к тому, чтобы быть все вместе. В случае matrizDinamica эта точка не выполняется, так как каждая линия будет находиться в случайном положении памяти и это вынудит к системе повторно листать кэш-память постоянно и возможно переводить это легко в потере достаточно воспринимаемого результата.

Я женю 2: я имею доступ в положения, известные во время компиляции

int matrizPila[10][10];
int **matrizDinamica;
int vectorPila[100];
int* vectorDinamico;

int funcMatrizPila()
{
  return matrizPila[3][4];
}

int funcMatrizDinamica()
{
  return matrizDinamica[3][4];
}

int funcVectorPila()
{
  return vectorPila[3*10+4];
}

int funcVectorDinamico(int numColumnas)
{
  return vectorDinamico[3*numColumnas+4];
}

Для этого кода вытекающий сборщик:

funcMatrizPila():
    mov     eax, DWORD PTR matrizPila[rip+136]
    ret
funcMatrizDinamica():
    mov     rax, QWORD PTR matrizDinamica[rip]
    mov     rax, QWORD PTR [rax+24]
    mov     eax, DWORD PTR [rax+16]
    ret
funcVectorPila():
    mov     eax, DWORD PTR vectorPila[rip+136]
    ret
funcVectorDinamico(int):
    lea     eax, [rdi+rdi*2]
    mov     rdx, QWORD PTR vectorDinamico[rip]
    cdqe
    mov     eax, DWORD PTR [rdx+16+rax*4]
    ret

В этом случае мы наблюдаем следующее:

  • Доступ в matrizPila и в vectorPila это точно тот же самый и время выполнения склоняется к 0. Составитель способен предвычислять офсет разрыва, уменьшая получение результата, которое должно решать простую сумму.
  • Доступ в matrizDinamica и vectorDinamico он проще, что было бы должно подразумевать меньшее время выполнения. Может быть, самый больший эффект получают в случае vectorDinamico, но, насколько самый дорого решать два неадреса он должен продолжать реализовывать их.

Такие совещания вещи классификация, в этом случае, будет очень сходной с той случая 1, но с временами выполнения больше отмелей.

Практический случай:

Мы протестируем сейчас все случаи, которые мы увидели, чтобы видеть, как они ведут себя. Для этого мы можем использовать такой код как следующий:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int matrizPila[10][10];
int **matrizDinamica;
int vectorPila[100];
int* vectorDinamico;

int funcMatrizPila(int fila, int columna)
{
  return matrizPila[fila][columna];
}

int funcMatrizDinamica(int fila, int columna)
{
  return matrizDinamica[fila][columna];
}

int funcVectorPila(int fila, int columna)
{
  return vectorPila[fila*10+columna];
}

int funcVectorDinamico(int fila, int columna, int numColumnas)
{
  return vectorDinamico[fila*numColumnas+columna];
}

int funcMatrizPilaFijo()
{
  return matrizPila[3][4];
}

int funcMatrizDinamicaFijo()
{
  return matrizDinamica[3][4];
}

int funcVectorPilaFijo()
{
  return vectorPila[3*10+4];
}

int funcVectorDinamicoFijo(int numColumnas)
{
  return vectorDinamico[3*numColumnas+4];
}

int res = 0;

#define BEGIN_TEST \
clock_t begin = clock(); \
for( int i=0; i<10000000; i++) \
  for( int j=0; j<100; j++)

#define END_TEST(nombre) \
clock_t end = clock(); \
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; \
printf("%-22s: %f\n",nombre,time_spent);

void testFunc0( const char* funcName, int(*func)())
{
  BEGIN_TEST

  res += func();

  END_TEST(funcName)
}

void testFunc1( const char* funcName, int(*func)(int), int var1)
{
  BEGIN_TEST

  res += func(var1);

  END_TEST(funcName)
}

void testFunc2( const char* funcName, int(*func)(int, int), int var1, int var2)
{
  BEGIN_TEST

  res += func(var1,var2);

  END_TEST(funcName)
}

void testFunc3( const char* funcName, int(*func)(int, int, int), int var1, int var2, int var3)
{
  BEGIN_TEST

  res += func(var1,var2,var3);

  END_TEST(funcName)
}

int main()
{
  matrizDinamica = (int**)malloc(10*sizeof(int*));
  for(int i=0; i<10; i++)
    matrizDinamica[i] = (int*)malloc(10*sizeof(int));

  vectorDinamico = (int*)malloc(100*sizeof(int));

  testFunc0( "funcMatrizPilaFijo",    funcMatrizPilaFijo);
  testFunc0( "funcVectorPilaFijo",    funcVectorPilaFijo);
  testFunc0( "funcMatrizDinamicaFijo",funcMatrizDinamicaFijo);
  testFunc1( "funcVectorDinamicoFijo",funcVectorDinamicoFijo,10);
  testFunc2( "funcMatrizPila",        funcMatrizPila,3,4);
  testFunc2( "funcVectorPila",        funcVectorPila,3,4);
  testFunc2( "funcMatrizDinamica",    funcMatrizDinamica,3,4);
  testFunc3( "funcVectorDinamico",    funcVectorDinamico,3,4,10);
}

В моем случае полученные результаты - следующие:

funcMatrizPilaFijo    : 1.810000
funcVectorPilaFijo    : 1.800000
funcMatrizDinamicaFijo: 1.800000
funcVectorDinamicoFijo: 2.092000
funcMatrizPila        : 2.161000
funcVectorPila        : 2.190000
funcMatrizDinamica    : 2.321000
funcVectorDinamico    : 2.940000

Там мы видим, как действительно соглашаться на положения, известные во время компиляции предполагает прогресс времени (касательно 10 %-20 %).

Результат, который, кажется, противоречит тому, что я прокомментировал, - что времена доступа vectorDinamico они хуже, чем полученные для matrizDinamica. Это такое, потому что в этом таком простом примере у памяти массива не только есть большие возможности кончаться положениями последующими но кроме того ввиду Ваш мало размер (400 байт), он не был бы должен представлять слишком много проблем для пагинации кэш-памяти. В более сложных средах результат matrizDinamica он должен бы быть достаточно хуже.

5
ответ дан 03.12.2019, 17:44
  • 1
    У меня нет слов. Безгрешный. Peaso из ответа, который мы говорим в моей земле. –  12.01.2017, 19:40
  • 2
    @Trauma спасибо :). Я думаю, что твой ответ совместим с mí в, deberí схвати, ставить реставрировать ее –  15.01.2017, 01:04