這個問題基于YouTube上的這個視頻,目的是審查這個專案。
在視頻中,主持人在分析專案時發現以下代碼塊是導致性能問題的原因:
std::optional<HitRecord> HittableObjectList::Hit(const Ray &r, float t_min, float t_max) const {
float closest_so_far = t_max;
return std::accumulate(begin(objects), end(objects), std::optional<HitRecord>{},
[&](const auto &temp_value, const auto &object) {
if(auto temp_hit = object -> Hit(r, t_min, closest_so_far); temp_hit) {
closest_so_far = temp_hit.value().t;
return temp_hit;
}
return temp_value;
});
}
我會假設該std::accumulate函式的功能類似于for回圈。對那里的性能不滿意(并且由于某種原因,分析器不會分析 lambda 代碼[可能是一個限制?]),審閱者將代碼更改為:
std::optional<HitRecord> HittableObjectList::Hit(const Ray &r, float t_min, float t_max) const {
float closest_so_far = t_max;
std::optional<HitRecord> record{};
for(size_t i = 0; i < objects.size(); i ) {
const std::shared_ptr<HittableObject> &object = objects[i];
if(auto temp_hit = object -> Hit(r, t_min, closest_so_far); temp_hit) {
closest_so_far = temp_hit.value().t;
record = temp_hit;
}
}
return record;
}
通過此更改,完成時間從 7 分 30 秒變為 22 秒。
我的問題是:
- 兩個代碼塊不是相同的嗎?為什么
std::accumulate這里要給予如此巨額的懲罰? - 如果不使用
autos,而是使用顯式型別,性能會更好嗎?
審稿人確實提到了一些建議,例如由于呼叫量大而避免在此處使用std::optionals 和std:shared_ptrs 并改為在 GPU 上執行此代碼,但現在我只對前面提到的那些點感興趣。
uj5u.com熱心網友回復:
免責宣告:我沒有運行高級測驗,這只是我基于視頻和代碼的分析。
從我在視頻中的分析中看到的,accumulate 中的熱點在這里:
_Val = _Reduce_op(_Val, *_UFirst);
由于_Reduce_op只是我們的 lambda,并且分析表明這個 lambda 不是瓶頸,那么這意味著這里唯一昂貴的操作是復制賦值運算子=。
看著HitRecord:
struct HitRecord {
point3 p;
vec3 normal;
std::shared_ptr<Material> mat_ptr;
float t;
bool front_face;
...
我們看到有很多東西,包括shared_ptr. 如果副本不存在,優化器可能會在不需要時洗掉副本shared_ptr。在熱回圈中復制 ashared_ptr的成本相對較高,因為它涉及原子操作。
請注意,在分析accumulate代碼中,我們看到他們試圖通過引入移動來在 c 20 中解決此問題。
#if _HAS_CXX20
_Val = _Reduce_op(_STD move(_Val), *_UFirst);
#else // ^^^ _HAS_CXX20 ^^^ // vvv !_HAS_CXX20 vvv
_Val = _Reduce_op(_Val, *_UFirst);
#endif // _HAS_CXX20
盡管要使這一舉措起作用,編譯器必須正確使用命名回傳值優化,當函式中有多個回傳時,它并不總是這樣做。您還必須更改 lambda 的簽名,以便它采用值或 r 值而不是參考。從 auto 更改為命名型別不會解決問題。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/377307.html
