Как BLAS получает такую экстремальную производительность?

Отмена слияния с мерзавцем так легка, Вы не должны даже волноваться о пробном прогоне:

$ git pull $REMOTE $BRANCH
# uh oh, that wasn't right
$ git reset --hard ORIG_HEAD
# all is right with the world

РЕДАКТИРОВАНИЕ: Как отмечено в комментариях ниже, если у Вас есть изменения в Вашем рабочем каталоге или районе сосредоточения войск, Вы, вероятно, захотите спрятать их прежде, чем сделать вышеупомянутое (иначе, они исчезнут после git reset выше)

98
задан DeusAduro 19 August 2009 в 23:30
поделиться

4 ответа

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

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

Ваш ЦП выполняет 3-4 инструкции за цикл, и если используются блоки SIMD, каждая инструкция обрабатывает 4 числа с плавающей запятой или 2 двойных. (конечно, это число тоже неточно, поскольку ЦП обычно может обрабатывать только одну инструкцию SIMD за цикл)

В-третьих, ваш код далек от оптимального:

  • Вы используете необработанные указатели, что означает, что компилятор должен предположить, что они могут быть псевдонимами. Существуют специфичные для компилятора ключевые слова или флаги, которые вы можете указать, чтобы сообщить компилятору, что они не являются псевдонимами. В качестве альтернативы вы должны использовать другие типы, а не исходные указатели, которые решают проблему.
  • You ' повторная обработка кеша, выполняя простой обход каждой строки / столбца входных матриц. Вы можете использовать блокировку, чтобы выполнить как можно больше работы с меньшим блоком матрицы, который умещается в кэше ЦП, прежде чем переходить к следующему блоку.
  • Для чисто числовых задач Fortran в значительной степени непобедим, а C ++ требуется много уговоров, чтобы набрать такую ​​же скорость. Это возможно, и есть несколько библиотек, демонстрирующих это (обычно с использованием шаблонов выражений), но это нетривиально, и это не только .
14
ответ дан 24 November 2019 в 05:16
поделиться

Я не знаю конкретно о реализации BLAS, но есть более эффективные алгоритмы для умножения матриц, которые имеют сложность выше O (n3). Хорошо известным является Алгоритм Штрассена

11
ответ дан 24 November 2019 в 05:16
поделиться

Это реальная скорость. Для примера того, что можно сделать с помощью ассемблера SIMD поверх кода C ++, см. Пример Матричные функции iPhone - они были более чем в 8 раз быстрее, чем версия C, и даже не «оптимизированная» сборка - нет конвейерная обработка еще и есть ненужные операции со стеком.

Также ваш код не « ограничивает правильное » - как компилятор узнает, что когда он изменяет C, он не изменяет A и B?

3
ответ дан 24 November 2019 в 05:16
поделиться

По многим причинам.

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

Другое дело, что Fortran хранит данные по столбцам, а C хранит данные по строкам. Я не проверял ваш код, но будьте осторожны с тем, как вы выполняете продукт. В C вы должны сканировать по строкам: таким образом вы сканируете свой массив по непрерывной памяти, уменьшая промахи кеша. Промахи в кэше - это первая причина неэффективности.

В-третьих, это зависит от используемой вами реализации blas. Некоторые реализации могут быть написаны на ассемблере и оптимизированы для конкретного процессора, который вы используете. Версия netlib написана на fortran 77.

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

Например, вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, вы сэкономите что-то.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм. Есть алгоритмы, которые масштабируются намного лучше .

Некоторые реализации могут быть написаны на ассемблере и оптимизированы для конкретного процессора, который вы используете. Версия netlib написана на fortran 77.

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

Например, вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, вы сэкономите что-то.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм. Есть алгоритмы, которые масштабируются намного лучше .

Некоторые реализации могут быть написаны на ассемблере и оптимизированы для конкретного процессора, который вы используете. Версия netlib написана на fortran 77.

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

Например, вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, вы сэкономите что-то.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм. Есть алгоритмы, которые масштабируются намного лучше .

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

Например, вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, вы сэкономите что-то.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм. Есть алгоритмы, которые масштабируются намного лучше .

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

Например, вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, вы сэкономите что-то.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм. Есть алгоритмы, которые масштабируются намного лучше .

вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, что вы что-то сэкономите.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм . Есть алгоритмы, которые масштабируются намного лучше .

вы можете переделать свой код таким образом

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Попробуйте, я уверен, что вы что-то сэкономите.

На ваш вопрос №1 причина в том, что умножение матриц масштабируется как O (n ^ 3), если вы используете тривиальный алгоритм . Есть алгоритмы, которые масштабируются намного лучше .

-24
ответ дан 24 November 2019 в 05:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: