Функция MATLAB медленная в первый раз, но гораздо быстрее впоследствии. Почему?

У меня есть большой файл функций MATLAB. Сначала он создает нулевую матрицу, затем обновляет около 70% ячеек, оценивая ряд соответствующих (длинных) алгебраических выражений, которые жестко закодированы в функции. После этого возвращается числовая матрица.

The .M файл около 4 MB большой (у меня есть 100 из этих m. файлы, но это не имеет прямого отношения). Когда я оцениваю функцию в первый раз, для оценки требуется около 9 секунд. Последующие запуски, однако, только около 0,1 секунды, что больше, чем я ожидал.

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

m. файл можно найти по ссылке ниже public (вы можете скопировать текст из браузера): https://dl.dropboxusercontent.com/u/157153767/K_a_12_102x.m

ввод командного окна вы должны использовать это: [test]=K_a_12_102x(414000000,1.1095 e+09,1.2500 e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397 e+08,8.9930 e+07,0,3.0397 e+08,0,1.0702 e+08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06)

3 ответов


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

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

чтобы ускорить все это: перенесите код на C, C# или что-то подобное и включите его в качестве DLL-файла. У вас будут постоянные и быстрые вычисления, но вы не сможете изменить их так же легко.

(используя DLL-файл C#, у вас также есть компиляция JIT, но это реже и, вероятно, все еще быстрее, чем MATLAB.)


Я сделал некоторое кодирование и портировал код на C#. Оригинальные тайминги являются 13.4 s и 0.15 s (первый/второй запуск)

простой порт, релиз конфигурация:

>> test_1
Elapsed time is 124.7 seconds.
>> test_1
Elapsed time is 0.0297 seconds.

таким образом, первый запуск намного хуже, чем MATLAB - bummer. Хотя я люблю C# как есть, это может быть не лучший инструмент для этой работы. (Кстати: мне пришлось использовать компилятор командной строки, потому что Visual Studio 2010 продолжал сбой...)


медленные первые запуски были задолго до того, как компилятор JIT был введен в MATLAB, и true даже для случая файлов MEX, на котором JIT-компилятор не применяется. Когда вы запускаете код в первый раз, MATLAB должен загрузить его с диска, проанализировать код (см. подробности анализа типа среды выполнения ниже) и применить компиляцию JIT, если это a .m-файл. Затем при выполнении выделяется пространство для данных, и инструкции загружаются в кэш процессора, где они могут оставаться с очень быстрое время доступа для дальнейших казней. Это причина вездесущих процедур "разогрева кэша" за пределами мира MATLAB, как я понимаю (извинения аппаратным баффам за мое размахивание руками). Однако, С.m fies, доступ к диску, вероятно, является большим фактором, даже с файлами, которые намного меньше ,чем" около 4 МБ", как в вашем случае. Существует также дополнительный шаг функция disambiguation, когда несколько функций имеют одно и то же имя.

чтобы увидеть это происходит для файла MEX, просто запустите clear mex и время вызова функции. MATLAB должен загрузить его с диска и в память снова и снова, вероятно, с недействительным кэшем процессора.

анализ типа времени выполнения

второй аспект функций ускорения кода (генерация кода JIT является первым), является анализ типа времени выполнения. Из старого MathWork документа:

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

вы можете рассмотреть эту часть процесса компиляции JIT, и это так. Но дело в том, что этот анализ выполняется при первом выполнении, независимо от того, решает ли ускоритель JIT-компиляцию каких-либо строк кода. Кстати, весь файл не компилируется в машинный код. Раньше можно было видеть какие линии ускорились в профилировщик с помощью setpref('profiler','showJitLines',1); но это, к сожалению, было удалено как функция.

в любом случае, после на самом деле, глядя на ваш код, существует ошеломляющее количество констант и переменных, которые необходимо проанализировать после загрузки файла с диска. Одна строка имеет длину более 31 000 символов с несколькими тысячами числовых литералов! Это много, чтобы проанализировать и решить, что нуждается в компиляции и что может быть кэшировано между запусками. Как бы демонстрируя этот момент, просто просматривая (не запуск) вашему коду удалось разбить редактор с помощью DirectUI::DUIXmlParser::InitializeParserFromXmlLiteReader на трассировке стека. Фу, какой гадкий код!

генерирует ли JIT-компилятор код для этой функции?

давайте время код с функциями ускорения MATLAB включен. Мы также запускаем контрольный тест, который мы знаем, чтобы работать около 8x медленнее без ускорения.

>> feature accel on
>> clear K_a_12_102x
>> x = rand(1000); tic,for t=1:1e6, x=x; end,toc % control
Elapsed time is 0.083878 seconds.
% do first-run of K_a_12_102x, took 13.280327 sec
>> tic; [test]=K_a_12_102x(414000000,1.1095e+09,1.2500e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397e+08,8.9930e+07,0,3.0397e+08,0,1.0702e+08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06); toc
Elapsed time is 0.151804 seconds.

теперь мы выключаем ускорение и запускаем те же тесты:

>> feature accel off
>> clear K_a_12_102x
>> tic,for t=1:1e6, x=x; end,toc % control
Elapsed time is 0.630039 seconds.
% do a first-run of K_a_12_102x, took 15.634775 seconds
>> tic; [test]=K_a_12_102x(414000000,1.1095e+09,1.2500e-04,0.0840,0.0840,0.0240,0.0240,0.0020,0.0020,0,0,0,0,3.0397e+08,8.9930e+07,0,3.0397e+08,0,1.0702e+08,0,0,0,0,0,0,497.7389,80.7355,-15.9811,391.1985,-15.9811,103.5248,20440000,0,20440000,0.06); toc
Elapsed time is 0.159683 seconds.

выводы

в выводы двоякие:

  1. время первого запуска не улучшается при отключенном ускорении (JIT) (13.28 sec с JIT on против 15.63 sec с JIT off).
  2. последующие запуски показывают, что машинный код не генерируется, когда JIT включен (0.1518 сек с JIT ON против 0.1597 сек с JIT off)

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

остается вопрос, что вызывает медленное время первого запуска? Некоторые возможности: загрузка текста кода с диска, извлечение (удаление комментариев и пробелов) код перед сохранением его в памяти, а не повторное использование переменной инициализации спасли от предыдущих запусков, может быть, ядро Матлаб инструкции, используемые функции, сохраненные в кэше процессора, и любой не-JIT-компилятором кода, анализа, необходимые для MATLAB, чтобы сделать во время выполнения проверки синтаксиса. Тот факт, что файл 4MB и невероятно сложный с точки зрения длины уравнения и количества числовых литералов предполагает, что его не кэш процессора, а начальная загрузка файла и анализ кода.


В дополнение к эффекту компилятора JIT, упомянутого TheCrumbMonster, могут быть различные кэширование эффекты происходит. Либо Matlab достаточно умен, чтобы повторно использовать некоторые из своих структур данных, либо у него есть некоторый код, уже хранящийся в кэш-памяти процессора, а не в основной памяти. На самом деле даже сам JIT полагается на кэширование результата компиляции, иначе вам пришлось бы перекомпилировать каждый раз, когда вы вызываете функцию. Также все современные операционные системы выполняют различные виды кэширования, поэтому вместо чтения некоторого файла данных, файла MEX или DLL с диска вы просто получаете его из памяти.

Это одна из причин того, что для точного измерения скорости выполнения какой-либо функции, вы не должны использовать простой tic(); toc() операторы, но используйте такую функцию, как timeit (используйте его, это отлично!). Это повторяет измерение несколько раз, чтобы "разогреть" кэш и всегда выбрасывает первые несколько измерения.

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

K_a_12=zeros(1089,100);
K_a_12(1011,1) = 2*h_a*((x*(250*G_a*L2^20*W + 5250*G_a*L1^2*L2^18*W + ...
K_a_12(1011,3) = 2*h_a*((x^13*(188955000*G_a*L2^8*W*h_1 +  ...

которые (я надеюсь) автоматически. Мне кажется, что расчет можно сделать гораздо эффективнее. Прежде всего, вы, похоже, заполняете только очень небольшую часть своей матрицы, поэтому вам, вероятно, следует использовать разреженные матрицы. Далее, от быстрого осмотра, каждый термин кажется формы h_a * x^n1 * const * L1^n2 * L2^n3 * ...., который может быть рассчитан упрощенным способом. Я бы поспорил, что весь расчет может быть выполнен в нескольких строках путем умножения и возведения в степень всего нескольких матриц, которые должны быть сохранены как mat-файлы, а не как расчет, написанный полностью. Наконец, вы также не используете около половины входных аргументов функции.