2 분 소요

C++ 코딩 알쓸신잡

최근에 online pose graph optimization 모듈을 만들다 보니, 불가피하게 for문을 많이 사용할 수 밖에 없었다.

Online이다 보니 real time이 중요해서 좀더 빠르게 for 문을 만들 수는 없나? 하다가 병렬 프로그래밍에 대해 알게되었다.

TBB, OpenMP 라이브러리와 C++17 STLfor_each는 어떻게 사용하고 언제 뭘 쓰면 좋은가?

나도 잘 모르는 내용들이라 잘못된 내용이 있을 수 있다…

찾아본 결과, 단순한 반복문이라면 for_each, 복잡한 작업이 필요하지만 thread scheduling을 자동으로 해주길 원한다면 TBB, 복잡한 작업이 필요하고 직접 thread 및 작업 제어를 원한다면 OpenMP를 사용하는 것이 낫다고 한다.

OpenMP

#pragma omp parallel
{
    // 중괄호에 묶인 구문만 병렬로 실행
    // 여기에 원하는 for문을 넣으면 된다. (아래도 동일)
}

// thread 수를 원하는 만큼 지정해주는 경우
#pragma omp parallel num_threads(n)

// 공유 변수 접근 시
#pragma omp atomic

// atomic에서 지원하지 않는 연산을 사용하는 경우
#pragma omp critical
  • Example
#pragma omp parallel
{
    lock_guard<mutex> lock(mutex);
    omp_set_num_threads(16);
    for (int i=0; i<size; i++){
        Eigen::Matrix4d idx_pose = keyframeVecUpdated[i].pose;
        double dist = (tmp_pose.block<3, 1>(0, 3) - idx_pose.block<3, 1>(0, 3)).norm();
    }
}

TBB(Threading Building Blocks)

tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()),
    [&](const tbb::blocked_range<size_t>& range) {
        for (size_t i = range.begin(); i != range.end(); ++i) {
            data[i] *= data[i];
        }
    }
);
  • Example from KISS-ICP
std::vector<Eigen::Vector3d> corrected_frame(frame.size());
tbb::parallel_for(size_t(0), frame.size(), [&](size_t i) {
    const auto &pt = frame[i];
    const Eigen::Vector3d rotationVector = pt.cross(Eigen::Vector3d(0., 0., 1.));
    corrected_frame[i] =
        Eigen::AngleAxisd(VERTICAL_ANGLE_OFFSET, rotationVector.normalized()) * pt;
});

C++ 17 STL (for_each)

std::for_each(std::execution::par, data.begin(), data.end(),
    [&](int& element) {
 		// 각 thread는 요소들을 순차적으로 처리
		// vectorized에 safe하다면 std::execution::par_unseq 사용
    }
);
  • Example
pcl::PointCloud<pcl::PointXYZI> transformPointcloud(const pcl::PointCloud<pcl::PointXYZI> &cloud_in, const Eigen::Matrix4d &pose_tf)
{
    int cloud_size = cloud_in.size();
	pcl::PointCloud<pcl::PointXYZI> cloud_out;
    cloud_out.resize(cloud_size);
    cloud_out = cloud_in;
	std::for_each(std::execution::par_unseq, cloud_out.begin(), cloud_out.end(), [&](pcl::PointXYZI &pt)
	{
		float x_ = pt.x;
		float y_ = pt.y;
		float z_ = pt.z;
		pt.x = pose_tf(0, 0) * x_ + pose_tf(0, 1) * y_ + pose_tf(0, 2) * z_ + pose_tf(0, 3);
		pt.y = pose_tf(1, 0) * x_ + pose_tf(1, 1) * y_ + pose_tf(1, 2) * z_ + pose_tf(1, 3);
		pt.z = pose_tf(2, 0) * x_ + pose_tf(2, 1) * y_ + pose_tf(2, 2) * z_ + pose_tf(2, 3);
	});
  return cloud_out;
}

추가: Lambda expression

  • C++11부터 지원
  • 코드를 간결하게 표현 가능하고, 가독성이 좋아진다
  • 일회성으로 함수 정의 없이 사용 가능
  • first-class function으로 취급되어 변수에 할당하거나 함수 인자, 반환값으로 사용 가능
  • 함수를 인라인으로 작성하여 알고리즘에 전달 가능

  • 위의 for_each 예시문을 보면 함수 객체로 구현하지 않고 변수를 참조 받아 바로 접근이 가능하다!

사용방법

1 std::function
auto func = [](std::string str){
    std::cout << str << std::endl;
};
func("Hello World!");
2 파라미터 받기 가능
auto func {
 	[](int num){std::cout << num << std::endl;}  
};
func(38);
3 함수의 파라미터로도 가능
template<typename T>
void func(T abc){
	abc();
}
int main(){
    auto num = [](){std::cout << "Hello!";};
    func(num);
}
4 반환
std::function<void (void)> coutlambda(){
    return [](){std::cout << "Hello!";};
}
5 STL container에 저장
std::vector<std::function<void (void)>> funcs;
funcs.push_back([](){std::cout << "Hello";});
funcs.push_back([](){std::cout << "World";});

for(auto& func : funcs){
    func();
}
6 참조
  • 아래 코드에서 sum은 지역 변수인데, 전역 변수라면 [&]로 해야합니다
std::array<int, 3> vector = {3, 4, 5};
int sum = 0;
std::for_each(vector.begin(), vector.end(), [&sum](int& num)
              sum += num;
});
7 복사
int num = 1;
[num](){
    std::cout << num;
}();

감사합니다! :relaxed:

태그:

카테고리:

업데이트:

댓글남기기