idealvin/coost

Memory allocation error using co/stl containers (co::vector, co::hash_map)

piperoc opened this issue · 6 comments

I wrote a simple graph class using the co::vector and the co::hash_map to use a common adjacency list technique.

I then created a unit test like the the other files in your unitest source folder.

All tests pass but at the very end I get what looks like a memory allocation error (it's probably my mistake in using those structures).
I tried to profile it but cannot figure it out.

Here's my class:

file: graph.h
#ifndef GRAPH_H
#define GRAPH_H

#include "cout.h"
#include "stl.h"
#include "log.h"


template <typename VertexType, typename IDType>
class Graph {

public:
    /// @brief default constructor
    Graph() = default;


    /// @brief add a vertex to the graph
    /// @param IdType the vertex id to be added
    /// @param VertexType the vertex to be added
    void addVertex(const IDType& id, const VertexType& vertex) {
        if (vertexMap.find(id) != vertexMap.end()) {
            ELOG << "vertex " << id << " already exists";
            return;
        }
        vertices.push_back(vertex);
        vertexMap[id] = vertices.size() - 1;
        adjacencyLists.push_back(co::vector<IDType>());
    }


    /// @brief add an edge to the graph
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    void addEdge(const IDType& source, const IDType& target) {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return;
        }
        adjacencyLists[vertexMap[source]].push_back(target);
    }

    /// @brief get a node by id
    /// @param IDType the vertex id
    /// @return VertexType the vertex
    VertexType findNode(const IDType& id) const {
        if (vertexMap.find(id) == vertexMap.end()) {
            ELOG << "vertex " << id << " does not exist";
            return VertexType();
        }
        return vertices[vertexMap.at(id)];
    }

    /// @brief remove a node by id
    /// @param IDType the vertex id
    void removeNode(const IDType& id) {
        if (vertexMap.find(id) == vertexMap.end()) {
            ELOG << "vertex " << id << " does not exist";
            return;
        }
        size_t index = vertexMap.at(id);
        vertices.erase(vertices.begin() + index);
        vertexMap.erase(id);
        adjacencyLists.erase(adjacencyLists.begin() + index);
        for (auto& list : adjacencyLists) {
            for (auto& node : list) {
                if (node == id) {
                    node = adjacencyLists.size();
                }
                if (node > index) {
                    node--;
                }
            }
        }
    }

    /// @brief remove an edge by id
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    void removeEdge(const IDType& source, const IDType& target) {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return;
        }
        size_t sourceIndex = vertexMap.at(source);
        size_t targetIndex = vertexMap.at(target);
        for (auto& node : adjacencyLists[sourceIndex]) {
            if (node == target) {
                node = adjacencyLists.size();
            }
            if (node > targetIndex) {
                node--;
            }
        }
    }


    /// @brief VertexMap getter
    /// @return co::map<IDType, size_t> the vertex map
    co::hash_map<IDType, size_t> VertexMap() const {
        return vertexMap;
    }

    /// @brief Vertices getter
    /// @return co::vector<VertexType> the vertices
    co::vector<VertexType> Vertices() const {
        return vertices;
    }

    /// @brief Edges getter
    /// @return co::vector<co::vector<IDType>> the adjacency lists
    co::vector<co::vector<IDType>> Edges() const {
        return adjacencyLists;
    }

    /// @brief function to check if an edge exists between two vertices
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    /// @return bool true if the edge exists, false otherwise
    bool hasEdge(const IDType& source, const IDType& target) const {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return false;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return false;
        }
        size_t sourceIndex = vertexMap.at(source);
        for (auto& node : adjacencyLists[sourceIndex]) {
            if (node == target) {
                return true;
            }
        }
        return false;
    }


private:
    /// @brief vertex list
    co::vector<VertexType> vertices;

    /// @brief vertex map
    co::hash_map<IDType, size_t> vertexMap;

    /// @brief adjacency lists representing the graph
    co::vector<co::vector<IDType>> adjacencyLists;
};


#endif // GRAPH_H

and here's the file implementing the tests:

file: graph.h
#include "co/flag.h"
#include "co/cout.h"
#include "co/log.h"
#include "co/unitest.h"
#include "co/fastring.h"
#include "co/mem.h"
#include "co/graph.h"
#include <chrono>


namespace test {

DEF_test(graph) {

	Graph<std::string, uint32_t> myGraph = Graph<std::string, uint32_t>();

        myGraph.addVertex(1, "a");
        myGraph.addVertex(2, "b");
        myGraph.addVertex(3, "c");
        myGraph.addVertex(4, "d");

        myGraph.addEdge(1, 2);
        myGraph.addEdge(2, 3);
        myGraph.addEdge(3, 4);
        myGraph.addEdge(4, 1);
        myGraph.addEdge(2, 4);
        myGraph.addEdge(4, 2);

    DEF_case(check_vertices) {
		
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[1]],"a");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[2]],"b");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[3]],"c");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[4]],"d");

    }

	DEF_case(find_node) {

		auto result = myGraph.findNode(3);
		EXPECT_EQ(result , "c");
	}

	DEF_case(find_node_not_found) {

		auto result = myGraph.findNode(5);
		EXPECT_EQ(result, "");
	}

	
	DEF_case(huge_graph) {
		
		const int NUMBER_OF_VERTICES = 2000;

		Graph<std::string, uint32_t> bigGraph;

		auto start = std::chrono::high_resolution_clock::now();

		for (int i = 0; i < NUMBER_OF_VERTICES; i++) {
			bigGraph.addVertex(i, "Vertex_" + std::to_string(i));
		}

		for (int sourceID = 1; sourceID <= NUMBER_OF_VERTICES; ++sourceID) {
			for (int targetID = 1; targetID <= NUMBER_OF_VERTICES; ++targetID) {
				// add 4 edges per vertex
                if (targetID  == (sourceID -1) || targetID == (sourceID +1) || targetID == (sourceID -2) || targetID == (sourceID +2)) 
						bigGraph.addEdge(sourceID, targetID);
			}
		}

		auto end = std::chrono::high_resolution_clock::now();
		auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

		cout << "Time taken to create the big graph: " << duration.count() << " microseconds" << std::endl;

		std::string halfwayVertex = "Vertex_" + std::to_string(NUMBER_OF_VERTICES/2);
		start = std::chrono::high_resolution_clock::now();
		auto result = bigGraph.findNode(NUMBER_OF_VERTICES/2);
		end = std::chrono::high_resolution_clock::now();
		duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
		cout << "Time taken to find node: " << duration.count() << " microseconds" << std::endl;
		EXPECT_EQ(result, halfwayVertex);

	}
    
}


} // namespace test

When I run the unit tests I get the following

> begin test: graph
 case check_vertices:
  EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[1]], "a") passed
  EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[2]], "b") passed
  EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[3]], "c") passed
  EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[4]], "d") passed
 case find_node:
  EXPECT_EQ(result, "c") passed
 case find_node_not_found:
  EXPECT_EQ(result, "") passed
 case huge_graph:
Time taken to create the big graph: 7080 microseconds
Time taken to find node: 1 microseconds
  EXPECT_EQ(result, halfwayVertex) passed
free(): invalid size
F1113 18:59:22.230] SIGABRT: aborted
Aborted

if I debug it aborts when calling void _destruct_range from the co::vector class (file include/co/vector.h line 322) as the for loop range that I'm indirectly passing (with the destructor) is probably wrong.

call stack
[...]
libc.so.6!__GI_raise(int sig) (\build\glibc-CVJwZb\glibc-2.27\sysdeps\unix\sysv\linux\raise.c:51)
libc.so.6!__GI_abort() (\build\glibc-CVJwZb\glibc-2.27\stdlib\abort.c:79)
libc.so.6!__libc_message(enum __libc_message_action action, const char * fmt) (\build\glibc-CVJwZb\glibc-2.27\sysdeps\posix\libc_fatal.c:181)
libc.so.6!malloc_printerr(const char * str) (\build\glibc-CVJwZb\glibc-2.27\malloc\malloc.c:5342)
libc.so.6!_int_free(int have_lock, mchunkptr p, mstate av) (\build\glibc-CVJwZb\glibc-2.27\malloc\malloc.c:4171)
libc.so.6!__GI___libc_free(void * mem) (\build\glibc-CVJwZb\glibc-2.27\malloc\malloc.c:3134)
co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator>::_destruct_range<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, 0>(co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator> * const this, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > * p, size_t beg, size_t end) (\home\cos\git\test-coost\libs\coost\include\co\vector.h:322)
co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator>::reset(co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator> * const this) (\home\cos\git\test-coost\libs\coost\include\co\vector.h:134)
co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator>::~vector(co::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, co::default_allocator> * const this) (\home\cos\git\test-coost\libs\coost\include\co\vector.h:79)
Graph<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int>::~Graph(Graph<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, unsigned int> * const this) 
[...]

Sorry for the lengthy description. Thanks for any advice

Just to make sure, I rewrote the graph class using std::vector and std::unorderded_map and I have no errors:

file: graph.h
#ifndef GRAPH_H
#define GRAPH_H

#include "co/cout.h"
//#include "co/stl.h"
#include <vector>
#include <unordered_map>
#include "co/log.h"
#include "co/json.h"
#include "co/fastring.h"

using namespace std;

template <typename VertexType, typename IDType>
class Graph {

public:
    /// @brief default constructor
    Graph() = default;

    
    /// @brief add a vertex to the graph
    /// @param IdType the vertex id to be added
    /// @param VertexType the vertex to be added
    void addVertex(const IDType& id, const VertexType& vertex) {
        if (vertexMap.find(id) != vertexMap.end()) {
            ELOG << "vertex " << id << " already exists";
            return;
        }
        vertices.push_back(vertex);
        vertexMap[id] = vertices.size() - 1;
        adjacencyLists.push_back(vector<IDType>());
    }


    /// @brief add an edge to the graph
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    void addEdge(const IDType& source, const IDType& target) {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return;
        }
        adjacencyLists[vertexMap[source]].push_back(target);
    }

    /// @brief get a node by id
    /// @param IDType the vertex id
    /// @return VertexType the vertex
    VertexType findNode(const IDType& id) const {
        if (vertexMap.find(id) == vertexMap.end()) {
            ELOG << "vertex " << id << " does not exist";
            return VertexType();
        }
        return vertices[vertexMap.at(id)];
    }

    /// @brief remove a node by id
    /// @param IDType the vertex id
    void removeNode(const IDType& id) {
        if (vertexMap.find(id) == vertexMap.end()) {
            ELOG << "vertex " << id << " does not exist";
            return;
        }
        size_t index = vertexMap.at(id);
        vertices.erase(vertices.begin() + index);
        vertexMap.erase(id);
        adjacencyLists.erase(adjacencyLists.begin() + index);
        for (auto& list : adjacencyLists) {
            for (auto& node : list) {
                if (node == id) {
                    node = adjacencyLists.size();
                }
                if (node > index) {
                    node--;
                }
            }
        }
    }

    /// @brief remove an edge by id
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    void removeEdge(const IDType& source, const IDType& target) {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return;
        }
        size_t sourceIndex = vertexMap.at(source);
        size_t targetIndex = vertexMap.at(target);
        for (auto& node : adjacencyLists[sourceIndex]) {
            if (node == target) {
                node = adjacencyLists.size();
            }
            if (node > targetIndex) {
                node--;
            }
        }

        // remove the edge from the adjacency list
        adjacencyLists[sourceIndex].erase(
            std::remove(adjacencyLists[sourceIndex].begin(), adjacencyLists[sourceIndex].end(), adjacencyLists.size()),
            adjacencyLists[sourceIndex].end());

    }


    /// @brief VertexMap getter
    /// @return co::map<IDType, size_t> the vertex map
    unordered_map<IDType, size_t> VertexMap() const {
        return vertexMap;
    }

    /// @brief Vertices getter
    /// @return vector<VertexType> the vertices
    vector<VertexType> Vertices() const {
        return vertices;
    }

    /// @brief Edges getter
    /// @return vector<vector<IDType>> the adjacency lists
    vector<vector<IDType>> Edges() const {
        return adjacencyLists;
    }

    /// @brief function to check if an edge exists between two vertices
    /// @param IDType the source vertex id
    /// @param IDType the target vertex id
    /// @return bool true if the edge exists, false otherwise
    bool hasEdge(const IDType& source, const IDType& target) const {
        if (vertexMap.find(source) == vertexMap.end()) {
            ELOG << "vertex " << source << " does not exist";
            return false;
        }
        if (vertexMap.find(target) == vertexMap.end()) {
            ELOG << "vertex " << target << " does not exist";
            return false;
        }
        size_t sourceIndex = vertexMap.at(source);
        for (auto& node : adjacencyLists[sourceIndex]) {
            if (node == target) {
                return true;
            }
        }
        return false;
    }


private:
    /// @brief vertex list
    vector<VertexType> vertices;

    /// @brief vertex map
    unordered_map<IDType, size_t> vertexMap;

    /// @brief adjacency lists representing the graph
    vector<vector<IDType>> adjacencyLists;
};

#endif // GRAPH_H

maybe I should use something different that co::hash_map or learn how to use it correctly...

Reproduced it. Let me see..

@piperoc

sorry, a little busy at work these days.It is a bug in co::vector due to SSO of std::string. You may use fastring instead of std::string to avoid this problem first. I'll try to make a fix later.

@idealvin awesome! That worked right away. Thank you so much for replying so quickly.

In case some needs it, here's the tests with the fastring instead of the std::string to get rid of the error:

file: graph.cc
#include "co/flag.h"
#include "co/cout.h"
#include "co/log.h"
#include "co/unitest.h"
#include "co/fastring.h"
#include "co/mem.h"
#include "co/graph.h"
#include <chrono>


namespace test {

DEF_test(graph) {

	Graph<fastring, uint32_t> myGraph = Graph<fastring, uint32_t>();

        myGraph.addVertex(1, "a");
        myGraph.addVertex(2, "b");
        myGraph.addVertex(3, "c");
        myGraph.addVertex(4, "d");

        myGraph.addEdge(1, 2);
        myGraph.addEdge(2, 3);
        myGraph.addEdge(3, 4);
        myGraph.addEdge(4, 1);
        myGraph.addEdge(2, 4);
        myGraph.addEdge(4, 2);

    DEF_case(check_vertices) {
		
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[1]],"a");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[2]],"b");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[3]],"c");
		EXPECT_EQ(myGraph.Vertices()[myGraph.VertexMap()[4]],"d");

    }

	DEF_case(find_node) {

		auto result = myGraph.findNode(3);
		EXPECT_EQ(result , "c");
	}

	DEF_case(find_node_not_found) {

		auto result = myGraph.findNode(5);
		EXPECT_EQ(result, "");
	}

	
	DEF_case(huge_graph) {
		
		const int NUMBER_OF_VERTICES = 2000;

		Graph<fastring, uint32_t> bigGraph;

		auto start = std::chrono::high_resolution_clock::now();

		for (int i = 0; i < NUMBER_OF_VERTICES; i++) {
			bigGraph.addVertex(i, "Vertex_" + std::to_string(i));
		}

		for (int sourceID = 1; sourceID <= NUMBER_OF_VERTICES; ++sourceID) {
			for (int targetID = 1; targetID <= NUMBER_OF_VERTICES; ++targetID) {
				// add 4 edges per vertex
                if (targetID  == (sourceID -1) || targetID == (sourceID +1) || targetID == (sourceID -2) || targetID == (sourceID +2)) 
						bigGraph.addEdge(sourceID, targetID);
			}
		}

		auto end = std::chrono::high_resolution_clock::now();
		auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

		cout << "Time taken to create the big graph: " << duration.count() << " microseconds" << std::endl;

		//EXPECT_EQ(bigGraph.Vertices().size(), NUMBER_OF_VERTICES);


		fastring halfwayVertex = "Vertex_" + std::to_string(NUMBER_OF_VERTICES/2);
		start = std::chrono::high_resolution_clock::now();
		auto result = bigGraph.findNode(NUMBER_OF_VERTICES/2);
		end = std::chrono::high_resolution_clock::now();
		duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
		cout << "Time taken to find node: " << duration.count() << " microseconds" << std::endl;
		EXPECT_EQ(result, halfwayVertex);

	}
    
}


} // namespace test

@idealvin that works. Thanks!