按值传递对象时与按引用传递对象时运行时较慢

Slow runtime when objects are passed by value vs when passed by reference

本文关键字:对象 运行时 按值传递 按引用传递      更新时间:2023-10-16

这是一个名为 Graph 的类的构造函数。在构造函数中,我正在尝试初始化某些内容,这是有效的构造器,即它运行并完成:

Graph(const float density, const int numVertex = 50): numEdges(0) {
        graph.resize(numVertex, vector<Vertex>(numVertex));
        for (int s = 0; s < numVertex; ++s) {
            for (int k = 0; k < s; ++k )
                if (edge_exist(density)) {
                    graph[s][k].visited = graph[k][s].visited = false;
                    graph[s][k].distance = graph[k][s].distance = _MAX;
                    ++numEdges;
                    int distance = rand() % 10 + 1;
                    graph[s][k].edges.emplace_back(piiv(distance, graph[k][s]));
                    graph[k][s].edges.emplace_back(piiv(distance, graph[s][k]));
                }
        }       
    }
typedef pair<int, Vertex> piiv;
vector<vector<Vertex> > graph;

该类有一个名为graph的字段,它是名为Vertex的结构向量的向量。

typedef struct vert {
    std::list<std::pair<int, vert> > edges;
    int distance;
    bool visited;
} Vertex;

现在,如果我保持顶点结构原样,而是使向量只是一个一维向量

vector<Vertex> graph;

并将构造函数更改为如下所示:

Graph(const float density, const int numVertex = 50): numEdges(0) {
        graph.resize(numVertex);
        for (int s = 0; s < numVertex; ++s) {
            for (int k = 0; k < s; ++k )
                if (edge_exist(density)) {
                    graph[s].visited = graph[k].visited = false;
                    graph[s].distance = graph[k].distance = _MAX;
                    ++numEdges;
                    int distance = rand() % 10 + 1;
                    graph[s].edges.emplace_back(piiv(distance, graph[k]));
                    graph[k].edges.emplace_back(piiv(distance, graph[s]));
                }
        }       
    }

现在,此更改导致代码的运行时间比向量为 2 维时的通常运行时间长得多。我没有耐心弄清楚它运行了多长时间,但我知道它运行得比第一个慢得不合理,而且没有明显的原因。好吧,对我来说并不明显,但我敢打赌这里有人对为什么会这样有一些见解。

所以我的问题是,是什么导致了程序中这种看似不必要的延迟?如果有帮助,这就是构造函数的调用方式:

Graph G(0.4);

我已经跟踪了问题来自第二个构造函数实现中的最后 2 行:

graph[s].edges.emplace_back(piiv(distance, graph[k]));
graph[k].edges.emplace_back(piiv(distance, graph[s]));

所以我想真正的问题是上述内容与第一个构造函数中所做的有什么不同?

编辑

宾果游戏!在调试时,我决定更改要声明的顶点结构,如下所示:

typedef struct vert {
    std::list<std::pair<int, vert&> > edges;
    int distance;
    bool visited;
} Vertex;

这似乎解决了问题,但为什么??为什么与按引用传递相比,按值传入顶点对象不起作用?

@Joe Z很好地总结了发生这种情况的原因。

std::list<std::pair<int, vert> > edges;

通过将Vertex边声明为包含其他Vertex值的其他列表,这意味着每次将新顶点添加到列表中时,复制构造函数都会立即启动并开始复制该vert的所有边,这也意味着复制该Vertex的所有子级,依此类推......

如您所见,这种贪得无厌的复制意味着随着图形变得越来越密集,我们的复制会逐渐花费更多的时间,如果我们决定在图形中引入一个循环,上帝会帮助我们,因为这意味着我们将在该程序完成之前耗尽内存。

edges 属性更改为具有此类型:

std::list<std::pair<int, vert&> > edges;

现在这意味着每次我们向图形添加新边时,我们只需维护对其他顶点的引用,而不是复制它们。这也意味着我们实际上能够检测到何时从另一个顶点移除了一条边。

此外,我实际上将此边缘定义为:

std::list<std::pair<int, const vert&> > edges;

这只是一个很好的做法,因为我们希望确保边是不可变的,并且引用另一个顶点并不意味着能够更改其属性。