Выполнение фрагмента кода путем создания одного потока ускоряет выполнение - PullRequest
0 голосов
/ 23 июня 2019

Я пробую некоторые тесты с использованием потоков Intel AVX2 и Posix.Давайте предположим, что я пытаюсь найти минимальное значение в образце.Когда я создаю простую программу, я запускаю функцию avx_min.Когда я создаю программу, которая внутри создает поток Posix, я изменил реализацию avx_min на avx_min_thread, как показано ниже, но фактическая реализация остается той же.Эта функция может использоваться для более чем одного потока, и ей не требуется синхронизация, поскольку потоки не «конфликтуют» (tid = 0,1, 2 и т. Д.).

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

PS: я компилирую их, используя:

  • case 1 (без создания потока): g ++ -mavx2 -O3 -o avxMinO3 avxMinO3.cpp

  • case 2 (создание потока posix внутри): g ++ -mavx2avxMinO3_t.cpp -lpthread -O3 -o avxMinO3_t

PS 2:

  • Время выполнения первого случая: 0,34 с
  • Выполнение второго случаявремя: 0,049 с
Case 1:

double initialize_input(int32_t** relation, int32_t value_bound, int32_t input_size){

  clock_t t;
  srand(time(NULL));
  t = clock();
  for(int32_t i = 0 ; i < input_size ; i++){
    (*relation)[i] = rand() % value_bound;
  }
  t = clock() - t;
  return ((double) t) / CLOCKS_PER_SEC;
}


int* avx_min(int32_t** relation, int32_t rel_size, double* function_time){

  clock_t tic, tac;
  __m256i input_buffer;
  int32_t* rel = (*relation);
  __m256i min = _mm256_set_epi32(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX);
  tic = clock();
  for(int i = 0 ; i < rel_size ; i += 8){
    input_buffer = _mm256_stream_load_si256((__m256i*)&rel[i]);
    min = _mm256_min_epi32(min, input_buffer);
  }
  tac = clock();
  double time_diff = (double)(tac - tic);
  (*function_time) = time_diff / CLOCKS_PER_SEC;
  int* temp = (int*)&min;
  return temp;
}


int main(int argc, char** argv) {

  int32_t* relation;
  double* function_time;
  int32_t input_size = 1024 * 1024 * 1024;
  int32_t value_bound = 1000;

  int alloc_time = initialize_input(&relation, value_bound, input_size);
  int* res = avx_min(&relation, input_size, function_time);
  return 0;
}

Случай 2:

template<typename T>
struct thread_input {
  T* relation;
  T rel_size;
  double function_time;
  short numberOfThreads;
  short tid;
};


template<typename T, typename S, typename I, typename RELTYPE>
T** createAndInitInputPtr(S numberOfThreads, I rel_size, S value_bound, RELTYPE** relation ){
  T **result = new T*[numberOfThreads];

  for (int i = 0; i < numberOfThreads; i++) {
    result[i] = new T;
    result[i]->rel_size = rel_size;
    result[i]->relation = (*relation);
    result[i]->numberOfThreads = numberOfThreads;
    result[i]->tid = i;
  }
  return result;
}


void* avx_min_t(void* input){

    clock_t tic, tac;
    struct thread_input<int32_t> *input_ptr;
    input_ptr = (struct thread_input<int32_t>*) input;

    int32_t* relation = input_ptr->relation;
    int32_t rel_size = input_ptr->rel_size;
    int32_t start = input_ptr->tid * 8;
    int32_t offset = input_ptr->numberOfThreads * 8;

    __m256i input_buffer;
    __m256i min = _mm256_set_epi32(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX);
    tic = clock();
    for(int i = start ; i < rel_size ; i += offset){
      input_buffer = _mm256_stream_load_si256((__m256i*)&relation[i]);
      min = _mm256_min_epi32(min, input_buffer);
    }
    tac = clock();
    double time_diff = (double)(tac-tic);
    time_diff = time_diff / CLOCKS_PER_SEC;
    input_ptr->function_time = time_diff;
}


int main(int argc, char* argv[]){

  int rel_size = 1024 * 1024 * 1024;
  short numberOfThreads = 1;
  short value_bound = 1000;

  pthread_t* threads = new pthread_t[numberOfThreads];

  short flag = 1; // flag to check proper aligned memory allocations

  int32_t* relation;
  double alloc_time = 0.0;
  flag = posix_memalign((void**)&relation, 32, rel_size * sizeof(int32_t));
  if(flag) {
    std::cout << "Memory allocation problem. Exiting..." << std::endl;
    exit(1);
  }
  alloc_time += initialize_input(&relation, value_bound, rel_size);


  struct thread_input<int32_t> **input_ptr = createAndInitInputPtr<struct thread_input<int32_t>, short, int, int32_t>(numberOfThreads, rel_size, value_bound, &relation);

  clock_t tic = clock();

  for (int i = 0; i < numberOfThreads; i++) {
    pthread_create(&threads[i], NULL, avx_min_t,(void*) input_ptr[i]);
  }


  for (int i = 0; i < numberOfThreads; i++) {
    pthread_join(threads[i], NULL);
  }
  tic = clock()-tic;
  double time = tic / CLOCKS_PER_SEC;
  std::cout << time << std::endl;
  return 0;
}

1 Ответ

3 голосов
/ 23 июня 2019

void* avx_min_t(void* input) ничего не делает с min, поэтому загрузка SIMD из массива оптимизируется.

Его внутренний цикл компилируется с gcc -O3 -march=haswell, и clang в основном такой же.

.L3:
        add     ebx, r12d
        cmp     r13d, ebx
        jg      .L3

Так что это буквально пустой цикл в asm, который занимает 0,04 секунды, чтобы увеличить указатель в 4GB / 32 bytes раз.

for(int i = start ; i < rel_size ; i += offset){
}

Я думаю, что вы хотели что-то вернуть, потому что функция объявлена ​​void* и имеет неопределенное поведение от выпадения конца непустой функции. GCC и clang предупреждают об этом по умолчанию, даже без необходимости -Wall. https://godbolt.org/z/Z1GWpU

<source>: In function 'void* avx_min_t(void*)':
<source>:66:1: warning: no return statement in function returning non-void [-Wreturn-type]
   66 | }
      | ^

Всегда проверяйте предупреждения вашего компилятора, особенно когда ваш код ведет себя странно. Включите -Wall и исправляйте также все предупреждения.

...