Моя программа рассчитывает моделирование по методу Монте-Карло для показателя риска. Чтобы максимально упростить, у меня есть:
1/ simulated daily cashflows
2/ to get a sample of a possible 1-year cashflow,
I need to draw 365 random daily cashflows and sum them
Следовательно, ежедневные денежные потоки являются эмпирически заданной функцией распределения, которая должна быть выбрана 365 раз. Для этого я
1/ sort the daily cashflows into an array called *this->distro*
2/ calculate 365 percentiles corresponding to random probabilities
Мне нужно провести симуляцию годового денежного потока, скажем, 10 тыс. Раз, чтобы получить совокупность симулированных годовых денежных потоков для работы. Подготовив функцию распределения ежедневных денежных потоков, я делаю выборку, как …
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
generatedVal = 0.0;
for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
{
prob = (FLT_TYPE)fastrand(); // prob [0,1]
dIdx = prob * dMaxDistroIndex; // scale prob to distro function size
// to get an index into distro array
_floor = ((FLT_TYPE)(long)dIdx); // fast version of floor
_ceil = _floor + 1.0f; // 'fast' ceil:)
iIdx1 = (unsigned int)( _floor );
iIdx2 = iIdx1 + 1;
// interpolation per se
generatedVal += this->distro[iIdx1]*(_ceil - dIdx );
generatedVal += this->distro[iIdx2]*(dIdx - _floor);
}
this->yearlyCashflows[idxSim] = generatedVal ;
}
Код внутри обоих for
циклы делает линейную интерполяцию. Если, скажем, 1000 долларов США соответствует вероятности = 0,01, 10000 долларов США соответствует вероятности = 0,1, то если у меня нет эмпирического числа для р = 0,05, я хочу получить 5000 долларов США путем интерполяции.
Вопрос: этот код работает правильно, хотя профилировщик говорит, что программа тратит около 60% времени выполнения на интерполяцию как таковую. Итак, мой вопрос, как я могу сделать эту задачу быстрее? Ниже приведены примеры времени выполнения, о которых сообщает VTune для конкретных строк:
prob = (FLT_TYPE)fastrand(); // 0.727s
dIdx = prob * dMaxDistroIndex; // 1.435s
_floor = ((FLT_TYPE)(long)dIdx); // 0.718s
_ceil = _floor + 1.0f; // -
iIdx1 = (unsigned int)( _floor ); // 4.949s
iIdx2 = iIdx1 + 1; // -
// interpolation per se
generatedVal += this->distro[iIdx1]*(_ceil - dIdx ); // -
generatedVal += this->distro[iIdx2]*(dIdx - _floor); // 12.704s
Тире означают, что профилировщик сообщает об отсутствии времени выполнения для этих линий.
Любая подсказка будет принята с благодарностью.
Даниил
РЕДАКТИРОВАТЬ:
И c.fogelklou, и MSalters указали на значительные улучшения. Лучший код в соответствии с тем, что сказал c.fogelklou:
converter = distroDimension / (FLT_TYPE)(RAND_MAX + 1)
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
generatedVal = 0.0;
for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
{
dIdx = (FLT_TYPE)fastrand() * converter;
iIdx1 = (unsigned long)dIdx);
_floor = (FLT_TYPE)iIdx1;
generatedVal += this->distro[iIdx1] + this->diffs[iIdx1] *(dIdx - _floor);
}
}
и лучшее, что у меня есть по линии MSalter это
normalizer = 1.0/(FLT_TYPE)(RAND_MAX + 1);
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
generatedVal = 0.0;
for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
{
dIdx = (FLT_TYPE)fastrand()* normalizer ;
iIdx1 = fastrand() % _g.xDayCount;
generatedVal += this->distro[iIdx1];
generatedVal += this->diffs[iIdx1]*dIdx;
}
}
Второй код ок. На 30 процентов быстрее. Теперь, из 95-х от общего времени выполнения, последняя строка потребляет 68-е. Последняя, кроме одной строки, потребляет всего 3,2 с, следовательно, умножение double * double должно быть дьяволом. Я подумал о SSE — сохранить последние три операнда в массив, а затем выполнить векторное умножение this-> diffs [i] * dIdx [i] и добавить это в this-> distro [i], но этот код выполняется на 50 процентов помедленнее. Следовательно, я думаю, что ударил стену.
Большое спасибо всем.
D.
Это предложение для небольшой оптимизации, устраняющей необходимость в ceil, двух приведениях и одном из умножений. Если вы работаете на процессоре с фиксированной запятой, это объясняет, почему умножение и приведение между float и int занимают так много времени. В этом случае попробуйте использовать оптимизацию с фиксированной запятой или включить с плавающей запятой в вашем компиляторе, если процессор поддерживает это!
for ( unsigned int idxSim = 0; idxSim < _g.xSimulationCount; idxSim++ )
{
generatedVal = 0.0;
for ( register unsigned int idxDay = 0; idxDay < 365; idxDay ++ )
{
prob = (FLT_TYPE)fastrand(); // prob [0,1]
dIdx = prob * dMaxDistroIndex; // scale prob to distro function size
// to get an index into distro array
iIdx1 = (long)dIdx;
_floor = (FLT_TYPE)iIdx1; // fast version of floor
iIdx2 = iIdx1 + 1;
// interpolation per se
{
const FLT_TYPE diff = this->distro[iIdx2] - this->distro[iIdx1];
const FLT_TYPE interp = this->distro[iIdx1] + diff * (dIdx - _floor);
generatedVal += interp;
}
}
this->yearlyCashflows[idxSim] = generatedVal ;
}
Я бы порекомендовал исправить fastrand
, Код с плавающей точкой не самый быстрый в мире, но особенно медленным является переключение между кодом с плавающей точкой и целочисленным кодом. Поскольку вам нужен целочисленный индекс, используйте целочисленную случайную функцию.
Может быть даже полезно предварительно сгенерировать все 365 случайных значений в цикле. Так как вам нужно только log2(dMaxDistroIndex)
биты случайности на значение, вы можете уменьшить количество вызовов RNG.
Впоследствии вы выберете случайное число от 0 до 1 для доли интерполяции.