Замена чрезвычайно медленной функции pow () - PullRequest
24 голосов
/ 14 февраля 2012

У нас есть решатель CFD, и во время симуляции было установлено, что на некоторых машинах он работает очень медленно, а на других - нет.При использовании Intel VTune было обнаружено, что проблема заключается в следующей строке (в Fortran):

RHOV= RHO_INF*((1.0_wp - COEFF*EXP(F0)))**(1.0_wp/(GAMM - 1.0_wp))

При детализации с помощью VTune проблема была обнаружена на сборочной линии call pow, а при трассировке стекапоказал, что использует __slowpow().После некоторых поисков эта страница обнаружилась с жалобами на то же самое.

На машине с libc версии 2.12 имитация заняла 18 секунд.На машине с libc версии 2.14 моделирование заняло 0 секунд.

Основываясь на информации на вышеупомянутой странице, проблема возникает, когда основание для pow() близко к 1,0.Таким образом, мы сделали еще один простой тест, в котором мы масштабировали базу по произвольному числу до pow(), а затем делили на число, возведенное в показатель степени после вызова pow().Это снизило время выполнения с 18 секунд до 0 секунд с libc 2.12.

Однако нецелесообразно помещать это во весь код, где мы делаем a**b.Как можно было бы заменить функцию pow() в libc?Например, я хотел бы, чтобы сборочная линия call pow, сгенерированная компилятором Fortran, вызывала пользовательскую функцию pow(), которую мы пишем, которая выполняет масштабирование, вызывает libc pow(), а затем делит на масштабирование.Как создать промежуточный слой, прозрачный для компилятора?

Редактировать

Чтобы уточнить, мы ищем что-то вроде (псевдокод):

double pow(a,b) {
   a *= 5.0
   tmp = pow_from_libc(a,b)
   return tmp/pow_from_libc(5.0, b)
}

Можно ли загрузитьpow из libc и переименовать его в нашей пользовательской функции, чтобы избежать конфликтов имен?Если файл customPow.o может переименовать pow из libc, что произойдет, если libc все еще нужен для других целей?Может ли это вызвать конфликт имен между pow в customPow.o и pow в libc?

Ответы [ 4 ]

22 голосов
/ 14 февраля 2012

Хорошо, подожди сейчас. Библиотека не звонит __slowpow() просто чтобы поиграть с вами; он вызывает __slowpow(), потому что полагает, что дополнительная точность необходима для получения точного результата для значений, которые вы ему даете (в этом случае, база очень близка к 1, экспонента порядка 1). Если вы заботитесь о точности этого вычисления, вы должны понять, почему это так и имеет ли это значение, прежде чем пытаться обойти это. Может быть так, что для (скажем) большого отрицательного F0 все это можно безопасно округлить до 1; или нет, в зависимости от того, что будет сделано с этим значением позже. Если вам когда-нибудь понадобится 1.d0 минус этот результат, вам понадобится дополнительная точность.

7 голосов
/ 14 февраля 2012

Просто напишите свою собственную функцию pow, поместите файл .o в статический архив библиотеки libmypow.a где-нибудь в пути к библиотеке компоновщика и передайте -lmypow при компоновке.

3 голосов
/ 14 февраля 2012

pow(a,b) - это то же самое, что и exp(b*ln(a)), возможно, эта замена подойдет вам.

1 голос
/ 14 февраля 2012

Я проверял это сам, и действительно, если я компилирую тестовую программу со страницы, на которую вы ссылаетесь, использует call pow в коде сборки.Тем не менее, при компиляции с оптимизацией -ffast-math нет никакого вызова pow, но результат немного отличается.

...