Научное программное обеспечение мало чем отличается от другого программного обеспечения в том, что касается того, как узнать, что нужно настраивать.
Метод, который я использую, является случайной паузой . Вот некоторые из ускорений, которые он нашел для меня:
Если большая часть времени тратится на функции, подобные log
и exp
, я могу видеть, каковы аргументы этих функций в зависимости от точек, из которых они вызываются. Часто их вызывают неоднократно с одним и тем же аргументом. Если это так, то запоминание создает огромный фактор ускорения.
Если я использую функции BLAS или LAPACK, я могу обнаружить, что большая часть времени затрачивается на подпрограммы для копирования массивов, умножения матриц, преобразования Холески и т. Д.
Процедура копирования массивов предназначена не для скорости, а для удобства. Вы можете найти менее удобный, но более быстрый способ сделать это.
Процедуры умножения или инвертирования матриц или преобразования Холески, как правило, имеют символьные аргументы, задающие параметры, такие как «U» или «L» для верхнего или нижнего треугольника. Опять же, это для удобства. Я обнаружил, что, поскольку мои матрицы были не очень большими, подпрограммы тратили более половины своего времени на вызов подпрограммы для сравнения символов только для расшифровки опций. Написание специализированных версий самых дорогостоящих математических программ привело к значительному ускорению.
Если я могу просто расширить последнее: подпрограмма умножения матриц DGEMM вызывает LSAME для декодирования своих символьных аргументов. Если посмотреть на процентные показатели по времени (единственная статистика, на которую стоит обратить внимание), профилировщики, считающиеся «хорошими», могут показать, что DGEMM использует некоторый процент от общего времени, например, 80%, и LSAME, используя некоторый процент общего времени, например, 50%. Глядя на первое, вы испытаете искушение сказать: «Что ж, это должно быть сильно оптимизировано, поэтому я мало что могу с этим поделать». Глядя на последнее, у вас возникнет соблазн сказать: «А? О чем это все? Это просто крошечная рутина. Этот профилировщик должен ошибаться!»
Это не так, это просто не говорит вам, что вам нужно знать. Случайная пауза показывает, что DGEMM находится на 80% выборок стека, а LSAME - на 50%. (Вам не нужно много образцов, чтобы обнаружить это. Обычно 10 достаточно.) Более того, во многих из этих примеров DGEMM находится в процессе вызова LSAME из пары разных строк кода.
Итак, теперь вы знаете, почему обе процедуры занимают так много времени. Вы также знаете, откуда в вашем коде они вызываются, чтобы провести все это время. Вот почему я использую случайную паузу и скептически отношусь к профилировщикам, независимо от того, насколько они хороши. Они больше заинтересованы в проведении измерений, чем в том, чтобы рассказать вам, что происходит.
Легко предположить, что подпрограммы математической библиотеки были оптимизированы до n-й степени, но на самом деле они оптимизированы для использования в широком диапазоне целей. Вам нужно увидеть, что на самом деле происходит, а не то, что легко предположить.
ДОБАВЛЕНО: Итак, чтобы ответить на два последних вопроса:
Какие самые важные вещи попробовать в первую очередь?
Возьмите 10-20 образцов стеков, и не просто суммируйте их, поймите, что каждый говорит вам. Сделайте это первым, последним и промежуточным. (Нет "попробовать", молодой Скайуокер.)
Как я могу узнать, сколько производительности я могу получить?
Образцы стека дадут вам очень приблизительную оценку того, какая доля времени будет сохранена. (Он следует за распределением , где - это количество выборок, которые отображают то, что вы собираетесь исправить, а - общее количество выборок. стоимость кода, который вы использовали для его замены, который, будем надеяться, будет небольшим.) Тогда коэффициент ускорения будет который может быть большим. Обратите внимание, как это ведет себя математически. Если и , среднее значение и мода
равны 0,5 для коэффициента ускорения 2. Вот распределение: если вы не склонны к риску, то да, вероятность мала (0,03%) этоИксβ( s + 1 , ( n - s ) + 1 )sN1 / ( 1 - х )п = 10с = 5Икс
Икс меньше 0,1, для ускорения менее 11%. Но балансировка - это равная вероятность того, что больше 0,9, а коэффициент ускорения больше 10! Если вы получаете деньги пропорционально скорости программы, это неплохо.Икс
Как я уже говорил вам ранее, вы можете повторить всю процедуру, пока не сможете больше, и составной коэффициент ускорения может быть довольно большим.
ДОБАВЛЕНО: В ответ на беспокойство Педро по поводу ложных срабатываний, позвольте мне попытаться построить пример, где они могут произойти. Мы никогда не решаем потенциальную проблему, если не видим ее два или более раз, поэтому мы можем ожидать ложных срабатываний, когда видим проблему как можно меньше раз, особенно когда общее количество выборок велико. Предположим, мы берем 20 образцов и видим его дважды. То есть его стоимость составляет 10% от общего времени выполнения, способа его распространения. (Среднее значение распределения выше - оно составляет .) Нижняя кривая на следующем графике - это распределение:( С + 1 ) / ( п + 2 ) = 3 / 22 = 13.6 %
Подумайте, взяли ли мы целых 40 образцов (больше, чем я когда-либо имел) и увидели проблему только в двух из них. Расчетная стоимость (режим) этой проблемы составляет 5%, как показано на более высокой кривой.
Что такое «ложное срабатывание»? Дело в том, что если вы решаете проблему, вы получаете такой меньший выигрыш, чем ожидалось, и вы сожалеете, что исправили ее. Кривые показывают (если проблема «мала»), что, хотя коэффициент усиления может быть меньше доли образцов, показывающих его, в среднем он будет больше.
Существует гораздо более серьезный риск - «ложный негатив». Это когда есть проблема, но она не найдена. (Этому способствует «предвзятость подтверждения», когда отсутствие доказательств обычно рассматривается как доказательство отсутствия.)
Что вы получаете с помощью профилировщика (хорошего), так это то, что вы получаете гораздо более точное измерение (таким образом, меньше вероятность ложных срабатываний), за счет гораздо менее точной информации о том, что на самом деле является проблемой (таким образом, меньше шансов найти и получить ее). любой выигрыш). Это ограничивает общее ускорение, которое может быть достигнуто.
Я хотел бы призвать пользователей профилировщиков сообщать о факторах ускорения, которые они фактически получают на практике.
Есть еще один момент, который нужно сделать повторно. Вопрос Педро о ложных срабатываниях.
Он упомянул, что могут возникнуть трудности при рассмотрении небольших проблем в высоко оптимизированном коде. (Для меня небольшая проблема - это та, которая составляет 5% или менее от общего времени.)
Поскольку вполне возможно построить программу, которая является полностью оптимальной, за исключением 5%, этот вопрос может быть решен только эмпирически, как в этом ответе . Чтобы обобщить из эмпирического опыта, это выглядит так:
Программа, как написано, обычно содержит несколько возможностей для оптимизации. (Мы можем назвать их «проблемами», но они часто представляют собой совершенно хороший код, просто способный к значительному улучшению.) Эта диаграмма иллюстрирует искусственную программу, занимающую некоторое время (скажем, 100 с), и она содержит проблемы A, B, C, ... что при обнаружении и исправлении сэкономите 30%, 21% и т. д. от первоначальных 100-х.
Обратите внимание, что проблема F стоит 5% от первоначального времени, поэтому она «маленькая», и ее трудно найти без 40 или более выборок.
Тем не менее, первые 10 выборок легко обнаруживают проблему А. ** Когда это исправлено, программе требуется всего 70 секунд для ускорения 100/70 = 1,43x. Это не только ускоряет выполнение программы, но и увеличивает пропорционально процентное соотношение оставшихся проблем. Например, для задачи B первоначально потребовалось 21 с, что составляло 21% от общего числа, но после удаления A, B отнимает 21 с из 70 с, или 30%, поэтому легче определить, когда весь процесс повторяется.
После того, как процесс повторяется пять раз, теперь время выполнения составляет 16,8 с, из которых проблема F составляет 30%, а не 5%, так что 10 выборок легко это находят.
В этом все дело. Опытным путем программы содержат ряд проблем, имеющих распределение размеров, и любая найденная и исправленная проблема облегчает поиск оставшихся. Чтобы достичь этого, ни одна из проблем не может быть пропущена, потому что, если они есть, они сидят там, занимая время, ограничивая общее ускорение и не увеличивая оставшиеся проблемы.
Вот почему очень важно найти проблемы, которые скрываются .
Если проблемы с A по F обнаружены и устранены, ускорение составляет 100 / 11,8 = 8,5x. Если один из них пропущен, например, D, то ускорение составляет всего 100 / (11,8 + 10,3) = 4,5x.
Это цена, заплаченная за ложные негативы.
Итак, когда профилировщик говорит, что «здесь, кажется, нет существенной проблемы» (т.е. хороший кодер, это практически оптимальный код), возможно, это правильно, а может и нет. ( Ложный минус .) Вы не знаете наверняка, есть ли еще проблемы, которые нужно исправить, для более быстрого ускорения, если только вы не попробуете другой метод профилирования и не обнаружите, что они есть. По моему опыту, метод профилирования не требует большого количества обобщенных выборок, но небольшого числа выборок, где каждая выборка понимается достаточно тщательно, чтобы распознать любую возможность для оптимизации.
** Требуется минимум 2 попадания по задаче, чтобы найти ее, если не известно, что существует (почти) бесконечный цикл. (Красные галочки представляют 10 случайных образцов); Среднее количество выборок, необходимых для получения 2 или более совпадений, когда проблема составляет 30%, составляет ( отрицательное биномиальное распределение ). 10 образцов находят его с вероятностью 85%, 20 образцов - 99,2% ( биномиальное распределение ). Для того, чтобы получить вероятность обнаружения проблемы, в R, оценить , например: .2 / 0,3 = 6,671 - pbinom(1, numberOfSamples, sizeOfProblem)
1 - pbinom(1, 20, 0.3) = 0.9923627
ДОБАВЛЕНО: доля сэкономленного времени соответствует бета-распределению , где - количество выборок, а - число, отображающее проблему. Однако коэффициент ускорения равен (при условии, что все сохранены), и было бы интересно понять распределение . Оказывается, следует за дистрибутивом BetaPrime . Я смоделировал это с 2 миллионами образцов, получая это поведение:β ( s + 1 , ( n - s ) + 1 ) n s y 1 / ( 1 - x ) x y y - 1Иксβ( s + 1 , ( n - s ) + 1 )NsY1 / ( 1 - х )ИксYY- 1
distribution of speedup
ratio y
s, n 5%-ile 95%-ile mean
2, 2 1.58 59.30 32.36
2, 3 1.33 10.25 4.00
2, 4 1.23 5.28 2.50
2, 5 1.18 3.69 2.00
2,10 1.09 1.89 1.37
2,20 1.04 1.37 1.17
2,40 1.02 1.17 1.08
3, 3 1.90 78.34 42.94
3, 4 1.52 13.10 5.00
3, 5 1.37 6.53 3.00
3,10 1.16 2.29 1.57
3,20 1.07 1.49 1.24
3,40 1.04 1.22 1.11
4, 4 2.22 98.02 52.36
4, 5 1.72 15.95 6.00
4,10 1.25 2.86 1.83
4,20 1.11 1.62 1.31
4,40 1.05 1.26 1.14
5, 5 2.54 117.27 64.29
5,10 1.37 3.69 2.20
5,20 1.15 1.78 1.40
5,40 1.07 1.31 1.17
Первые два столбца дают 90% доверительный интервал для коэффициента ускорения. Средний коэффициент ускорения равен за исключением случая, когда . В этом случае оно не определено, и, по мере того, как я увеличиваю количество моделируемых значений , эмпирическое среднее значение увеличивается.s = n y( n + 1 ) / ( n - s )s = nY
Это график распределения факторов ускорения и их средних значений для 2 попаданий из 5, 4, 3 и 2 выборок. Например, если взять 3 выборки, и 2 из них являются попаданиями в проблему, и эту проблему можно устранить, средний коэффициент ускорения будет равен 4x. Если 2 попадания видны только в 2 выборках, среднее ускорение не определено - концептуально, поскольку программы с бесконечными циклами существуют с ненулевой вероятностью!