다음 코드에 표시된 것처럼 여러 원자 루틴 중 하나가 함수에서 호출됩니다 messagePassing
. 어떤 것을 사용할 것인지는 중첩 루프에 들어가기 전에 결정됩니다. 현재 구현에서는 while
런타임 성능을 위해 여러 루프가 사용됩니다. 가독성과 유지 관리를 위해 자신을 반복 (중첩 된 루프에서 공유 작업을 반복)하는 것을 피하고 messagePassingCleanButSlower
.
런타임 성능을 희생하지 않는 접근 방식이 있습니까?
두 가지 시나리오를 처리해야합니다.
#include <vector>
template<typename Uint, typename Real>
class Graph {
public:
void messagePassing(Uint nit, Uint type);
void messagePassingCleanButSlower(Uint nit, Uint type);
private:
struct Vertex {}; // Details are hidden since they are distracting.
std::vector< Vertex > vertices;
void atomicMessagePassingType1(Vertex &v);
void atomicMessagePassingType2(Vertex &v);
void atomicMessagePassingType3(Vertex &v);
// ...
// may have other types
};
template<typename Uint, typename Real>
void
Graph<Uint, Real>::
messagePassing(Uint nit, Uint type)
{
Uint count = 0; // round counter
if (type == 1) {
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
atomicMessagePassingType1(v);
}
}
}
else if (type == 2) {
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
atomicMessagePassingType2(v);
}
}
}
else {
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
atomicMessagePassingType3(v);
}
}
}
}
template<typename Uint, typename Real>
void
Graph<Uint, Real>::
messagePassingCleanButSlower(Uint nit, Uint type)
{
Uint count = 0; // round counter
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
if (type == 1) {
atomicMessagePassingType1(v);
}
else if (type == 2) {
atomicMessagePassingType2(v);
}
else {
atomicMessagePassingType3(v);
}
}
}
}
여기에서 벤치 마크를 참조하십시오.
1. 작업 atomicMessagePassingTypeX
이 정말 짧은 테스트 시나리오를 설정했습니다 (최적화 장벽 만 있음). 나는 대략 100
요소 vertices
와 100
외부의 반복을 선택했습니다 while
. 이러한 조건은 실제 코드에 따라 다를 수 있으므로 내 벤치 마크 결과가 귀하의 사례에 적용되는지 여부는 자신의 코드를 벤치마킹하여 확인해야합니다.
네 가지 테스트 사례는 다음과 같습니다. 두 가지 변형, 다른 답변에서 언급 된 함수 포인터가있는 변형과 다음과 같이 디스패치 람다를 통해 함수 포인터가 호출되는 변형 :
template<typename Uint, typename Real>
void
Graph<Uint, Real>::
messagePassingLambda(Uint nit, Uint type)
{
using ftype = decltype(&Graph::atomicMessagePassingType1);
auto lambda = [&](ftype what_to_call) {
Uint count = 0; // round counter
while (count < nit) {
++count;
// many operations
for (auto &v : vertices) {
// many other operations
(this->*what_to_call)(v);
}
}
};
if(type == 1) lambda(&Graph::atomicMessagePassingType1);
else if(type == 2) lambda(&Graph::atomicMessagePassingType2);
else lambda(&Graph::atomicMessagePassingType3);
}
GCC 9.1 / Clang 8.0 및 O2 / O3의 모든 조합을 사용해보십시오. O3에서 두 컴파일러 모두 "느린"변형에 대해 거의 동일한 성능을 제공한다는 것을 알 수 있습니다. GCC의 경우 실제로 최고입니다. 컴파일러는 적어도 내부 루프에서 if
/ else
문을 끌어 올린 다음 나에게 완전히 명확하지 않은 어떤 이유로 GCC는 첫 번째 변형과 다르게 내부 루프의 명령을 재정렬하므로 결과적으로 조금 더 빠릅니다.
함수 포인터 변형은 지속적으로 가장 느립니다.
람다 변형은 성능면에서 첫 번째 변형과 사실상 동일합니다. 람다가 인라인되면 본질적으로 동일한 이유가 분명하다고 생각합니다.
인라인되지 않은 경우 간접 호출로 인해 상당한 성능 저하가 발생할 수 있습니다 what_to_call
. 다음의 각 호출 사이트에서 적절한 직접 호출을 사용하여 다른 유형을 강제하면이를 방지 할 수 있습니다 lambda
.
C ++ 14 이상에서는 일반적인 람다를 만들 수 있습니다.
auto lambda = [&](auto what_to_call) {
호출 양식 (this->*what_to_call)(v);
을 조정하고 what_to_call();
다른 람다로 호출하십시오.
lambda([&](){ atomicMessagePassingType1(v); });
컴파일러가 디스패치 당 하나의 함수를 인스턴스화하도록 강제하고 잠재적 인 간접 호출을 제거해야합니다.
C ++ 11에서는 일반 람다 또는 변수 템플릿을 만들 수 없으므로 보조 람다를 인수로 사용하는 실제 함수 템플릿을 작성해야합니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다