for문을 빠르게!
C++ 코딩 알쓸신잡
최근에 online pose graph optimization 모듈을 만들다 보니, 불가피하게 for문을 많이 사용할 수 밖에 없었다.
Online이다 보니 real time이 중요해서 좀더 빠르게 for 문을 만들 수는 없나? 하다가 병렬 프로그래밍에 대해 알게되었다.
TBB, OpenMP 라이브러리와 C++17 STL의 for_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:
댓글남기기