diff --git a/src/mesh/CartesianMeshBuilder.cpp b/src/mesh/CartesianMeshBuilder.cpp
index 9f6ea50cab88390669e3471ad3c70f25e870e390..79fb0a2011bceae67a20e7b877f2896a2af4e66e 100644
--- a/src/mesh/CartesianMeshBuilder.cpp
+++ b/src/mesh/CartesianMeshBuilder.cpp
@@ -5,6 +5,8 @@
 #include <utils/Array.hpp>
 #include <utils/Messenger.hpp>
 
+#include <utils/Timer.hpp>
+
 template <size_t Dimension>
 NodeValue<TinyVector<Dimension>>
 CartesianMeshBuilder::_getNodeCoordinates(const TinyVector<Dimension>&,
@@ -67,6 +69,9 @@ CartesianMeshBuilder::_getNodeCoordinates(const TinyVector<3>& a,
                                           const TinyVector<3, uint64_t>& cell_size,
                                           const IConnectivity& connectivity) const
 {
+  Timer t;
+  t.start();
+
   const TinyVector<3, uint64_t> node_size = [&] {
     TinyVector node_size{cell_size};
     for (size_t i = 0; i < 3; ++i) {
@@ -95,6 +100,8 @@ CartesianMeshBuilder::_getNodeCoordinates(const TinyVector<3>& a,
       }
     });
 
+  std::cout << "CartesianMeshBuilder::_getNodeCoordinates t = " << t << '\n';
+
   return xr;
 }
 
@@ -104,6 +111,8 @@ CartesianMeshBuilder::_buildCartesianMesh(const TinyVector<Dimension>& a,
                                           const TinyVector<Dimension>& b,
                                           const TinyVector<Dimension, uint64_t>& cell_size)
 {
+  Timer t;
+  t.start();
   static_assert(Dimension <= 3, "unexpected dimension");
 
   LogicalConnectivityBuilder logical_connectivity_builder{cell_size};
@@ -117,6 +126,8 @@ CartesianMeshBuilder::_buildCartesianMesh(const TinyVector<Dimension>& a,
   NodeValue<TinyVector<Dimension>> xr = _getNodeCoordinates(a, b, cell_size, connectivity);
 
   m_mesh = std::make_shared<Mesh<ConnectivityType>>(p_connectivity, xr);
+
+  std::cout << "_buildCartesianMesh t = " << t << '\n';
 }
 
 template <size_t Dimension>
diff --git a/src/mesh/ConnectivityBuilderBase.cpp b/src/mesh/ConnectivityBuilderBase.cpp
index 55c56b3a4b78f28ecdf8613d04d4d0348b9e5c4a..9a02e30931998e629b1f6b480ff28191d6453405 100644
--- a/src/mesh/ConnectivityBuilderBase.cpp
+++ b/src/mesh/ConnectivityBuilderBase.cpp
@@ -7,8 +7,6 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 
-#include <map>
-#include <unordered_map>
 #include <vector>
 
 template <size_t Dimension>
@@ -16,240 +14,538 @@ void
 ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities(ConnectivityDescriptor& descriptor)
 {
   static_assert((Dimension == 2) or (Dimension == 3), "Invalid dimension to compute cell-face connectivities");
-  using CellFaceInfo = std::tuple<CellId, unsigned short, bool>;
-  using Face         = ConnectivityFace<Dimension>;
 
   const auto& node_number_vector = descriptor.node_number_vector;
-  Array<unsigned short> cell_nb_faces(descriptor.cell_to_node_vector.size());
-  std::map<Face, std::vector<CellFaceInfo>> face_cells_map;
+
+  size_t total_number_of_faces = 0;
+
   for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
     const auto& cell_nodes = descriptor.cell_to_node_vector[j];
 
     if constexpr (Dimension == 2) {
+      total_number_of_faces += cell_nodes.size();
+    } else if constexpr (Dimension == 3) {
       switch (descriptor.cell_type_vector[j]) {
-      case CellType::Triangle: {
-        cell_nb_faces[j] = 3;
-        // face 0
-        Face f0({cell_nodes[1], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[2], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+      case CellType::Hexahedron: {
+        total_number_of_faces += 6;
         break;
       }
-      case CellType::Quadrangle: {
-        cell_nb_faces[j] = 4;
-        // face 0
-        Face f0({cell_nodes[0], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[1], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[2], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
-
-        // face 3
-        Face f3({cell_nodes[3], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+      case CellType::Tetrahedron: {
+        total_number_of_faces += 4;
         break;
       }
-      case CellType::Polygon: {
-        cell_nb_faces[j] = cell_nodes.size();
-        for (size_t i = 0; i < cell_nodes.size(); ++i) {
-          Face f({cell_nodes[i], cell_nodes[(i + 1) % cell_nodes.size()]}, node_number_vector);
-          face_cells_map[f].emplace_back(std::make_tuple(j, i, f.reversed()));
-        }
+      case CellType::Prism: {
+        total_number_of_faces += 5;
+        break;
+      }
+      case CellType::Pyramid: {
+        total_number_of_faces += cell_nodes.size();
+        break;
+      }
+      case CellType::Diamond: {
+        total_number_of_faces += 2 * (cell_nodes.size() - 2);
         break;
       }
       default: {
         std::ostringstream error_msg;
-        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 2";
+        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
         throw UnexpectedError(error_msg.str());
       }
       }
-    } else if constexpr (Dimension == 3) {
-      switch (descriptor.cell_type_vector[j]) {
-      case CellType::Hexahedron: {
-        // face 0
-        Face f0({cell_nodes[3], cell_nodes[2], cell_nodes[1], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+    }
+  }
+
+  const size_t total_number_of_face_by_node = [&] {
+    if constexpr (Dimension == 2) {
+      return 2 * total_number_of_faces;
+    } else {
+      Assert(Dimension == 3);
+      size_t count_number_of_face_by_node = 0;
+      for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
+        switch (descriptor.cell_type_vector[j]) {
+        case CellType::Hexahedron: {
+          count_number_of_face_by_node += 6 * 4;
+          break;
+        }
+        case CellType::Tetrahedron: {
+          count_number_of_face_by_node += 4 * 3;
+          break;
+        }
+        case CellType::Prism: {
+          count_number_of_face_by_node += 3 * 4 + 2 * 3;
+          break;
+        }
+        case CellType::Pyramid: {
+          const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+          count_number_of_face_by_node += 1 * cell_nodes.size() + cell_nodes.size() * 3;
+          break;
+        }
+        case CellType::Diamond: {
+          const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+          count_number_of_face_by_node += cell_nodes.size() * 3 * 2;
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
+          throw UnexpectedError(error_msg.str());
+        }
+        }
+      }
+      return count_number_of_face_by_node;
+    }
+  }();
 
-        // face 1
-        Face f1({cell_nodes[4], cell_nodes[5], cell_nodes[6], cell_nodes[7]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+  Array<unsigned int> faces_node_list(total_number_of_face_by_node);
+  Array<unsigned int> face_ending(total_number_of_faces + 1);
+  size_t i_face  = 0;
+  face_ending[0] = 0;
 
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[4], cell_nodes[7], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+  Array<unsigned short> cell_nb_faces(descriptor.cell_to_node_vector.size());
+  {
+    size_t i_face_node = 0;
+    for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
+      const auto& cell_nodes = descriptor.cell_to_node_vector[j];
 
-        // face 3
-        Face f3({cell_nodes[1], cell_nodes[2], cell_nodes[6], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+      if constexpr (Dimension == 2) {
+        switch (descriptor.cell_type_vector[j]) {
+        case CellType::Triangle: {
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          cell_nb_faces[j] = 3;
+          break;
+        }
+        case CellType::Quadrangle: {
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          face_ending[i_face + 1] = face_ending[i_face] + 2;
+          i_face++;
+          cell_nb_faces[j] = 4;
+          break;
+        }
+        case CellType::Polygon: {
+          for (size_t i = 0; i < cell_nodes.size(); ++i) {
+            faces_node_list[i_face_node] = cell_nodes[i];
+            i_face_node++;
+            faces_node_list[i_face_node] = cell_nodes[(i + 1) % cell_nodes.size()];
+            i_face_node++;
+
+            face_ending[i_face + 1] = face_ending[i_face] + 2;
+            i_face++;
+          }
 
-        // face 4
-        Face f4({cell_nodes[0], cell_nodes[1], cell_nodes[5], cell_nodes[4]}, node_number_vector);
-        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+          cell_nb_faces[j] = cell_nodes.size();
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 2";
+          throw UnexpectedError(error_msg.str());
+        }
+        }
+      } else if constexpr (Dimension == 3) {
+        switch (descriptor.cell_type_vector[j]) {
+        case CellType::Hexahedron: {
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[4];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+          faces_node_list[i_face_node++] = cell_nodes[6];
+          faces_node_list[i_face_node++] = cell_nodes[7];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[4];
+          faces_node_list[i_face_node++] = cell_nodes[7];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[6];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+          faces_node_list[i_face_node++] = cell_nodes[4];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[7];
+          faces_node_list[i_face_node++] = cell_nodes[6];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          cell_nb_faces[j] = 6;
+          break;
+        }
+        case CellType::Tetrahedron: {
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[3];
 
-        // face 5
-        Face f5({cell_nodes[3], cell_nodes[7], cell_nodes[6], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f5].emplace_back(std::make_tuple(j, 5, f5.reversed()));
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
 
-        cell_nb_faces[j] = 6;
-        break;
-      }
-      case CellType::Tetrahedron: {
-        cell_nb_faces[j] = 4;
-        // face 0
-        Face f0({cell_nodes[1], cell_nodes[2], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[0], cell_nodes[3], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[1], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
-
-        // face 3
-        Face f3({cell_nodes[0], cell_nodes[2], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
-        break;
-      }
-      case CellType::Prism: {
-        // face 0
-        Face f0({cell_nodes[2], cell_nodes[1], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[2];
 
-        // face 1
-        Face f1({cell_nodes[3], cell_nodes[4], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
 
-        // face 2
-        Face f2({cell_nodes[1], cell_nodes[2], cell_nodes[5], cell_nodes[4]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[3];
 
-        // face 3
-        Face f3({cell_nodes[0], cell_nodes[1], cell_nodes[4], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
 
-        // face 4
-        Face f4({cell_nodes[2], cell_nodes[0], cell_nodes[3], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[1];
 
-        cell_nb_faces[j] = 5;
-        break;
-      }
-      case CellType::Pyramid: {
-        cell_nb_faces[j] = cell_nodes.size();
-        std::vector<unsigned int> base_nodes(cell_nodes.size() - 1);
-        for (size_t i = 0; i < base_nodes.size(); ++i) {
-          base_nodes[base_nodes.size() - 1 - i] = cell_nodes[i];
-        }
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
 
-        // base face
-        {
-          Face base_face(base_nodes, node_number_vector);
-          face_cells_map[base_face].emplace_back(std::make_tuple(j, 0, base_face.reversed()));
+          cell_nb_faces[j] = 4;
+          break;
         }
-        // side faces
-        const auto pyramid_vertex = cell_nodes[cell_nodes.size() - 1];
-        for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-          Face side_face({base_nodes[(i_node + 1) % base_nodes.size()], base_nodes[i_node], pyramid_vertex},
-                         node_number_vector);
-          face_cells_map[side_face].emplace_back(std::make_tuple(j, i_node + 1, side_face.reversed()));
+        case CellType::Prism: {
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[4];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 3;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+          faces_node_list[i_face_node++] = cell_nodes[4];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[1];
+          faces_node_list[i_face_node++] = cell_nodes[4];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          faces_node_list[i_face_node++] = cell_nodes[2];
+          faces_node_list[i_face_node++] = cell_nodes[0];
+          faces_node_list[i_face_node++] = cell_nodes[3];
+          faces_node_list[i_face_node++] = cell_nodes[5];
+
+          face_ending[i_face + 1] = face_ending[i_face] + 4;
+          i_face++;
+
+          cell_nb_faces[j] = 5;
+          break;
         }
-        break;
-      }
-      case CellType::Diamond: {
-        cell_nb_faces[j] = 2 * (cell_nodes.size() - 2);
-        std::vector<unsigned int> base_nodes;
-        std::copy_n(cell_nodes.begin() + 1, cell_nodes.size() - 2, std::back_inserter(base_nodes));
+        case CellType::Pyramid: {
+          cell_nb_faces[j] = cell_nodes.size();
+          std::vector<unsigned int> base_nodes(cell_nodes.size() - 1);
+          for (size_t i = 0; i < base_nodes.size(); ++i) {
+            base_nodes[base_nodes.size() - 1 - i] = cell_nodes[i];
+          }
 
-        {   // top faces
-          const auto top_vertex = cell_nodes[cell_nodes.size() - 1];
+          for (size_t i = 0; i < base_nodes.size(); ++i) {
+            faces_node_list[i_face_node++] = base_nodes[i];
+          }
+
+          face_ending[i_face + 1] = face_ending[i_face] + base_nodes.size();
+          i_face++;
+
+          // side faces
+          const auto pyramid_vertex = cell_nodes[cell_nodes.size() - 1];
           for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-            Face top_face({base_nodes[i_node], base_nodes[(i_node + 1) % base_nodes.size()], top_vertex},
-                          node_number_vector);
-            face_cells_map[top_face].emplace_back(std::make_tuple(j, i_node, top_face.reversed()));
+            faces_node_list[i_face_node++] = base_nodes[(i_node + 1) % base_nodes.size()];
+            faces_node_list[i_face_node++] = base_nodes[i_node];
+            faces_node_list[i_face_node++] = pyramid_vertex;
+
+            face_ending[i_face + 1] = face_ending[i_face] + 3;
+            i_face++;
           }
+          break;
         }
+        case CellType::Diamond: {
+          std::vector<unsigned int> base_nodes;
+          std::copy_n(cell_nodes.begin() + 1, cell_nodes.size() - 2, std::back_inserter(base_nodes));
+
+          {   // top faces
+            const auto top_vertex = cell_nodes[cell_nodes.size() - 1];
+            for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
+              faces_node_list[i_face_node++] = base_nodes[i_node];
+              faces_node_list[i_face_node++] = base_nodes[(i_node + 1) % base_nodes.size()];
+              faces_node_list[i_face_node++] = top_vertex;
+
+              face_ending[i_face + 1] = face_ending[i_face] + 3;
+              i_face++;
+            }
+          }
 
-        {   // bottom faces
-          const auto bottom_vertex = cell_nodes[0];
-          for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-            Face bottom_face({base_nodes[(i_node + 1) % base_nodes.size()], base_nodes[i_node], bottom_vertex},
-                             node_number_vector);
-            face_cells_map[bottom_face].emplace_back(
-              std::make_tuple(j, i_node + base_nodes.size(), bottom_face.reversed()));
+          {   // bottom faces
+            const auto bottom_vertex = cell_nodes[0];
+            for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
+              faces_node_list[i_face_node++] = base_nodes[(i_node + 1) % base_nodes.size()];
+              faces_node_list[i_face_node++] = base_nodes[i_node];
+              faces_node_list[i_face_node++] = bottom_vertex;
+
+              face_ending[i_face + 1] = face_ending[i_face] + 3;
+              i_face++;
+            }
           }
+          cell_nb_faces[j] = 2 * (cell_nodes.size() - 2);
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
+          throw UnexpectedError(error_msg.str());
+        }
         }
-        break;
       }
-      default: {
-        std::ostringstream error_msg;
-        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
-        throw UnexpectedError(error_msg.str());
+    }
+  }
+
+  Array<bool> face_reversed(total_number_of_faces);
+
+  if constexpr (Dimension == 2) {
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      if (node_number_vector[faces_node_list[2 * i_face]] > node_number_vector[faces_node_list[2 * i_face + 1]]) {
+        std::swap(faces_node_list[2 * i_face], faces_node_list[2 * i_face + 1]);
+        face_reversed[i_face] = true;
+      } else {
+        face_reversed[i_face] = false;
+      }
+    }
+  } else if constexpr (Dimension == 3) {
+    std::vector<int> buffer;
+
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      const size_t face_node_number      = face_ending[i_face + 1] - face_ending[i_face];
+      size_t i_face_node_smallest_number = 0;
+      for (size_t i_face_node = 1; i_face_node < face_node_number; ++i_face_node) {
+        if (node_number_vector[faces_node_list[face_ending[i_face] + i_face_node]] <
+            node_number_vector[faces_node_list[face_ending[i_face] + i_face_node_smallest_number]]) {
+          i_face_node_smallest_number = i_face_node;
+        }
       }
+
+      if (i_face_node_smallest_number != 0) {
+        buffer.resize(face_node_number);
+        for (size_t i_node = i_face_node_smallest_number; i_node < face_node_number; ++i_node) {
+          buffer[i_node - i_face_node_smallest_number] = faces_node_list[face_ending[i_face] + i_node];
+        }
+        for (size_t i_node = 0; i_node < i_face_node_smallest_number; ++i_node) {
+          buffer[i_node + face_node_number - i_face_node_smallest_number] =
+            faces_node_list[face_ending[i_face] + i_node];
+        }
+
+        for (size_t i_node = 0; i_node < face_node_number; ++i_node) {
+          faces_node_list[face_ending[i_face] + i_node] = buffer[i_node];
+        }
+      }
+
+      if (node_number_vector[faces_node_list[face_ending[i_face] + 1]] >
+          node_number_vector[faces_node_list[face_ending[i_face + 1] - 1]]) {
+        for (size_t i_node = 1; i_node <= (face_node_number + 1) / 2 - 1; ++i_node) {
+          std::swap(faces_node_list[face_ending[i_face] + i_node], faces_node_list[face_ending[i_face + 1] - i_node]);
+        }
+
+        face_reversed[i_face] = true;
+      } else {
+        face_reversed[i_face] = false;
       }
     }
   }
 
-  {
-    descriptor.cell_to_face_vector.resize(descriptor.cell_to_node_vector.size());
-    for (CellId j = 0; j < descriptor.cell_to_face_vector.size(); ++j) {
-      descriptor.cell_to_face_vector[j].resize(cell_nb_faces[j]);
-    }
-    FaceId l = 0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj = 0; lj < cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed]         = cells_vector[lj];
-        descriptor.cell_to_face_vector[cell_number][cell_local_face] = l;
-      }
-      ++l;
+  Array<unsigned int> node_to_duplicate_face_row(node_number_vector.size() + 1);
+  node_to_duplicate_face_row.fill(0);
+  for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+    for (size_t i_node_face = face_ending[i_face]; i_node_face < face_ending[i_face + 1]; ++i_node_face) {
+      node_to_duplicate_face_row[faces_node_list[i_node_face] + 1]++;
     }
   }
 
+  for (size_t i_node = 1; i_node < node_to_duplicate_face_row.size(); ++i_node) {
+    node_to_duplicate_face_row[i_node] += node_to_duplicate_face_row[i_node - 1];
+  }
+
+  Array<unsigned int> node_duplicate_face_list(node_to_duplicate_face_row[node_to_duplicate_face_row.size() - 1]);
+
   {
-    descriptor.cell_face_is_reversed_vector.resize(descriptor.cell_to_node_vector.size());
-    for (CellId j = 0; j < descriptor.cell_face_is_reversed_vector.size(); ++j) {
-      descriptor.cell_face_is_reversed_vector[j] = Array<bool>(cell_nb_faces[j]);
+    Array<unsigned int> node_face_row_idx(node_number_vector.size());
+    node_face_row_idx.fill(0);
+
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      for (size_t i_node_face = face_ending[i_face]; i_node_face < face_ending[i_face + 1]; ++i_node_face) {
+        const size_t node_id = faces_node_list[i_node_face];
+        node_duplicate_face_list[node_to_duplicate_face_row[node_id] + node_face_row_idx[node_id]] = i_face;
+        node_face_row_idx[node_id]++;
+      }
     }
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj = 0; lj < cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed]                  = cells_vector[lj];
-        descriptor.cell_face_is_reversed_vector[cell_number][cell_local_face] = reversed;
+  }
+
+  Array<unsigned int> dup_face_to_face(total_number_of_faces);
+  parallel_for(
+    total_number_of_faces, PUGS_LAMBDA(size_t i_face) { dup_face_to_face[i_face] = i_face; });
+
+  auto is_same_face = [=](const size_t i_face, const size_t j_face) {
+    if ((face_ending[i_face + 1] - face_ending[i_face]) != (face_ending[j_face + 1] - face_ending[j_face])) {
+      return false;
+    } else {
+      auto i_face_node = face_ending[i_face];
+      auto j_face_node = face_ending[j_face];
+      while (i_face_node < face_ending[i_face + 1]) {
+        if (faces_node_list[i_face_node] != faces_node_list[j_face_node]) {
+          return false;
+        }
+        i_face_node++;
+        j_face_node++;
+      }
+      return true;
+    }
+  };
+
+  size_t nb_dup_faces = 0;
+  for (size_t i_node = 0; i_node < node_number_vector.size(); ++i_node) {
+    for (size_t i_node_face = node_to_duplicate_face_row[i_node];
+         i_node_face < node_to_duplicate_face_row[i_node + 1] - 1; ++i_node_face) {
+      for (size_t j_node_face = i_node_face + 1; j_node_face < node_to_duplicate_face_row[i_node + 1]; ++j_node_face) {
+        if (dup_face_to_face[node_duplicate_face_list[i_node_face]] ==
+            dup_face_to_face[node_duplicate_face_list[j_node_face]])
+          continue;
+        if (is_same_face(node_duplicate_face_list[i_node_face], node_duplicate_face_list[j_node_face])) {
+          dup_face_to_face[node_duplicate_face_list[j_node_face]] =
+            dup_face_to_face[node_duplicate_face_list[i_node_face]];
+          nb_dup_faces++;
+        }
       }
     }
   }
 
+  // compute face_id
+  Array<FaceId> dup_face_to_face_id(total_number_of_faces);
   {
-    descriptor.face_to_node_vector.resize(face_cells_map.size());
-    int l = 0;
-    for (const auto& face_info : face_cells_map) {
-      const Face& face                  = face_info.first;
-      descriptor.face_to_node_vector[l] = face.nodeIdList();
-      ++l;
+    FaceId face_id = 0;
+    for (size_t i_dup_face = 0; i_dup_face < total_number_of_faces; ++i_dup_face) {
+      if (dup_face_to_face[i_dup_face] == i_dup_face) {
+        dup_face_to_face_id[i_dup_face] = face_id;
+        ++face_id;
+      } else {
+        size_t i_face = dup_face_to_face[i_dup_face];
+        while (i_face != dup_face_to_face[i_face]) {
+          i_face = dup_face_to_face[i_face];
+        }
+        dup_face_to_face_id[i_dup_face] = dup_face_to_face_id[i_face];
+      }
+    }
+  }
+
+  Array<unsigned int> cell_face_begin(cell_nb_faces.size() + 1);
+  cell_face_begin[0] = 0;
+  for (size_t i = 0; i < cell_nb_faces.size(); ++i) {
+    cell_face_begin[i + 1] = cell_face_begin[i] + cell_nb_faces[i];
+  }
+
+  descriptor.cell_to_face_vector.resize(descriptor.cell_to_node_vector.size());
+  for (CellId j = 0; j < descriptor.cell_to_face_vector.size(); ++j) {
+    descriptor.cell_to_face_vector[j].resize(cell_nb_faces[j]);
+
+    for (size_t i_face = 0; i_face < cell_nb_faces[j]; ++i_face) {
+      descriptor.cell_to_face_vector[j][i_face] = dup_face_to_face_id[cell_face_begin[j] + i_face];
     }
   }
 
   {
     // Face numbers may change if numbers are provided in the file
-    descriptor.face_number_vector.resize(face_cells_map.size());
-    for (size_t l = 0; l < face_cells_map.size(); ++l) {
+    descriptor.face_number_vector.resize(total_number_of_faces - nb_dup_faces);
+    for (size_t l = 0; l < descriptor.face_number_vector.size(); ++l) {
       descriptor.face_number_vector[l] = l;
     }
   }
+
+  {
+    descriptor.face_to_node_vector.resize(total_number_of_faces - nb_dup_faces);
+    int l = 0;
+
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      if (dup_face_to_face[i_face] == i_face) {
+        descriptor.face_to_node_vector[l].resize(face_ending[i_face + 1] - face_ending[i_face]);
+
+        for (size_t i_face_node = 0; i_face_node < face_ending[i_face + 1] - face_ending[i_face]; ++i_face_node) {
+          descriptor.face_to_node_vector[l][i_face_node] = faces_node_list[face_ending[i_face] + i_face_node];
+        }
+        l++;
+      }
+    }
+  }
+
+  {
+    descriptor.cell_face_is_reversed_vector.resize(descriptor.cell_to_node_vector.size());
+    size_t l = 0;
+    for (CellId j = 0; j < descriptor.cell_face_is_reversed_vector.size(); ++j) {
+      descriptor.cell_face_is_reversed_vector[j].resize(cell_nb_faces[j]);
+      for (size_t i_face = 0; i_face < cell_nb_faces[j]; ++i_face) {
+        descriptor.cell_face_is_reversed_vector[j][i_face] = face_reversed[l];
+        ++l;
+      }
+    }
+  }
 }
 
 template <size_t Dimension>
@@ -257,76 +553,248 @@ void
 ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(ConnectivityDescriptor& descriptor)
 {
   static_assert(Dimension == 3, "Invalid dimension to compute face-edge connectivities");
-  using FaceEdgeInfo = std::tuple<FaceId, unsigned short, bool>;
-  using Edge         = ConnectivityFace<2>;
 
-  const auto& node_number_vector = descriptor.node_number_vector;
   Array<unsigned short> face_nb_edges(descriptor.face_to_node_vector.size());
-  std::map<Edge, std::vector<FaceEdgeInfo>> edge_faces_map;
-  for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-    const auto& face_nodes = descriptor.face_to_node_vector[l];
-
-    face_nb_edges[l] = face_nodes.size();
-    for (size_t r = 0; r < face_nodes.size() - 1; ++r) {
-      Edge e({face_nodes[r], face_nodes[r + 1]}, node_number_vector);
-      edge_faces_map[e].emplace_back(std::make_tuple(l, r, e.reversed()));
-    }
-    {
-      Edge e({face_nodes[face_nodes.size() - 1], face_nodes[0]}, node_number_vector);
-      edge_faces_map[e].emplace_back(std::make_tuple(l, face_nodes.size() - 1, e.reversed()));
+  {
+    for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
+      const auto& face_nodes = descriptor.face_to_node_vector[l];
+
+      face_nb_edges[l] = face_nodes.size();
     }
   }
 
-  std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_id_map;
+  Array<unsigned int> face_edge_ending(face_nb_edges.size() + 1);
+  face_edge_ending[0] = 0;
+  for (size_t i_face = 0; i_face < face_nb_edges.size(); ++i_face) {
+    face_edge_ending[i_face + 1] = face_edge_ending[i_face] + face_nb_edges[i_face];
+  }
+
+  const size_t total_number_of_face_edges = face_edge_ending[face_edge_ending.size() - 1];
+
+  const size_t total_number_of_node_by_face_edges = 2 * total_number_of_face_edges;
+
+  Array<unsigned int> face_edges_node_list(total_number_of_node_by_face_edges);
   {
-    descriptor.face_to_edge_vector.resize(descriptor.face_to_node_vector.size());
-    for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-      descriptor.face_to_edge_vector[l].resize(face_nb_edges[l]);
+    size_t i_edge_node = 0;
+    for (size_t i_face = 0; i_face < face_nb_edges.size(); ++i_face) {
+      for (size_t i_node = 0; i_node < descriptor.face_to_node_vector[i_face].size() - 1; ++i_node) {
+        face_edges_node_list[i_edge_node++] = descriptor.face_to_node_vector[i_face][i_node];
+        face_edges_node_list[i_edge_node++] = descriptor.face_to_node_vector[i_face][i_node + 1];
+      }
+      face_edges_node_list[i_edge_node++] =
+        descriptor.face_to_node_vector[i_face][descriptor.face_to_node_vector[i_face].size() - 1];
+      face_edges_node_list[i_edge_node++] = descriptor.face_to_node_vector[i_face][0];
     }
-    EdgeId e = 0;
-    for (const auto& edge_faces_vector : edge_faces_map) {
-      const auto& faces_vector = edge_faces_vector.second;
-      for (unsigned short l = 0; l < faces_vector.size(); ++l) {
-        const auto& [face_number, face_local_edge, reversed]         = faces_vector[l];
-        descriptor.face_to_edge_vector[face_number][face_local_edge] = e;
-      }
-      edge_id_map[edge_faces_vector.first] = e;
-      ++e;
+  }
+
+  Array<bool> face_edge_is_reversed(total_number_of_face_edges);
+
+  for (size_t i_edge = 0; i_edge < total_number_of_face_edges; ++i_edge) {
+    if (descriptor.node_number_vector[face_edges_node_list[2 * i_edge]] >
+        descriptor.node_number_vector[face_edges_node_list[2 * i_edge + 1]]) {
+      std::swap(face_edges_node_list[2 * i_edge], face_edges_node_list[2 * i_edge + 1]);
+      face_edge_is_reversed[i_edge] = true;
+    } else {
+      face_edge_is_reversed[i_edge] = false;
     }
   }
 
+  const size_t total_number_of_edges = face_edge_is_reversed.size();
+  Array<unsigned int> node_to_face_edge_row(descriptor.node_number_vector.size() + 1);
+  node_to_face_edge_row.fill(0);
+  for (size_t i_edge = 0; i_edge < total_number_of_edges; ++i_edge) {
+    for (size_t i_edge_node = 0; i_edge_node < 2; ++i_edge_node) {
+      node_to_face_edge_row[face_edges_node_list[2 * i_edge + i_edge_node] + 1]++;
+    }
+  }
+  for (size_t i_node = 1; i_node < node_to_face_edge_row.size(); ++i_node) {
+    node_to_face_edge_row[i_node] += node_to_face_edge_row[i_node - 1];
+  }
+
+  Array<unsigned int> node_to_face_edge_list(node_to_face_edge_row[node_to_face_edge_row.size() - 1]);
+
   {
-    descriptor.face_edge_is_reversed_vector.resize(descriptor.face_to_node_vector.size());
-    for (FaceId j = 0; j < descriptor.face_edge_is_reversed_vector.size(); ++j) {
-      descriptor.face_edge_is_reversed_vector[j] = Array<bool>(face_nb_edges[j]);
+    Array<unsigned int> node_to_face_edge_row_idx(descriptor.node_number_vector.size());
+    node_to_face_edge_row_idx.fill(0);
+
+    for (size_t i_edge = 0; i_edge < total_number_of_edges; ++i_edge) {
+      for (size_t i_edge_node = 0; i_edge_node < 2; ++i_edge_node) {
+        const size_t node_id = face_edges_node_list[2 * i_edge + i_edge_node];
+
+        node_to_face_edge_list[node_to_face_edge_row[node_id] + node_to_face_edge_row_idx[node_id]] = i_edge;
+        node_to_face_edge_row_idx[node_id]++;
+      }
     }
-    for (const auto& edge_faces_vector : edge_faces_map) {
-      const auto& faces_vector = edge_faces_vector.second;
-      for (unsigned short lj = 0; lj < faces_vector.size(); ++lj) {
-        const auto& [face_number, face_local_edge, reversed]                  = faces_vector[lj];
-        descriptor.face_edge_is_reversed_vector[face_number][face_local_edge] = reversed;
+  }
+
+  Array<unsigned int> face_edge_to_edge(total_number_of_edges);
+  parallel_for(
+    total_number_of_edges, PUGS_LAMBDA(size_t i_edge) { face_edge_to_edge[i_edge] = i_edge; });
+
+  auto is_same_face = [=](const size_t i_edge, const size_t j_edge) {
+    auto i_edge_first_node = 2 * i_edge;
+    auto j_edge_first_node = 2 * j_edge;
+    return ((face_edges_node_list[i_edge_first_node] == face_edges_node_list[j_edge_first_node]) and
+            (face_edges_node_list[i_edge_first_node + 1] == face_edges_node_list[j_edge_first_node + 1]));
+  };
+
+  const auto& node_number_vector = descriptor.node_number_vector;
+  size_t nb_duplicate_edges      = 0;
+  for (size_t i_node = 0; i_node < node_number_vector.size(); ++i_node) {
+    for (size_t i_node_face = node_to_face_edge_row[i_node]; i_node_face < node_to_face_edge_row[i_node + 1] - 1;
+         ++i_node_face) {
+      for (size_t j_node_face = i_node_face + 1; j_node_face < node_to_face_edge_row[i_node + 1]; ++j_node_face) {
+        if (face_edge_to_edge[node_to_face_edge_list[i_node_face]] ==
+            face_edge_to_edge[node_to_face_edge_list[j_node_face]])
+          continue;
+        if (is_same_face(node_to_face_edge_list[i_node_face], node_to_face_edge_list[j_node_face])) {
+          face_edge_to_edge[node_to_face_edge_list[j_node_face]] =
+            face_edge_to_edge[node_to_face_edge_list[i_node_face]];
+          nb_duplicate_edges++;
+        }
       }
     }
   }
 
+  // compute edge_id
+  Array<EdgeId> dup_edge_to_edge_id(total_number_of_edges);
   {
-    descriptor.edge_to_node_vector.resize(edge_faces_map.size());
-    int e = 0;
-    for (const auto& edge_info : edge_faces_map) {
-      const Edge& edge                  = edge_info.first;
-      descriptor.edge_to_node_vector[e] = edge.nodeIdList();
-      ++e;
+    EdgeId edge_id = 0;
+    for (size_t i_dup_edge = 0; i_dup_edge < total_number_of_edges; ++i_dup_edge) {
+      if (face_edge_to_edge[i_dup_edge] == i_dup_edge) {
+        dup_edge_to_edge_id[i_dup_edge] = edge_id++;
+      } else {
+        size_t i_edge = i_dup_edge;
+        do {
+          i_edge = face_edge_to_edge[i_edge];
+        } while (i_edge != face_edge_to_edge[i_edge]);
+
+        dup_edge_to_edge_id[i_dup_edge] = dup_edge_to_edge_id[i_edge];
+      }
+    }
+  }
+
+  Array<NodeId> edge_to_node_list(2 * (total_number_of_edges - nb_duplicate_edges));
+  {
+    for (size_t i_dup_edge = 0; i_dup_edge < total_number_of_edges; ++i_dup_edge) {
+      if (face_edge_to_edge[i_dup_edge] == i_dup_edge) {
+        const EdgeId edge_id               = dup_edge_to_edge_id[i_dup_edge];
+        edge_to_node_list[2 * edge_id]     = face_edges_node_list[2 * i_dup_edge];
+        edge_to_node_list[2 * edge_id + 1] = face_edges_node_list[2 * i_dup_edge + 1];
+      }
     }
   }
 
   {
     // Edge numbers may change if numbers are provided in the file
-    descriptor.edge_number_vector.resize(edge_faces_map.size());
-    for (size_t e = 0; e < edge_faces_map.size(); ++e) {
-      descriptor.edge_number_vector[e] = e;
+    descriptor.edge_number_vector.resize(total_number_of_edges - nb_duplicate_edges);
+    for (size_t i_edge = 0; i_edge < descriptor.edge_number_vector.size(); ++i_edge) {
+      descriptor.edge_number_vector[i_edge] = i_edge;
+    }
+  }
+
+  descriptor.edge_to_node_vector.resize(total_number_of_edges - nb_duplicate_edges);
+  {
+    for (EdgeId edge_id = 0; edge_id < total_number_of_edges - nb_duplicate_edges; ++edge_id) {
+      descriptor.edge_to_node_vector[edge_id].resize(2);
+
+      for (size_t i_edge_node = 0; i_edge_node < 2; ++i_edge_node) {
+        descriptor.edge_to_node_vector[edge_id][i_edge_node] = edge_to_node_list[2 * edge_id + i_edge_node];
+      }
+    }
+  }
+
+  // Use real edge ids
+  for (size_t i_edge = 0; i_edge < face_edge_to_edge.size(); ++i_edge) {
+    face_edge_to_edge[i_edge] = dup_edge_to_edge_id[face_edge_to_edge[i_edge]];
+  }
+
+  descriptor.face_to_edge_vector.resize(descriptor.face_to_node_vector.size());
+  {
+    size_t i_edge = 0;
+    for (FaceId face_id = 0; face_id < descriptor.face_to_edge_vector.size(); ++face_id) {
+      descriptor.face_to_edge_vector[face_id].resize(descriptor.face_to_node_vector[face_id].size());
+
+      for (size_t i_face_edge = 0; i_face_edge < descriptor.face_to_edge_vector[face_id].size(); ++i_face_edge) {
+        descriptor.face_to_edge_vector[face_id][i_face_edge] = face_edge_to_edge[i_edge++];
+      }
     }
   }
 
+  descriptor.face_edge_is_reversed_vector.resize(descriptor.face_to_node_vector.size());
+  {
+    size_t i_edge = 0;
+    for (FaceId face_id = 0; face_id < descriptor.face_edge_is_reversed_vector.size(); ++face_id) {
+      descriptor.face_edge_is_reversed_vector[face_id].resize(descriptor.face_to_node_vector[face_id].size());
+
+      for (size_t i_face_edge = 0; i_face_edge < descriptor.face_edge_is_reversed_vector[face_id].size();
+           ++i_face_edge) {
+        descriptor.face_edge_is_reversed_vector[face_id][i_face_edge] = face_edge_is_reversed[i_edge++];
+      }
+    }
+  }
+
+  Array<size_t> node_to_duplicated_edge_id_list(node_to_face_edge_list.size());
+  for (size_t i_node_edge = 0; i_node_edge < node_to_duplicated_edge_id_list.size(); ++i_node_edge) {
+    node_to_duplicated_edge_id_list[i_node_edge] = dup_edge_to_edge_id[node_to_face_edge_list[i_node_edge]];
+  }
+
+  Array<bool> node_to_edge_is_duplicated(node_to_face_edge_list.size());
+  node_to_edge_is_duplicated.fill(false);
+
+  Array<unsigned int> node_nb_edges(descriptor.node_number_vector.size());
+  node_nb_edges.fill(0);
+
+  for (size_t node_id = 0; node_id < descriptor.node_number_vector.size(); ++node_id) {
+    size_t nb_dup_edges = 0;
+    for (EdgeId i_edge = node_to_face_edge_row[node_id]; i_edge < node_to_face_edge_row[node_id + 1] - 1; ++i_edge) {
+      if (not node_to_edge_is_duplicated[i_edge]) {
+        for (EdgeId j_edge = i_edge + 1; j_edge < node_to_face_edge_row[node_id + 1]; ++j_edge) {
+          if (node_to_duplicated_edge_id_list[i_edge] == node_to_duplicated_edge_id_list[j_edge]) {
+            node_to_edge_is_duplicated[j_edge] = true;
+            nb_dup_edges++;
+          }
+        }
+      }
+    }
+    node_nb_edges[node_id] = node_to_face_edge_row[node_id + 1] - node_to_face_edge_row[node_id] - nb_dup_edges;
+  }
+
+  Array<unsigned int> node_to_edge_row(descriptor.node_number_vector.size() + 1);
+  node_to_edge_row[0] = 0;
+  for (size_t node_id = 0; node_id < descriptor.node_number_vector.size(); ++node_id) {
+    node_to_edge_row[node_id + 1] = node_to_edge_row[node_id] + node_nb_edges[node_id];
+  }
+
+  Array<EdgeId> node_to_edge_list(node_to_edge_row[node_to_edge_row.size() - 1]);
+  {
+    size_t l = 0;
+    for (size_t i_edge_id = 0; i_edge_id < node_to_duplicated_edge_id_list.size(); ++i_edge_id) {
+      if (not node_to_edge_is_duplicated[i_edge_id]) {
+        node_to_edge_list[l++] = node_to_duplicated_edge_id_list[i_edge_id];
+      }
+    }
+  }
+
+  auto find_edge = [=](const NodeId& node0_id, const NodeId& node1_id) -> EdgeId {
+    auto [first_node_id, second_node_id] = [&] {
+      if (descriptor.node_number_vector[node0_id] < descriptor.node_number_vector[node1_id]) {
+        return std::make_pair(node0_id, node1_id);
+      } else {
+        return std::make_pair(node1_id, node0_id);
+      }
+    }();
+
+    for (size_t i_node_edge = node_to_edge_row[first_node_id]; i_node_edge < node_to_edge_row[first_node_id + 1];
+         ++i_node_edge) {
+      EdgeId edge_id = node_to_edge_list[i_node_edge];
+      if (edge_to_node_list[2 * edge_id + 1] == second_node_id) {
+        return edge_id;
+      }
+    }
+    throw UnexpectedError("Cannot find cell edge in edge list");
+  };
+
   {
     descriptor.cell_to_edge_vector.reserve(descriptor.cell_to_node_vector.size());
     for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
@@ -338,13 +806,9 @@ ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(Co
         std::vector<unsigned int> cell_edge_vector;
         cell_edge_vector.reserve(6);
         for (int i_edge = 0; i_edge < 6; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const auto& e        = local_edge[i_edge];
+          const EdgeId edge_id = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
+          cell_edge_vector.push_back(edge_id);
         }
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
         break;
@@ -355,29 +819,21 @@ ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(Co
         std::vector<unsigned int> cell_edge_vector;
         cell_edge_vector.reserve(12);
         for (int i_edge = 0; i_edge < 12; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const auto& e        = local_edge[i_edge];
+          const EdgeId edge_id = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
+          cell_edge_vector.push_back(edge_id);
         }
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
         break;
       }
       case CellType::Prism: {
-        constexpr int local_edge[12][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}};
+        constexpr int local_edge[9][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}};
         std::vector<unsigned int> cell_edge_vector;
         cell_edge_vector.reserve(9);
         for (int i_edge = 0; i_edge < 9; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const auto& e        = local_edge[i_edge];
+          const EdgeId edge_id = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
+          cell_edge_vector.push_back(edge_id);
         }
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
         break;
@@ -389,23 +845,16 @@ ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(Co
 
         std::vector<unsigned int> cell_edge_vector;
         cell_edge_vector.reserve(number_of_edges);
+
         for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const EdgeId edge_id = find_edge(base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]);
+          cell_edge_vector.push_back(edge_id);
         }
 
         const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
         for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], top_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const EdgeId edge_id = find_edge(base_nodes[i_edge], top_vertex);
+          cell_edge_vector.push_back(edge_id);
         }
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
         break;
@@ -417,37 +866,29 @@ ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(Co
 
         std::vector<unsigned int> cell_edge_vector;
         cell_edge_vector.reserve(number_of_edges);
+
         for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+          const EdgeId edge_id = find_edge(base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]);
+          cell_edge_vector.push_back(edge_id);
         }
 
-        const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], top_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+        {
+          const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
+          for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
+            const EdgeId edge_id = find_edge(base_nodes[i_edge], top_vertex);
+            cell_edge_vector.push_back(edge_id);
           }
-          cell_edge_vector.push_back(i->second);
         }
 
-        const unsigned int bottom_vertex = cell_nodes[0];
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], bottom_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+        {
+          const unsigned int bottom_vertex = cell_nodes[0];
+          for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
+            const EdgeId edge_id = find_edge(base_nodes[i_edge], bottom_vertex);
+            cell_edge_vector.push_back(edge_id);
           }
-          cell_edge_vector.push_back(i->second);
         }
 
         descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
-
         break;
       }
       default: {
diff --git a/src/mesh/ConnectivityDescriptor.hpp b/src/mesh/ConnectivityDescriptor.hpp
index 8f26988eadaa01dfec8aa0f14404a82819273475..0c7f60b38b9b83e95a244fb5c7d4c4b1d13bfeaf 100644
--- a/src/mesh/ConnectivityDescriptor.hpp
+++ b/src/mesh/ConnectivityDescriptor.hpp
@@ -47,8 +47,8 @@ class ConnectivityDescriptor
     }
   }
 
-  std::vector<Array<bool>> cell_face_is_reversed_vector;
-  std::vector<Array<bool>> face_edge_is_reversed_vector;
+  std::vector<std::vector<bool>> cell_face_is_reversed_vector;
+  std::vector<std::vector<bool>> face_edge_is_reversed_vector;
 
   std::vector<CellType> cell_type_vector;
 
diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
index fd5b58c6daaf5fd44c0cb440e7af472b9f9aebbf..2cd8965f2929a8453d4f53b5d692c981fcf44906 100644
--- a/src/mesh/ConnectivityDispatcher.cpp
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -167,7 +167,7 @@ template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
 void
 ConnectivityDispatcher<Dimension>::_gatherFrom(
   const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
-  std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector)
+  std::vector<std::vector<std::remove_const_t<DataType>>>& gathered_vector)
 {
   using MutableDataType = std::remove_const_t<DataType>;
 
@@ -206,7 +206,7 @@ ConnectivityDispatcher<Dimension>::_gatherFrom(
   for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
     int l = 0;
     for (size_t i = 0; i < item_list_to_recv_size_by_proc[i_rank]; ++i) {
-      Array<MutableDataType> data_vector(number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]);
+      std::vector<MutableDataType> data_vector(number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]);
       for (size_t k = 0; k < data_vector.size(); ++k) {
         data_vector[k] = recv_data_to_gather_by_proc[i_rank][l++];
       }
diff --git a/src/mesh/ConnectivityDispatcher.hpp b/src/mesh/ConnectivityDispatcher.hpp
index 73ebc548d98a01e4999611927272206df4af1c23..ef1f385bcd4676a0c1afe1c9a6a53989830712bf 100644
--- a/src/mesh/ConnectivityDispatcher.hpp
+++ b/src/mesh/ConnectivityDispatcher.hpp
@@ -184,7 +184,7 @@ class ConnectivityDispatcher
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
   void _gatherFrom(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
-                   std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector);
+                   std::vector<std::vector<std::remove_const_t<DataType>>>& gathered_vector);
 
   template <typename SubItemOfItemT>
   void _buildNumberOfSubItemPerItemToRecvByProc();
diff --git a/src/mesh/GmshReader.cpp b/src/mesh/GmshReader.cpp
index e555ca224c1d02f11e11696542774f2e470b6432..c033433f807b2313c2dab4132f9ecc287ad413fc 100644
--- a/src/mesh/GmshReader.cpp
+++ b/src/mesh/GmshReader.cpp
@@ -561,8 +561,8 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
 
     for (size_t i_face = 0; i_face < face_nb_cell.size(); ++i_face) {
       if (face_nb_cell[i_face] == 1) {
-        for (size_t node_id : descriptor.face_to_edge_vector[i_face]) {
-          is_boundary_edge[node_id] = true;
+        for (size_t edge_id : descriptor.face_to_edge_vector[i_face]) {
+          is_boundary_edge[edge_id] = true;
         }
       }
     }
diff --git a/src/mesh/LogicalConnectivityBuilder.cpp b/src/mesh/LogicalConnectivityBuilder.cpp
index 41092faaf90a9a3f9a24ac1c561304373ba748d0..5314f956c27de580ee227f8f8519648d4b105629 100644
--- a/src/mesh/LogicalConnectivityBuilder.cpp
+++ b/src/mesh/LogicalConnectivityBuilder.cpp
@@ -6,6 +6,8 @@
 #include <utils/Array.hpp>
 #include <utils/Messenger.hpp>
 
+#include <utils/Timer.hpp>
+
 #include <unordered_map>
 
 template <size_t Dimension>
@@ -535,6 +537,9 @@ template <>
 void
 LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& cell_size)
 {
+  Timer t;
+  t.start();
+
   constexpr size_t Dimension = 3;
 
   const TinyVector<Dimension, uint64_t> node_size = [&] {
@@ -586,6 +591,10 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
     return (node_logic_id[0] * node_size[1] + node_logic_id[1]) * node_size[2] + node_logic_id[2];
   };
 
+  std::cout << "LogicalConnectivityBuilder:\n - prepare t = " << t << '\n';
+  t.stop();
+  t.start();
+
   descriptor.cell_to_node_vector.resize(number_of_cells);
   constexpr size_t nb_node_per_cell = 1 << Dimension;
   for (size_t j = 0; j < number_of_cells; ++j) {
@@ -603,14 +612,36 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
       descriptor.cell_to_node_vector[j][7] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1, 1});
     }
   }
+  std::cout << " - descriptor.cell_to_node_vector t = " << t << '\n';
+  t.stop();
 
+  t.start();
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<Dimension>(descriptor);
+  std::cout << " - cell->face, face->node connectivities t = " << t << '\n';
+  t.stop();
+
+  t.start();
   ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<Dimension>(descriptor);
+  std::cout << " - face->edge, edge->node, cell->edge connectivities t = " << t << '\n';
+  t.stop();
+
+  t.start();
 
   this->_buildBoundaryNodeList(cell_size, descriptor);
+  std::cout << " - boundaryNodeList t = " << t << '\n';
+  t.stop();
+
+  t.start();
   this->_buildBoundaryEdgeList(cell_size, descriptor);
+  std::cout << " - boundaryEdgeList t = " << t << '\n';
+  t.stop();
+
+  t.start();
   this->_buildBoundaryFaceList(cell_size, descriptor);
+  std::cout << " - boundaryFaceList t = " << t << '\n';
+  t.stop();
 
+  t.start();
   descriptor.cell_owner_vector.resize(number_of_cells);
   std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
 
@@ -622,8 +653,12 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
 
   descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
   std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  std::cout << " - final infos t = " << t << '\n';
+  t.stop();
 
+  t.start();
   m_connectivity = Connectivity<Dimension>::build(descriptor);
+  std::cout << " - Connectivity<Dimension>::build(descriptor) t = " << t << '\n';
 }
 
 template <size_t Dimension>
diff --git a/src/mesh/MeshBuilderBase.cpp b/src/mesh/MeshBuilderBase.cpp
index a3a8440bd25499d8aea34a08e7c836b66a4c3a32..973a6099afb49595d02403c697bbb927d2a7ba63 100644
--- a/src/mesh/MeshBuilderBase.cpp
+++ b/src/mesh/MeshBuilderBase.cpp
@@ -132,8 +132,14 @@ MeshBuilderBase::_checkMesh() const
       if (intersection.size() > 1) {
         std::ostringstream error_msg;
         error_msg << "invalid mesh.\n\tFollowing faces\n";
-        for (FaceId face_id : intersection) {
-          error_msg << "\t - id=" << face_id << " number=" << connectivity.faceNumber()[face_id] << '\n';
+        for (FaceId intersection_face_id : intersection) {
+          error_msg << "\t - id=" << intersection_face_id
+                    << " number=" << connectivity.faceNumber()[intersection_face_id] << '\n';
+          error_msg << "\t   nodes:";
+          for (size_t i = 0; i < face_to_node_matrix[intersection_face_id].size(); ++i) {
+            error_msg << ' ' << face_to_node_matrix[intersection_face_id][i];
+          }
+          error_msg << '\n';
         }
         error_msg << "\tare duplicated";
         throw NormalError(error_msg.str());