Mój problem jest dość prosty, chcę użyć lambda w taki sam sposób, w jaki mogę użyć funktora jako "komparatora", pozwól mi wyjaśnić trochę lepiej. Mam dwie duże struktury, obie mają własną implementację operator<
i mam również klasę useless
(jest to po prostu nazwa klasy w kontekście tego pytania), które używają dwóch struktur, wszystko wygląda tak:Używanie lambda zamiast obiektu funkcji, zła wydajność
struct be_less
{
//A lot of stuff
int val;
be_less(int p_v):val(p_v){}
bool operator<(const be_less& p_other) const
{
return val < p_other.val;
}
};
struct be_more
{
//A lot of stuff
int val;
be_more(int p_v):val(p_v){}
bool operator<(const be_more& p_other) const
{
return val > p_other.val;
}
};
class useless
{
priority_queue<be_less> less_q;
priority_queue<be_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
whould chciałbym usunąć powielania w dwóch struct tych, najprostszy pomysł jest, aby struct szablonu i dostarczyć dwa funktor, aby wykonać zadanie porównania:
template<typename Comp>
struct be_all
{
//Lot of stuff, better do not duplicate
int val;
be_all(int p_v):val{p_v}{}
bool operator<(const be_all<Comp>& p_other) const
{
return Comp()(val,p_other.val);
}
};
class comp_less
{
public:
bool operator()(int p_first,
int p_second)
{
return p_first < p_second;
}
};
class comp_more
{
public:
bool operator()(int p_first,
int p_second)
{
return p_first > p_second;
}
};
typedef be_all<comp_less> all_less;
typedef be_all<comp_more> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
tę pracę bardzo dobrze , teraz na pewno nie mam żadnych duplikatów w kodzie struct w cenie dwóch dodatkowych obiektów funkcji. Zauważ, że bardzo upraszczam implementację operator<
, hipotetyczny rzeczywisty kod robi o wiele więcej niż tylko porównywanie dwóch int.
Potem myślał o tym, jak zrobić to samo przy użyciu lambda (podobnie jak w eksperymencie) tylko .Powierzchnia roztwór roboczy byłem w stanie realizować to:
template<typename Comp>
struct be_all
{
int val;
function<bool(int,int)> Comparator;
be_all(Comp p_comp,int p_v):
Comparator(move(p_comp)),
val{p_v}
{}
bool operator<(const be_all& p_other) const
{
return Comparator(val, p_other.val);
}
};
auto be_less = [](int p_first,
int p_second)
{
return p_first < p_second;
};
auto be_more = [](int p_first,
int p_second)
{
return p_first > p_second;
};
typedef be_all<decltype(be_less)> all_less;
typedef be_all<decltype(be_more)> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(be_less,elem);
more_q.emplace(be_more,elem);
}
}
};
Ta implementacja nie tylko dodać nowego członka do danych zawierających strukturę, ale również bardzo słabej wydajności, przygotowałem mały test, w którym tworzę jedną instancję dla wszystkich nieprzydatnych klas, które tu pokazałem, za każdym razem, gdy karmię konstruktora wektorem pełnym 2 milionów liczby całkowite, wyniki są następujące:
- Trwa 48 ms xecute konstruktora pierwszego bezużyteczne klasy
- trwa 228ms aby utworzyć drugą bezużyteczną klasę (funktora)
- Staje 557ms, aby utworzyć trzecią bezużyteczną klasę (lambda)
wyraźnie cenę płaci się za usunięte Powielanie jest bardzo wysokie, a w oryginalnym kodzie powielanie wciąż istnieje. Proszę zwrócić uwagę, jak kiepska jest wydajność trzeciej implementacji, dziesięciokrotnie wolniejsza niż oryginalna, uważam, że trzecia implementacja jest wolniejsza niż druga, z powodu dodatkowego parametru w konstruktorze be_all
... ale:
Właściwie istnieje również czwarty przypadek, gdzie nadal stosowany lambda ale pozbyć się członka Comparator
i dodatkowego parametru w be_all
kod jest następujący:
template<typename Comp>
struct be_all
{
int val;
be_all(int p_v):val{p_v}
{}
bool operator<(const be_all& p_other) const
{
return Comp(val, p_other.val);
}
};
bool be_less = [](int p_first,
int p_second)
{
return p_first < p_second;
};
bool be_more = [](int p_first,
int p_second)
{
return p_first > p_second;
};
typedef be_all<decltype(be_less)> all_less;
typedef be_all<decltype(be_more)> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
Jeśli usunąć auto
od lambda i użyj bool
zamiast kodu zbuduj, nawet jeśli użyję Comp(val, p_other.val)
w operator<
.
Co jest bardzo dziwne dla mnie jest to, że ta czwarta realizacja (lambda bez członu Comparator
) jest jeszcze wolniej niż inne, na końcu średnia wydajność udało mi się zarejestrować, są następujące:
- 48MS
- 228ms
- 557ms
- 698ms
Dlaczego funktora w tym scenariuszu są znacznie szybsze niż lambda? Spodziewałem się, że lambda będzie przynajmniej dobrze grała jako zwykły funktor, czy ktoś z was może skomentować? Czy istnieje jakiś techniczny powód, dla którego czwarta implementacja jest wolniejsza od trzeciej?
PS:
kompilator używam jest g ++ 4.8.2 z -O3. W moim teście i utworzyć dla każdej klasy useless
instancji i za pomocą stopera i uwzględnienia Wymagany czas:
namespace benchmark
{
template<typename T>
long run()
{
auto start=chrono::high_resolution_clock::now();
T t(data::plenty_of_data);
auto stop=chrono::high_resolution_clock::now();
return chrono::duration_cast<chrono::milliseconds>(stop-start).count();
}
}
oraz:
cout<<"Bad code: "<<benchmark::run<bad_code::useless>()<<"ms\n";
cout<<"Bad code2: "<<benchmark::run<bad_code2::useless>()<<"ms\n";
cout<<"Bad code3: "<<benchmark::run<bad_code3::useless>()<<"ms\n";
cout<<"Bad code4: "<<benchmark::run<bad_code4::useless>()<<"ms\n";
Zbiór liczb wejściowych jest taka sama dla wszystkich, plenty_of_data
jest wektorem pełnym 2 milionów intergerów.
Dzięki za poświęcony czas
TL; DR: Porównywalny funktor ma zwykle co najmniej jednego "operator boola" (T, U) "oceniający" na mniej niż " –
Jestem leniwy. Czy możesz opublikować jeden blok kodu, który zawiera 4 zmierzone przypadki, wyraźnie zaznaczone, że mogę wykonać w moim własnym kompilatorze w jednej kopii i wkleić? –