Несколько лет назад я получил задание в школе, где мне пришлось распараллелить Raytracer.
Это было простое задание, и мне очень понравилось работать над ним.
Сегодня я почувствовал, как профилировать raytracer, чтобы увидеть, могу ли я заставить его работать быстрее (без полной перестройки кода). Во время профилирования я заметил что-то интересное:
// Sphere.Intersect
public bool Intersect(Ray ray, Intersection hit)
{
double a = ray.Dir.x * ray.Dir.x +
ray.Dir.y * ray.Dir.y +
ray.Dir.z * ray.Dir.z;
double b = 2 * (ray.Dir.x * (ray.Pos.x - Center.x) +
ray.Dir.y * (ray.Pos.y - Center.y) +
ray.Dir.z * (ray.Pos.z - Center.z));
double c = (ray.Pos.x - Center.x) * (ray.Pos.x - Center.x) +
(ray.Pos.y - Center.y) * (ray.Pos.y - Center.y) +
(ray.Pos.z - Center.z) * (ray.Pos.z - Center.z) - Radius * Radius;
// more stuff here
}
Согласно профилировщику, 25% времени процессора было потрачено на get_Dir
и get_Pos
, поэтому я решил оптимизировать код следующим образом:
// Sphere.Intersect
public bool Intersect(Ray ray, Intersection hit)
{
Vector3d dir = ray.Dir, pos = ray.Pos;
double xDir = dir.x, yDir = dir.y, zDir = dir.z,
xPos = pos.x, yPos = pos.y, zPos = pos.z,
xCen = Center.x, yCen = Center.y, zCen = Center.z;
double a = xDir * xDir +
yDir * yDir +
zDir * zDir;
double b = 2 * (xDir * (xPos - xCen) +
yDir * (yPos - yCen) +
zDir * (zPos - zCen));
double c = (xPos - xCen) * (xPos - xCen) +
(yPos - yCen) * (yPos - yCen) +
(zPos - zCen) * (zPos - zCen) - Radius * Radius;
// more stuff here
}
С поразительными результатами.
В исходном коде запуск raytracer с его аргументами по умолчанию (создание изображения 1024x1024 с прямой молнией и без AA) займет ~ 88 секунд.
В модифицированном коде то же самое займет немного меньше 60 секунд.
Я добился ускорения ~ 1.5 с этой небольшой модификацией кода.
Сначала я подумал, что геттер для Ray.Dir
и Ray.Pos
делает некоторые вещи за сценой, что замедлит работу программы.
Вот геттеры для обоих:
public Vector3d Pos
{
get { return _pos; }
}
public Vector3d Dir
{
get { return _dir; }
}
Итак, оба возвращают Vector3D и что он.
Мне действительно интересно, как вызов getter займет гораздо больше времени, чем прямой доступ к переменной.
Это из-за переменных кэширования процессора? Или, может быть, накладные расходы от вызова этих методов неоднократно складывались? Или, может быть, JIT обрабатывает последний случай лучше, чем первый? Или, может быть, что-то еще я не вижу?
Любые идеи были бы оценены.
Изменить:
Как предположил @MatthewWatson, я использовал строку StopWatch
для выпуска времени вне отладчика. Чтобы избавиться от шума, я несколько раз запускал тесты. В результате прежний код занимает ~ 21 секунд (между 20.7 и 20.9), а последний - ~ 19 секунд (между 19 и 19.2).
Разница стала незначительной, но она все еще существует.