Почему MATLAB так быстр в умножении матриц? - PullRequest
175 голосов
/ 19 мая 2011

Я делаю некоторые тесты с CUDA, C ++, C # и Java и использую MATLAB для проверки и генерации матрицы. Но когда я умножаю с MATLAB, 2048x2048 и даже более крупные матрицы почти мгновенно умножаются.

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

Только CUDA конкурентоспособна, но я думал, что по крайней мере C ++ будет несколько ближе и не будет 60x медленнее.

Итак, мой вопрос - как MATLAB делает это так быстро?

C ++ Код:

float temp = 0;
timer.start();
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * matice2[m][k];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

Edit: Я также не знаю, что думать о результатах C #. Алгоритм такой же, как C ++ и Java, но есть гигантский скачок 2048 с 1024?

Edit2: Обновлены MATLAB и 4096x4096 результаты

Ответы [ 12 ]

160 голосов
/ 05 июля 2015

Этот вопрос повторяется и на него следует дать более четкие ответы, чем "Matlab использует высоко оптимизированные библиотеки" или "Matlab использует MKL" один раз в Stackoverflow.

История

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

Я не специалист по истории, но, видимо, тогда все просто переписали его версию на Фортране с помощью простых циклов. Затем пришла некоторая стандартизация с идентификацией «ядер» (базовых процедур), которые необходимы для решения большинства задач линейной алгебры. Эти основные операции были затем стандартизированы в спецификации под названием «Основные подпрограммы линейной алгебры (BLAS)». Инженеры могли бы тогда назвать эти стандартные, хорошо проверенные подпрограммы BLAS в своем коде, делая их работу намного проще.

BLAS:

BLAS эволюционировал от уровня 1 (первая версия, которая определяла скалярно-векторные и векторно-векторные операции) до уровня 2 (операции вектор-матрица) до уровня 3 (операции матрица-матрица) и предоставлял все больше и больше «ядер» так стандартизировано все больше и больше фундаментальных операций линейной алгебры. Оригинальные реализации Fortran 77 все еще доступны на сайте Netlib .

На пути к повышению производительности:

Таким образом, за эти годы (особенно между выпусками BLAS уровня 1 и уровня 2: в начале 80-х) аппаратное обеспечение изменилось с появлением векторных операций и иерархий кэша. Эти изменения позволили существенно повысить производительность подпрограмм BLAS. Затем разные поставщики внедрили свои подпрограммы BLAS, которые становились все более и более эффективными.

Я не знаю всех исторических реализаций (тогда я не был ни ребенком, ни ребенком), но две самые заметные из них появились в начале 2000-х годов: Intel MKL и GotoBLAS. Ваш Matlab использует Intel MKL, который является очень хорошим, оптимизированным BLAS, и это объясняет великолепную производительность, которую вы видите.

Технические подробности по умножению матриц:

Так почему же Matlab (MKL) так быстро работает на dgemm (общее умножение матрицы на матрицу с двойной точностью)? Проще говоря: потому что он использует векторизацию и хорошее кэширование данных. В более сложных терминах: см. Статью , предоставленную Джонатаном Муром.

По сути, когда вы выполняете умножение в предоставленном вами коде C ++, вы совсем не дружественны к кешу. Поскольку я подозреваю, что вы создали массив указателей на массивы строк, ваш доступ во внутреннем цикле к k-му столбцу "matice2": matice2[m][k] очень медленный. Действительно, когда вы обращаетесь к matice2[0][k], вы должны получить k-й элемент массива 0 вашей матрицы. Затем на следующей итерации вы должны получить доступ к matice2[1][k], который является k-м элементом другого массива (массив 1). Затем на следующей итерации вы получаете доступ к еще одному массиву и т. Д. Так как вся матрица matice2 не может поместиться в старшие кеши (размер 8*1024*1024 байт), программа должна извлечь нужный элемент из main память, теряя много времени.

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

timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
    for (int q = 0; q < rozmer; q++)
    {
        tempmat[p][q] = matice2[q][p];
    }
}
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * tempmat[k][m];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

Таким образом, вы можете видеть, как простота размещения кэша значительно увеличила производительность вашего кода.В настоящее время в реальных реализациях dgemm это используется на очень обширном уровне: они выполняют умножение на блоки матрицы, определяемые размером TLB (буфер трансляции с кратким изложением, если коротко: что может эффективно кэшироваться), чтобы они передавали потокдля процессора точно количество данных, которые он может обработать.Другим аспектом является векторизация, они используют векторизованные инструкции процессора для оптимальной пропускной способности команд, чего вы не можете сделать из своего кроссплатформенного кода C ++.

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

82 голосов
/ 19 мая 2011

Вот мои результаты с использованием MATLAB R2011a + Parallel Computing Toolbox на машине с Tesla C2070:

>> A = rand(1024); gA = gpuArray(A);
% warm up by executing the operations a couple of times, and then:
>> tic, C = A * A; toc
Elapsed time is 0.075396 seconds.
>> tic, gC = gA * gA; toc
Elapsed time is 0.008621 seconds.

MATLAB использует высоко оптимизированные библиотеки для умножения матриц, поэтому умножение матриц в обычном MATLAB так быстро. Версия gpuArray использует MAGMA .

Обновление с использованием R2014a на машине с Tesla K20c и новыми функциями timeit и gputimeit:

>> A = rand(1024); gA = gpuArray(A);
>> timeit(@()A*A)
ans =
    0.0324
>> gputimeit(@()gA*gA)
ans =
    0.0022

Обновление с использованием R2018b на машине WIN64 с 16 физическими ядрами и Tesla V100:

>> timeit(@()A*A)
ans =
    0.0229
>> gputimeit(@()gA*gA)
ans =
   4.8019e-04
39 голосов
/ 20 мая 2011

Вот почему .MATLAB не выполняет наивное матричное умножение, зацикливаясь на каждом элементе так, как вы это делали в своем коде C ++.

Конечно, я предполагаю, что вы просто использовали C=A*B вместо того, чтобы писать функцию умножения самостоятельно.

19 голосов
/ 19 мая 2011

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

Вы также можете посмотреть статью Гото и Ван Де Гейна «Анатомия высокопроизводительного матричного умножения» в http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.1785&rep=rep1&type=pdf

9 голосов
/ 07 ноября 2015

Ответ: LAPACK и BLAS библиотеки делают MATLAB ослепительно быстрым при матричных операциях, а не какой-либо проприетарный код от людей из MATLAB.

Используйте библиотеки LAPACK и / или BLAS в своем коде C ++ для матричных операций, и вы должны получить производительность, аналогичную MATLAB. Эти библиотеки должны быть свободно доступны в любой современной системе, а их части были разработаны в течение десятилетий в научных кругах. Обратите внимание, что существует несколько реализаций, включая некоторые закрытые источники, такие как Intel MKL .

Обсуждение того, как BLAS обеспечивает высокую производительность , доступно здесь.


Кстати, я испытываю серьезную боль при вызове библиотек LAPACK непосредственно из c (но оно того стоит). Вам нужно ОЧЕНЬ внимательно прочитать документацию.

9 голосов
/ 04 ноября 2012

При выполнении умножения матриц вы используете простой метод умножения, который занимает время O(n^3).

Существует алгоритм умножения матриц, который принимает O(n^2.4). Это означает, что при n=2000 ваш алгоритм требует ~ в 100 раз больше вычислений, чем лучший алгоритм.
Вы действительно должны проверить страницу википедии для умножения матриц для получения дополнительной информации об эффективных способах ее реализации.

6 голосов
/ 19 мая 2011

Я полагаю, что в зависимости от вашей версии Matlab она уже использует ваш графический процессор.

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

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

3 голосов
/ 10 сентября 2015

MATLAB использует высокооптимизированную реализацию LAPACK от Intel, известную как Intel Math Kernel Library (Intel MKL) - в частности, функция dgemm .Скорость Эта библиотека использует преимущества процессорных функций, включая инструкции SIMD и многоядерные процессоры.Они не документируют, какой конкретный алгоритм они используют.Если бы вы вызывали Intel MKL из C ++, вы должны увидеть аналогичную производительность.

Я не уверен, какую библиотеку MATLAB использует для умножения GPU, но, вероятно, что-то вроде nVidia CUBLAS .

2 голосов
/ 17 января 2019

Поскольку MATLAB - это язык программирования, впервые разработанный для числовой линейной алгебры (матричные манипуляции), в котором есть библиотеки, специально разработанные для умножения матриц.И теперь MATLAB также может использовать для этого дополнительно GPU (графический процессор) .

И если мыпосмотрите на результаты ваших вычислений:

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

, тогда мы увидим, что не только MATLAB очень быстр в умножении матриц: CUDA C (язык программирования от NVIDIA) дает лучшие результаты, чем MATLAB.CUDA C также имеет библиотеки, специально разработанные для умножения матриц, и использует графические процессоры.

Краткая история MATLAB

Клив Молер, председатель отдела компьютерных наукКафедра в Университете Нью-Мексико начала разрабатывать MATLAB в конце 1970-х годов.Он разработал его, чтобы дать своим студентам доступ к LINPACK (программная библиотека для выполнения числовой линейной алгебры) и EISPACK (это программное обеспечениебиблиотека для численного расчета линейной алгебры) без необходимости изучать фортран.Вскоре он распространился на другие университеты и нашел сильную аудиторию в сообществе прикладной математики.Джек Литтл, инженер, подвергся воздействию этого факта во время визита Молера в Стэнфордский университет в 1983 году. Признавая его коммерческий потенциал, он присоединился к Молеру и Стиву Бангерту.Они переписали MATLAB на C и основали MathWorks в 1984 году, чтобы продолжить его развитие.Эти переписанные библиотеки были известны как JACKPAC.В 2000 году MATLAB был переписан для использования более нового набора библиотек для работы с матрицами, LAPACK (стандартная библиотека программного обеспечения для числовой линейной алгебры).

Источник

Что такое CUDA C

CUDA C использует также библиотеки, специально разработанные для умножения матриц, такие как OpenGL (Open Graphics Library).Он также использует GPU и Direct3D (в MS Windows).

Платформа CUDA предназначена для работы с такими языками программирования, как C, C ++ иFortran.Эта доступность облегчает специалистам по параллельному программированию использование ресурсов графического процессора, в отличие от предыдущих API, таких как Direct3D и OpenGL , которыеТребуются продвинутые навыки в графическом программировании.Кроме того, CUDA поддерживает такие среды программирования, как OpenACC и OpenCL .

enter image description here

Пример потока обработки CUDA:

  1. Копирование данных из основной памяти в память графического процессора
  2. CPU запускает вычислительное ядро ​​GPU
  3. Ядра CUDA графического процессора выполняют ядро ​​параллельно
  4. Копируют полученные данные из памяти графического процессора в основную память

Сравнение скоростей выполнения процессора и графического процессора

Мы выполнили тест, в котором мы измерили количество времени, которое потребовалось для выполнения 50 временных шагов для размеров сетки 64, 128, 512, 1024 и 2048 на процессоре Intel Xeon X5650.а затем с помощью графического процессора NVIDIA Tesla C2050.

enter image description here

Для размера сетки 2048 алгоритм показывает уменьшение времени вычислений в 7,5 раз по сравнению с более чемминута на процессоре до менее 10 секунд на графическом процессоре.График масштаба журнала показывает, что ЦП на самом деле быстрее для небольших размеров сетки.Однако по мере развития и развития технологии решения для графических процессоров все в большей степени способны справляться с небольшими проблемами, и эта тенденция будет продолжаться.

Источник

Из введения в Руководство по программированию в CUDA C:

Управляемый ненасытным рыночным спросом на 3D-графику высокой четкости в реальном времени, программируемый графический процессор или графический процессор превратился в высокопараллельный многопоточный многоядерный процессор с огромной вычислительной мощностью и очень высокой пропускной способностью памяти, о чем свидетельствует Figure 1 и Figure 2.

Рисунок 1. операций с плавающей запятой в секунду для CPU и GPU

enter image description here

Рисунок 2 .Пропускная способность памяти для CPU и GPU

enter image description here

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

Рисунок 3 .Графический процессор выделяет больше транзисторов для обработки данных

enter image description here

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

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

Источник

Расширенное чтение


Несколько интересных лиц

Я написал умножение матриц в C ++, которое выполняется так же быстро, как и в Matlab, но потребовалось некоторое внимание.(До этого Matlab использовал для этого графические процессоры).

Цитата из этот ответ .

2 голосов
/ 18 октября 2015

Это медленно в C ++, потому что вы не используете многопоточность.По сути, если A = BC, где все они являются матрицами, первая строка A может быть вычислена независимо от 2-й строки и т. Д. Если A, B и C все n на n матриц, вы можете ускорить умножение намножитель n ^ 2, например

a_ {i, j} = sum_ {k} b_ {i, k} c_ {k, j}

Если вы используете, скажем, Eigen[http://eigen.tuxfamily.org/dox/GettingStarted.html], многопоточность встроена и количество потоков регулируется.

...