C: производительность pthread ниже, чем у single thread

Я запутался в производительности своего кода, при работе с одним потоком он использует только 13 с, но он будет потреблять 80 с. Я не знаю, может ли вектор быть доступен только одному потоку за раз, если это так, вероятно, мне придется использовать массив структур для хранения данных вместо вектора, может ли кто-нибудь помочь?

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iterator>
#include <string>
#include <ctime>
#include <bangdb/database.h>
#include "SEQ.h"

#define NUM_THREADS 16

using namespace std;


typedef struct _thread_data_t {
    std::vector<FDT> *Query;
    unsigned long start;
    unsigned long end;
    connection* conn;
    int thread;
} thread_data_t;



void *thr_func(void *arg) {

    thread_data_t *data = (thread_data_t *)arg;
    std::vector<FDT> *Query = data->Query;
    unsigned long start = data->start;
    unsigned long end = data->end;
    connection* conn = data->conn;

    printf("thread %d started %lu -> %lu\n", data->thread, start, end);

    for (unsigned long i=start;i<=end ;i++ )
    {
        FDT *fout = conn->get(&((*Query).at(i)));
        if (fout == NULL)
        {
            //printf("%s\tNULL\n", s);

        }
        else
        {
            printf("Thread:%d\t%s\n", data->thread, fout->data);
        }
    }

    pthread_exit(NULL);
}


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

    if (argc<2)
    {
        printf("USAGE: ./seq <.txt>\n");
        printf("/home/rd/SCRIPTs/12X18610_L5_I052.R1.clean.code.seq\n");

        exit(-1);
    }
    printf("%s\n", argv[1]);

    vector<FDT> Query;

    FILE* fpin;
    if((fpin=fopen(argv[1],"r"))==NULL)  {
        printf("Can't open Input file %s\n", argv[1]);
        return -1; 
    }

    char *key = (char *)malloc(36);

    while (fscanf(fpin, "%s", key) != EOF)
    {
        SEQ * sequence = new SEQ(key);

        FDT *fk = new FDT( (void*)sequence, sizeof(*sequence) );

        Query.push_back(*fk);
    }

    unsigned long Querysize = (unsigned long)(Query.size());
    std::cout << "myvector stores " << Querysize << " numbers.\n";



    //create database, table and connection
    database* db = new database((char*)"berrydb");

    //get a table, a new one or existing one, walog tells if log is on or off
    table* tbl = db->gettable((char*)"hg19", JUSTOPEN);

    if(tbl == NULL)
    {
        printf("ERROR:table NULL error");
        exit(-1);
    }

    //get a new connection
    connection* conn = tbl->getconnection();
    if(conn == NULL)
    {
        printf("ERROR:connection NULL error");
        exit(-1);
    }

    cerr<<"begin querying...\n";


    time_t begin, end;
    double duration;
    begin = clock();




    unsigned long ThreadDealSize = Querysize/NUM_THREADS;
    cerr<<"Querysize:"<<ThreadDealSize<<endl;



    pthread_t thr[NUM_THREADS];
    int rc;

    thread_data_t thr_data[NUM_THREADS];

    for (int i=0;i<NUM_THREADS ;i++ )
    {
        unsigned long ThreadDealStart = ThreadDealSize*i;
        unsigned long ThreadDealEnd   = ThreadDealSize*(i+1) - 1;

        if (i == (NUM_THREADS-1) )
        {
            ThreadDealEnd = Querysize-1;
        }

        thr_data[i].conn = conn;
        thr_data[i].Query = &Query;
        thr_data[i].start = ThreadDealStart;
        thr_data[i].end = ThreadDealEnd;
        thr_data[i].thread = i;
    }


    for (int i=0;i<NUM_THREADS ;i++ )
    {
        if (rc = pthread_create(&thr[i], NULL, thr_func, &thr_data[i]))
        {
          fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
          return EXIT_FAILURE;
        }
    }


    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(thr[i], NULL);
    }


    cerr<<"done\n"<<endl;
    end = clock();
    duration = double(end - begin) / CLOCKS_PER_SEC;
    cerr << "runtime:   " << duration << "\n" << endl;

    db->closedatabase(OPTIMISTIC);
    delete db;
    printf("Done\n");


  return EXIT_SUCCESS;
}

person user1744416    schedule 14.12.2012    source источник
comment
Это может случиться, когда однопоточный код работает быстрее, чем многопоточный, из-за времени переключения контекста в многопоточной программе.   -  person Grijesh Chauhan    schedule 14.12.2012


Ответы (1)


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

Что, вероятно, ваша проблема, так это printf. printf является потокобезопасным, то есть вы можете вызывать его из любого количества потоков одновременно, но за счет внутреннего взаимного исключения.

Большая часть работы в поточной части вашей программы выполняется внутри printf. Итак, что, вероятно, происходит, так это то, что все потоки запускаются и быстро достигают printf, где останавливаются все, кроме первого. Когда printf завершает работу и освобождает мьютекс, система рассматривает возможность планирования ожидавших его потоков. Вероятно, это так, поэтому происходит довольно медленное переключение контекста. И повторяется через каждые printf.

Как именно это происходит, зависит от того, какой фактический примитив блокировки используется, что зависит от вашей операционной системы и версий стандартной библиотеки. Система должна каждый раз будить только следующего спящего, но многие реализации фактически будят их всех. Таким образом, в дополнение к тому, что printf выполняются в основном в циклическом режиме, вызывая одно переключение контекста для каждого, может быть довольно много дополнительных ложных пробуждений, в которых поток просто обнаруживает, что блокировка удерживается, и снова переходит в спящий режим.

Таким образом, урок из этого состоит в том, что потоки не делают вещи автоматически быстрее. Они помогают только тогда, когда:

  • Поток тратит большую часть своего времени на блокировку системных вызовов. В таких вещах, как сетевые серверы, потоки ждут данных из сокета, затем данных для ответа с диска и, наконец, сети для принятия ответа. В таких случаях помогает наличие множества потоков, если они в основном независимы.
  • Количество потоков равно количеству потоков процессора. В настоящее время обычно используется число 4 (четырехъядерный или двухъядерный процессор с технологией Hyper-Threading). Дополнительные потоки физически не могут выполняться параллельно, поэтому они не дают выигрыша и несут небольшие накладные расходы. Таким образом, 16 потоков — это излишество.

И они никогда не помогают, когда все манипулируют одними и теми же объектами, так что большую часть времени они все равно тратят на ожидание блокировок. В дополнение к любому из ваших собственных объектов, которые вы блокируете, имейте в виду, что дескрипторы входных и выходных файлов также должны быть внутренне заблокированы.

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

person Jan Hudec    schedule 14.12.2012