使用boost::adjacency_matrix的测试程序编程

372 篇文章 ¥29.90 ¥99.00
本文介绍如何使用Boost库中的boost::adjacency_matrix来表示和操作图的邻接矩阵。通过一个简单的测试程序,展示了创建无向图、添加边、遍历节点和边的过程,帮助理解如何利用Boost库处理图形问题。

使用boost::adjacency_matrix的测试程序编程

boost::adjacency_matrix是Boost库中的一个工具,用于表示和操作图的邻接矩阵。邻接矩阵是一种常见的图表示方法,它使用二维矩阵来表示图中节点之间的连接关系。在这篇文章中,我们将编写一个测试程序来演示boost::adjacency_matrix的用法。

首先,我们需要安装和配置Boost库。Boost是一个开源的C++库集合,提供了许多有用的工具和算法。您可以从Boost的官方网站(https://www.boost.org/ ↗)下载并安装Boost库。安装完成后,您需要在您的编译环境中配置Boost库的路径。

接下来,我们将编写一个简单的程序来测试boost::adjacency_matrix。下面是一个示例程序:

#include <iostream>
#include 
void DataSDMap::parse_graph(OsmGraph const &sdMap, Eigen::Affine3d const &transformationMatrix, std::vector<EdgeInfo> &edges_info, bool crop) const noexcept { // get roi corner points std::vector<Point> canvas_polygon; if (crop) { double min_x = -1 * roiLength; double max_x = roiLength; double min_y = -1 * roiWidth; double max_y = roiWidth; canvas_polygon.push_back(Point(min_x, min_y)); canvas_polygon.push_back(Point(min_x, max_y)); canvas_polygon.push_back(Point(max_x, max_y)); canvas_polygon.push_back(Point(max_x, min_y)); } auto start = std::chrono::high_resolution_clock::now(); edges_info.clear(); // 读取graph中的edges数据,还原edge geometry // std::cout << "Number of edges: " << boost::num_edges(sdMap) << std::endl; std::pair<boost::adjacency_list<>::vertex_iterator, boost::adjacency_list<>::vertex_iterator> vertices_pair = boost::vertices(sdMap); // boost::iterator_range<boost::graph_traits<OsmGraph>::vertex_iterator> // vertices_pair = boost::make_iterator_range(vertices(sdMap)); for (auto e : // boost::make_iterator_range(boost::edges(sdMap))) for (auto e = boost::edges(sdMap).first; e != boost::edges(sdMap).second; ++e) { const OsmEdgeProperties &edgeProps = sdMap[*e]; // get vertices geometry LineString linestring_global; std::string line_geometry_string = edgeProps.geometry; if (line_geometry_string.empty()) { boost::adjacency_list<>::vertex_descriptor source = boost::source(*e, sdMap); boost::adjacency_list<>::vertex_descriptor target = boost::target(*e, sdMap); if (source == target) continue; line_geometry_string = "LINESTRING ("; const OsmNodeProperties &startNodeProps = sdMap[*(vertices_pair.first + source)]; line_geometry_string = line_geometry_string + startNodeProps.x + " " + startNodeProps.y + ", "; const OsmNodeProperties &endNodeProps = sdMap[*(vertices_pair.first + target)]; line_geometry_string = line_geometry_string + " " + endNodeProps.x + " " + endNodeProps.y + ")"; } boost::geometry::read_wkt(line_geometry_string, linestring_global); auto end = std::chrono::high_resolution_clock::now(); auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); 分析这段代码在周期性调用时,运行时间在递增,分析可能的原因?
06-13
<think>我们分析代码中可能导致运行时间递增的原因。代码周期性调用,每次调用都会解析一个图(OsmGraph)中的边(edges),并将解析后的几何信息存储到edges_info中。可能的原因:1.**内存泄漏或未释放资源**:在每次调用中,可能会创建一些对象(如linestring_global)而没有正确释放,导致内存增长,从而使得后续操作(如内存分配)变慢。2.**字符串拼接效率低**:在构建WKT字符串时,使用了多次字符串拼接(`line_geometry_string=line_geometry_string+...`),这会产生大量的临时字符串,导致内存分配和复制操作增加,随着调用次数增多,内存碎片化可能加剧,使得字符串操作变慢。3.**容器未预分配**:`edges_info`在每次调用时被清除(`clear()`),但后续会重新填充。如果每次填充的数量很大,且没有预分配,可能导致多次重新分配内存,尤其是在循环内部不断添加元素时(虽然代码中没有直接看到向edges_info添加元素的代码,但函数目的应该是填充edges_info,所以可能在循环后有添加操作,但未显示)。另外,`canvas_polygon`在crop为true时也会添加4个点,但每次调用都会重新创建,这本身开销不大。4.**日志输出影响**:代码中注释掉了输出边数量的日志,但循环内部没有输出日志。然而,在循环结束后有一个计时日志(但代码中只显示了计时的获取,没有显示输出,所以可能没有输出日志?)。如果实际代码中有输出日志,频繁的I/O操作可能会累积延迟。5.**Boost.Geometry的read_wkt可能内部有状态**:虽然不太可能,但需要确认`read_wkt`函数是否每次调用都会清理内部状态,或者是否有缓存机制导致随着调用次数增加而变慢。6.**图结构变化**:如果传入的sdMap在每次调用时都在增大(边和节点增多),那么每次处理的数据量在增加,自然时间会增加。但问题描述是周期性调用,可能图结构不变?所以需要确认传入的图是否不变。但是,问题描述是“周期性调用时,运行时间在递增”,即使传入相同的图,时间也在增加。因此,我们重点考虑资源累积(如内存泄漏)和效率降低(如字符串操作、内存分配)的问题。具体分析代码中的可疑点:-在循环内部,对于每条边:a.如果边的geometry属性为空,则手动构建一个WKT字符串(两点组成的线)。这里使用字符串拼接,效率较低,且可能产生大量临时对象。b.使用`boost::geometry::read_wkt`解析字符串,创建LineString对象。由于函数是noexcept,所以没有异常处理,但内部操作可能会抛出异常?如果抛出异常,由于noexcept,程序会终止,所以可能不是异常的问题。重点怀疑:1.字符串拼接:每次拼接都创建新的字符串,分配内存,然后释放旧字符串。多次循环后,内存碎片化可能导致分配时间增加。2.内存泄漏:`linestring_global`在每次循环中都会重新创建,但是它的内部可能会分配动态内存(如点坐标的容器)。在循环迭代时,这个对象会析构吗?是的,它在循环内部定义,每次循环结束时会析构。但是,如果`LineString`类型是一个复杂的容器,其析构函数可能没有及时释放内存(例如,内存被保留到下一次分配,但实际标准库容器通常会在析构时释放内存)。所以这里可能不是泄漏,但是频繁的构造和析构可能导致内存分配器效率降低(尤其是当分配大量小对象时)。另外,注意`vertices_pair`是在循环外部获取的,但它在循环内部被使用(在构建字符串时通过`vertices_pair.first+source`来获取顶点描述符对应的节点属性)。这个操作本身是O(1)的,不会随着调用次数增加而变慢。建议的优化措施:1.优化字符串拼接:使用`std::ostringstream`来构建字符串,避免多次分配。2.预分配内存:对于`edges_info`,如果知道边的数量,可以提前`reserve`,避免动态扩容(虽然代码中没有显示添加,但函数参数edges_info是按引用传入,并进行了clear,所以后续应该会添加)。另外,对于`line_geometry_string`,如果可能,可以复用并预留足够空间。3.减少临时对象:例如,将`linestring_global`的声明放在循环内部是合理的,因为它每次循环后都会析构。但是,如果`LineString`类型有`clear()`方法,可以在循环外声明,然后在循环内部复用,避免反复分配和释放内存(但要注意,`read_wkt`会覆盖之前的内容)。4.检查内存增长:使用内存分析工具(如valgrind)检查是否有内存泄漏。修改后的代码示例:我们将修改字符串拼接的方式,并尝试复用一些对象(如字符串流)。注意:由于函数是constnoexcept,所以不能修改成员变量,因此我们只能使用局部变量。修改代码:```cppvoidDataSDMap::parse_graph(OsmGraphconst&sdMap,Eigen::Affine3dconst&transformationMatrix,std::vector<EdgeInfo>&edges_info,boolcrop)constnoexcept{//getroicornerpointsstd::vector<Point>canvas_polygon;if(crop){doublemin_x=-1*roiLength;doublemax_x=roiLength;doublemin_y=-1*roiWidth;doublemax_y=roiWidth;canvas_polygon.push_back(Point(min_x,min_y));canvas_polygon.push_back(Point(min_x,max_y));canvas_polygon.push_back(Point(max_x,max_y));canvas_polygon.push_back(Point(max_x,min_y));}autostart=std::chrono::high_resolution_clock::now();edges_info.clear();//预分配edges_info的内存,假设我们知道边的数量constsize_tnum_edges=boost::num_edges(sdMap);edges_info.reserve(num_edges);autovertices_pair=boost::vertices(sdMap);//获取顶点迭代器对//使用一个字符串流来避免多次字符串拼接std::ostringstreamwkt_stream;for(autoe=boost::edges(sdMap).first;e!=boost::edges(sdMap).second;++e){constOsmEdgeProperties&edgeProps=sdMap[*e];std::stringline_geometry_string=edgeProps.geometry;if(line_geometry_string.empty()){boost::adjacency_list<>::vertex_descriptorsource=boost::source(*e,sdMap);boost::adjacency_list<>::vertex_descriptortarget=boost::target(*e,sdMap);if(source==target)continue;//使用字符串流构建WKT字符串wkt_stream.str("");//清空流wkt_stream.clear();//清除错误状态wkt_stream<<"LINESTRING("<<sdMap[*(vertices_pair.first+source)].x<<""<<sdMap[*(vertices_pair.first+source)].y<<","<<sdMap[*(vertices_pair.first+target)].x<<""<<sdMap[*(vertices_pair.first+target)].y<<")";line_geometry_string=wkt_stream.str();}LineStringlinestring_global;boost::geometry::read_wkt(line_geometry_string,linestring_global);//...这里应该还有后续处理,比如将linestring_global转换后添加到edges_info中?//计时和日志(如果确实需要,但建议减少日志输出,尤其是在循环内)//autoend=std::chrono::high_resolution_clock::now();//autodelay=...;}//注意:实际代码中可能还需要处理linestring_global,然后添加到edges_info}```解释:-使用`std::ostringstream`来构建WKT字符串,避免了多次字符串拼接带来的临时对象和内存分配。-为`edges_info`预分配了内存,避免在添加元素时多次扩容(假设循环后有添加操作)。-将`vertices_pair`的计算移出循环,避免重复计算(虽然`boost::vertices`可能很快,但移出循环是好的做法)。另外,注意`wkt_stream`在循环外部创建,循环内每次清空后复用,避免重复创建流对象。如果运行时间仍然递增,那么需要检查:-`boost::geometry::read_wkt`是否存在内部状态累积(比如缓存)?查看Boost.Geometry文档,该函数应该没有内部状态,每次调用都是独立的。-是否由于内存碎片化导致?长期运行的程序,频繁分配和释放内存可能导致碎片化,使得内存分配时间增加。可以考虑使用内存池。最后,如果可能,建议使用性能分析工具(如gperftools)来定位热点。相关问题:§§相关问题§§:1.如何检测C++程序中的内存碎片化问题?2.在C++中,使用ostringstream和字符串拼接(+)在性能上有何差异?3.Boost.Geometry的read_wkt函数在解析WKT字符串时,有没有内存分配方面的优化技巧?4.对于需要频繁创建和销毁的对象,如何通过对象池模式来优化性能?</think>### 分析代码运行时间递增的原因 您的代码在周期性调用时运行时间递增,主要存在以下性能问题: #### 1. **字符串拼接效率低下** ```cpp line_geometry_string = line_geometry_string + startNodeProps.x + " " + startNodeProps.y + ", " + ...; ``` - **问题**:多次使用`operator+`进行字符串拼接会产生大量临时字符串对象 - **影响**: - 每次拼接都需要内存分配和复制操作 - 随着调用次数增加,内存碎片化加剧 - 在边数量较多时,开销呈指数级增长 #### 2. **顶点迭代器重复计算** ```cpp std::pair<boost::adjacency_list<>::vertex_iterator, boost::adjacency_list<>::vertex_iterator> vertices_pair = boost::vertices(sdMap); ``` - **问题**:在循环外获取顶点迭代器对,但每次访问都需要计算偏移 - **影响**: - `*(vertices_pair.first + source)` 需要指针算术运算 - 随着图规模增大,访问效率降低 #### 3. **几何对象解析开销** ```cpp boost::geometry::read_wkt(line_geometry_string, linestring_global); ``` - **问题**:WKT解析涉及动态内存分配和坐标转换 - **影响**: - 每次解析都需要创建新的`LineString`对象 - Boost.Geometry可能积累内部状态 - 未复用已分配的内存资源 #### 4. **日志输出成为瓶颈** ```cpp auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); // 假设后续有日志输出 ``` - **问题**:控制台输出是同步操作 - **影响**: - 当边数量大时,日志量线性增长 - I/O等待时间随着调用次数增加而累积 #### 5. **容器未预分配** ```cpp edges_info.clear(); ``` - **问题**:清除容器但未预分配空间 - **影响**: - 后续添加元素可能触发多次扩容 - 内存分配器开销随着调用次数增加 ### 优化后的代码实现 ```cpp void DataSDMap::parse_graph(OsmGraph const &sdMap, Eigen::Affine3d const &transformationMatrix, std::vector<EdgeInfo> &edges_info, bool crop) const noexcept { // 1. 预分配ROI多边形内存 std::vector<Point> canvas_polygon; if (crop) { canvas_polygon.reserve(4); // 预分配4个点 double min_x = -roiLength, max_x = roiLength; double min_y = -roiWidth, max_y = roiWidth; canvas_polygon.emplace_back(min_x, min_y); canvas_polygon.emplace_back(min_x, max_y); canvas_polygon.emplace_back(max_x, max_y); canvas_polygon.emplace_back(max_x, min_y); } // 2. 预分配edges_info内存 const size_t num_edges = boost::num_edges(sdMap); edges_info.clear(); edges_info.reserve(num_edges); // 3. 获取顶点属性数组(避免循环内重复计算) const auto vertices_range = boost::make_iterator_range(boost::vertices(sdMap)); std::vector<OsmNodeProperties> node_props; node_props.reserve(boost::num_vertices(sdMap)); for (auto v : vertices_range) { node_props.push_back(sdMap[v]); } // 4. 优化字符串处理 static thread_local std::string wkt_buffer; // 线程局部复用缓冲区 wkt_buffer.clear(); wkt_buffer.reserve(128); // 预分配WKT字符串空间 // 5. 复用几何对象 static thread_local LineString linestring_reused; // 线程局部复用 auto start = std::chrono::high_resolution_clock::now(); // 6. 使用range-based循环 for (auto e : boost::make_iterator_range(boost::edges(sdMap))) { const OsmEdgeProperties& edgeProps = sdMap[e]; // 7. 高效处理WKT字符串 if (edgeProps.geometry.empty()) { const auto& src_node = node_props[boost::source(e, sdMap)]; const auto& tgt_node = node_props[boost::target(e, sdMap)]; // 直接操作预分配缓冲区 wkt_buffer = "LINESTRING ("; wkt_buffer.append(std::to_string(src_node.x)).append(" "); wkt_buffer.append(std::to_string(src_node.y)).append(", "); wkt_buffer.append(std::to_string(tgt_node.x)).append(" "); wkt_buffer.append(std::to_string(tgt_node.y)).append(")"); } else { wkt_buffer = edgeProps.geometry; } // 8. 复用几何对象 linestring_reused.clear(); boost::geometry::read_wkt(wkt_buffer, linestring_reused); // 9. 后续处理逻辑... } // 10. 限速日志输出 static size_t call_count = 0; if (++call_count % 100 == 0) { auto end = std::chrono::high_resolution_clock::now(); auto delay = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); // 异步或缓冲日志输出 } } ``` ### 关键优化点说明 1. **内存管理优化**: - 使用`reserve()`预分配所有容器内存 - 线程局部变量(`thread_local`)复用字符串和几何对象缓冲区 - 直接操作预分配字符串缓冲区避免临时对象 2. **数据访问优化**: - 预先提取顶点属性到连续内存数组 - 使用`make_iterator_range`简化Boost.Graph遍历 - 用`emplace_back`替代`push_back`减少拷贝 3. **几何处理优化**: - 复用`LineString`对象避免重复构造/析构 - 使用`clear()`重置几何对象而非重新创建 4. **日志输出优化**: - 每100次调用输出一次日志 - 使用静态计数器控制输出频率 - 建议实现异步日志系统 5. **性能关键点**: - 将`boost::vertices()`移出热路径循环 - 避免在循环内进行属性查找计算 - 使用连续内存存储节点属性加速访问 ### 验证建议 1. **性能分析**: ```bash perf record -e cycles:u -g ./your_program perf report -n --stdio ``` 2. **内存分析**: ```bash valgrind --tool=massif --stacks=yes ./your_program ms_print massif.out.* ``` 3. **Boost.Graph优化**: - 考虑使用`vecS`作为顶点容器提高访问效率 - 使用`boost::lookup_edge()`替代迭代查找
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值