Многопоточное добавление общих матриц очень медленно при использовании пользовательских классов - PullRequest
0 голосов
/ 22 октября 2018

Я реализовал добавление двух общих матриц, используя многопоточность и последовательный алгоритм.Я протестировал свою программу с двумя большими матрицами (2000x2000), которые содержали действительные числа (двойные числа), и результаты были очень хорошими.Операцию удалось закончить очень быстро.Позже я реализовал класс, представляющий комплексное число, и попытался повторить один и тот же сценарий с двумя матрицами, и обнаружил, что требуются целые годы, чтобы завершить весь процесс даже для двух матриц 50x50.Что я должен сделать, чтобы увеличить продолжительность выполнения?

Это метод, который создает потоки (прежде всего я создаю два одномерных массива, чтобы было легче обеспечить каждый поток своей начальной и конечной точкой):

template<typename T, typename Func>
Matrix<T> *calculateLinearDistribution(Matrix<T> *matrix1,
                                       Matrix<T> *matrix2,
                                       Func operation,
                                       int nThreads) {
    const int n = matrix1->getN(), m = matrix2->getM(), totalNumbers = n * m;
    Matrix<T> *result = new Matrix<T>(n, m);
    T *matrix1Unidim = new T[totalNumbers];
    T *matrix2Unidim = new T[totalNumbers];
    convertMatrixToUnidimensionalArray(matrix1, matrix1Unidim);
    convertMatrixToUnidimensionalArray(matrix1, matrix2Unidim);
    if (totalNumbers < nThreads) {
        nThreads = totalNumbers;
    }
    const int quantityPerThread = totalNumbers / nThreads;
    int rest = totalNumbers % nThreads;
    int start = 0, end = 0;
    std::vector<std::thread> threads;
    std::chrono::milliseconds startTime = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch());
    for (int i = 0; i < nThreads; i++) {
        end += quantityPerThread;
        if (rest > 0) {
            end++;
            rest--;
        }
        threads.push_back(std::thread(MultithreadedMethods<T, Func>::linearElementsDistribution, &matrix1Unidim[0],
                                      &matrix2Unidim[0], result, start, end, operation));
        start = end;
    }
    for (int i = 0; i < nThreads; i++) {
        threads[i].join();
    }
    std::chrono::milliseconds endTime = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch());
    std::ofstream out(linearElemensStatisticsFile, std::ios_base::app);
    std::chrono::milliseconds time = endTime - startTime;
    out << "Dimensiune matrice: " << matrix1->getN() << "x" << matrix1->getM()
        << " | Nr. threads: " << nThreads << " | Timp de executie: " << time.count() << std::endl;
    out.close();
    delete[] matrix1Unidim;
    delete[] matrix2Unidim;
    return result;
}

Это функция, предоставленная потоку:

template<typename T, typename Func>
void MultithreadedMethods<T, Func>::linearElementsDistribution(T *matrix1,
                                                               T *matrix2,
                                                               Matrix<T> *result,
                                                               int start,
                                                               int end,
                                                               Func operation) {
    const int m = result->getM();
    for (int i = start; i < end; i++) {
        result->getElements()[i / m][i % m] = operation(matrix1[i], matrix2[i]);
    }
}

Здесь я запускаю процесс с действительными числами (это очень быстро):

Matrix<double> *linearDistributionResult = calculateLinearDistribution(matrix1,
                                                                               matrix2,
                                                                               [](double a, double b) {
                                                                                   return a +
                                                                                          b;
                                                                               }, nThreads);

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

Matrix<ComplexNumber> *linearDistributionResult = calculateLinearDistribution(matrix1,
                                                                                          matrix2,
                                                                                          [](ComplexNumber a,
                                                                                             ComplexNumber b) {
                                                                                              return ComplexNumber(
                                                                                                      a.getRealComponent() +
                                                                                                      b.getRealComponent(),
                                                                                                      a.getImaginaryComponent() +
                                                                                                      b.getImaginaryComponent());
                                                                                          }, nThreads);

Конечно, этоявляется последовательной реализацией (я хотел бы отметить, что это также очень медленно, когда я использую комплексные числа по сравнению с действительными числами):

template<typename T, typename Func>
Matrix<T> *calculateSequentialResult(Matrix<T> *matrix1,
                                     Matrix<T> *matrix2,
                                     Func operation) {
    const int n = matrix1->getN(), m = matrix1->getM();
    Matrix<T> *result = new Matrix<T>(n, m);
    std::chrono::milliseconds startTime = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch());
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            result->getElements()[i][j] = operation(matrix1->getElements()[i][j], matrix2->getElements()[i][j]);
        }
    }
    std::chrono::milliseconds endTime = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch());
    std::ofstream out(sequentialElementsStatistics, std::ios_base::app);
    std::chrono::milliseconds time = endTime - startTime;
    out << "Dimensiune matrice: " << matrix1->getN() << "x" << matrix1->getM()
        << " | Nr. threads: 1 | Timp de executie: " << time.count() << std::endl;
    out.close();
    return result;
}

ОБНОВЛЕНИЕ Это результат при использовании Very Sleepy Для анализа выполнения: enter image description here

Класс ComplexNumber:

   class ComplexNumber {
    private:
        double realComponent;
        double imaginaryComponent;

    public:

        ComplexNumber() {}

        ComplexNumber(const ComplexNumber &complexNumber);

        double getRealComponent() const;

        ComplexNumber(double realComponent, double imaginaryComponent);

        void setRealComponent(double realComponent);

        double getImaginaryComponent() const;

        void setImaginaryComponent(double imaginaryComponent);

        friend std::ostream &operator<<(std::ostream &os, const ComplexNumber &complexNumber);
    };

and the definition:

double ComplexNumber::getRealComponent() const {
    return realComponent;
}

void ComplexNumber::setRealComponent(double realComponent) {
    ComplexNumber::realComponent = realComponent;
}

double ComplexNumber::getImaginaryComponent() const {
    return imaginaryComponent;
}

void ComplexNumber::setImaginaryComponent(double imaginaryComponent) {
    ComplexNumber::imaginaryComponent = imaginaryComponent;
}

ComplexNumber::ComplexNumber(double realComponent, double imaginaryComponent) : realComponent(realComponent),
                                                                                imaginaryComponent(imaginaryComponent) {

}

ComplexNumber::ComplexNumber(const ComplexNumber &complexNumber) {
    this->imaginaryComponent = complexNumber.imaginaryComponent;
    this->realComponent = complexNumber.realComponent;
}

std::ostream &operator<<(std::ostream &os, const ComplexNumber &complexNumber) {
    if (complexNumber.imaginaryComponent == 0) {
        os << std::to_string(complexNumber.realComponent);
    } else if (complexNumber.realComponent == 0) {
        os << std::to_string(complexNumber.imaginaryComponent) + "i";
    } else
        os << std::to_string(complexNumber.realComponent) + ((complexNumber.imaginaryComponent < 0) ?
                                                             ("-" + std::to_string(complexNumber.imaginaryComponent) +
                                                              "i") :
                                                             ("+" + std::to_string(complexNumber.imaginaryComponent) +
                                                              "i"));
    return os;
}

Решено

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

1 Ответ

0 голосов
/ 22 октября 2018

Перепишите это:

struct ComplexNumber {
    double real; // *maybe* = 0
    double imaginary; // *maybe* = 0

    ComplexNumber( double r, double i ):real(r), imaginary(i) {}

    ComplexNumber() = default;
    ComplexNumber(const ComplexNumber &complexNumber) = default;
    ComplexNumber& operator=(const ComplexNumber &complexNumber) = default;

};
std::ostream &operator<<(std::ostream &os, const ComplexNumber &complexNumber);

<< может быть медленным и не должно быть другом.Прекратите использовать средства доступа (особенно не встроенные) для доступа к вашим полям.

Если вам действительно нужны средства доступа, по крайней мере, сделайте их встроенными и в заголовке.Но здесь они бессмысленны.

Я бы тоже написал operator+ и тому подобное, даже если бы они мне не были нужны, потому что почему бы и нет.

struct ComplexNumber {
    double real; // *maybe* = 0
    double imaginary; // *maybe* = 0

    ComplexNumber( double r, double i ):real(r), imaginary(i) {}

    ComplexNumber() = default;
    ComplexNumber(const ComplexNumber &complexNumber) = default;
    ComplexNumber& operator=(const ComplexNumber &complexNumber) = default;

  ComplexNumber& operator+=( ComplexNumber const& o )& {
    real += o.real;
    imaginary += o.imaginary;
    return *this;
  }
  ComplexNumber& operator-=( ComplexNumber const& o )& {
    real -= o.real;
    imaginary -= o.imaginary;
    return *this;
  }
  ComplexNumber& operator*=( ComplexNumber const& o )& {
    ComplexNumber r{ real*o.real - imaginary*o.imaginary, real*o.imaginary + imaginary*o.real };
    *this = r;
    return *this;
  }

  friend ComplexNumber operator+( ComplexNumber lhs, ComplexNumber const& rhs ) {
    lhs += rhs;
    return lhs;
  }
  friend ComplexNumber operator-( ComplexNumber lhs, ComplexNumber const& rhs ) {
    lhs -= rhs;
    return lhs;
  }
  friend ComplexNumber operator*( ComplexNumber lhs, ComplexNumber const& rhs ) {
    lhs *= rhs;
    return lhs;
  }
};

, который мертвый мозгОбразец, но я не могу оправдать наличие типа ComplexNumber без по крайней мере тех.(Я пропустил /, потому что остается важное решение о том, как бороться с делением на ноль).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...