This CL removes the Graph.edge_set_ field. This field stores a set of the Edge* that are in a Graph. However, Graph already stores this information, in Graph.edges_. There's really no good reason to keep both of these collections. To convert everything to use Graph.edges_ instead of Graph.edge_set_, I defined a class which handled excluding nullptr from iteration of the edges_ vector.

This caused changes to non-contractual behavior of the runtime (enumeration order), so the unit tests are updated to reflect this.

On a real-world graph used by our team, which contains 13190 nodes and 20796 edges, this change reduced heap allocation from 39.1 MB to 38.0 MB, for a drop of about 3%.
Change: 154781831
This commit is contained in:
A. Unique TensorFlower 2017-05-01 16:14:06 -08:00 committed by TensorFlower Gardener
parent 0135602ffd
commit aebaf317ce
4 changed files with 75 additions and 15 deletions

View File

@ -456,7 +456,7 @@ Status FunctionLibraryRuntimeImpl::Instantiate(
void DumpGraph(StringPiece label, const Graph* g) {
// TODO(zhifengc): Change Graph to record #nodes.
VLOG(1) << "Graph " << label << " #nodes " << g->num_nodes() << " #edges "
<< g->edges().size();
<< g->num_edges();
if (VLOG_IS_ON(2)) {
for (const auto& line : str_util::Split(DebugString(g), '\n')) {
VLOG(2) << "|| " << line;

View File

@ -424,7 +424,7 @@ TEST_F(FunctionLibraryRuntimeTest, ControlDeps) {
n8 = NoOp() @ n4
n9 = Identity[T=float](n3) @ n8
n10 = Identity[T=float](n2) @ n8
n11 = NoOp() @ n10, n9
n11 = NoOp() @ n9, n10
n5 = Mul[T=float](n2, n2) @ n11
n6 = Add[T=float](n4, n5)
}
@ -500,8 +500,8 @@ TEST_F(FunctionLibraryRuntimeTest, Gradient_XTimesTwo) {
OptimizeGraph(lib_, &g);
const char* e2 = R"P(
(n2:float, n3:float) -> (n9:float) {
n11 = Const[dtype=int32, value=Tensor<type: int32 shape: [0] values: >]()
n10 = Const[dtype=float, value=Tensor<type: float shape: [] values: 2>]()
n11 = Const[dtype=int32, value=Tensor<type: int32 shape: [0] values: >]()
n6 = Shape[T=float, out_type=int32](n2)
n5 = Mul[T=float](n3, n10)
n7 = BroadcastGradientArgs[T=int32](n6, n11)
@ -614,10 +614,10 @@ TEST_F(FunctionLibraryRuntimeTest, Gradient_AddSum) {
n17 = Sum[T=float, Tidx=int32, keep_dims=false](n14, n16)
n19 = SymbolicGradient[Tin={float, int32, float}, Tout={float, int32}, f=Sum[T=float, Tidx=int32, keep_dims=false]](n14, n16, n26)
n21 = SymbolicGradient[Tin={float, float, float}, Tout={float, float}, f=Add[T=float]](n24, n25, n19)
n28 = Identity[T=float](n21:1)
n27 = Identity[T=float](n21)
n6 = Identity[T=float](n28)
n28 = Identity[T=float](n21:1)
n8 = Identity[T=float](n27)
n6 = Identity[T=float](n28)
}
)P";
EXPECT_EQ(e1, DebugString(g.get()));
@ -626,8 +626,8 @@ TEST_F(FunctionLibraryRuntimeTest, Gradient_AddSum) {
const char* e2 = R"P(
(n4:float, n3:float) -> (n25:float, n23:float) {
n2 = Const[dtype=float, value=Tensor<type: float shape: [] values: 1>]()
n8 = Const[dtype=int32, value=Tensor<type: int32 shape: [] values: 0>]()
n7 = Const[dtype=int32, value=Tensor<type: int32 shape: [] values: 1>]()
n8 = Const[dtype=int32, value=Tensor<type: int32 shape: [] values: 0>]()
n19 = Shape[T=float, out_type=int32](n3)
n9 = Add[T=float](n4, n3)
n20 = Shape[T=float, out_type=int32](n4)
@ -641,10 +641,10 @@ TEST_F(FunctionLibraryRuntimeTest, Gradient_AddSum) {
n16 = Reshape[T=float, Tshape=int32](n2, n15)
n17 = Div[T=int32](n14, n15)
n18 = Tile[T=float, Tmultiples=int32](n16, n17)
n24 = Sum[T=float, Tidx=int32, keep_dims=false](n18, n21)
n22 = Sum[T=float, Tidx=int32, keep_dims=false](n18, n21:1)
n25 = Reshape[T=float, Tshape=int32](n24, n20)
n24 = Sum[T=float, Tidx=int32, keep_dims=false](n18, n21)
n23 = Reshape[T=float, Tshape=int32](n22, n19)
n25 = Reshape[T=float, Tshape=int32](n24, n20)
}
)P";
EXPECT_EQ(e2, DebugString(g.get()));

View File

@ -344,7 +344,7 @@ const Edge* Graph::AddEdge(Node* source, int x, Node* dest, int y) {
CHECK(source->out_edges_.insert(e).second);
CHECK(dest->in_edges_.insert(e).second);
edges_.push_back(e);
edge_set_.insert(e);
++num_edges_;
return e;
}
@ -354,8 +354,8 @@ void Graph::RemoveEdge(const Edge* e) {
CHECK_EQ(e->src_->out_edges_.erase(e), size_t{1});
CHECK_EQ(e->dst_->in_edges_.erase(e), size_t{1});
CHECK_EQ(e, edges_[e->id_]);
CHECK_GT(num_edges_, 0);
CHECK_EQ(edge_set_.erase(e), size_t{1});
edges_[e->id_] = nullptr;
Edge* del = const_cast<Edge*>(e);
@ -365,6 +365,7 @@ void Graph::RemoveEdge(const Edge* e) {
del->src_output_ = kControlSlot - 1;
del->dst_input_ = kControlSlot - 1;
free_edges_.push_back(del);
--num_edges_;
}
Status Graph::AddFunctionLibrary(const FunctionDefLibrary& fdef_lib) {

View File

@ -268,6 +268,66 @@ class Edge {
int dst_input_;
};
// Allows for iteration of the edges of a Graph, by iterating the underlying
// Graph.edges_ vector while skipping over null entries.
class GraphEdgesIterable {
private:
const std::vector<Edge*>& edges_;
public:
explicit GraphEdgesIterable(const std::vector<Edge*>& edges)
: edges_(edges) {}
typedef Edge* value_type;
class const_iterator {
private:
// The underlying iterator.
std::vector<value_type>::const_iterator iter_;
// The end of the underlying iterator.
std::vector<value_type>::const_iterator end_;
// Advances iter_ until it reaches a non-null item, or reaches the end.
void apply_filter() {
while (iter_ != end_ && *iter_ == nullptr) {
++iter_;
}
}
public:
const_iterator(std::vector<value_type>::const_iterator iter,
std::vector<value_type>::const_iterator end)
: iter_(iter), end_(end) {
apply_filter();
}
bool operator==(const const_iterator& other) const {
return iter_ == other.iter_;
}
bool operator!=(const const_iterator& other) const {
return iter_ != other.iter_;
}
// This is the prefix increment operator (++x), which is the operator
// used by C++ range iteration (for (x : y) ...). We intentionally do not
// provide a postfix increment operator.
const_iterator& operator++() {
++iter_;
apply_filter();
return *this;
}
value_type operator*() { return *iter_; }
};
const_iterator begin() {
return const_iterator(edges_.begin(), edges_.end());
}
const_iterator end() { return const_iterator(edges_.end(), edges_.end()); }
};
// Thread compatible but not thread safe.
class Graph {
public:
@ -345,7 +405,7 @@ class Graph {
// smaller than num_edge_ids(). If one needs to create an array of
// edges indexed by edge ids, num_edge_ids() should be used as the
// array's size.
int num_edges() const { return edges().size(); }
int num_edges() const { return num_edges_; }
// Serialize the nodes starting at `from_node_id` to a GraphDef.
void ToGraphDefSubRange(GraphDef* graph_def, int from_node_id) const;
@ -381,7 +441,7 @@ class Graph {
// Access to the set of all edges. Example usage:
// for (const Edge* e : graph.edges()) { ... }
const EdgeSet& edges() const { return edge_set_; }
GraphEdgesIterable edges() const { return GraphEdgesIterable(edges_); }
// The pre-defined nodes.
enum { kSourceId = 0, kSinkId = 1 };
@ -421,9 +481,8 @@ class Graph {
// the edge with that id was removed from the graph.
std::vector<Edge*> edges_;
// For ease of iteration, we currently just keep a set of all live
// edges. May want to optimize by removing this copy.
EdgeSet edge_set_;
// The number of entries in edges_ that are not nullptr.
int num_edges_ = 0;
// Allocated but free nodes and edges.
std::vector<Node*> free_nodes_;