[Bf-blender-cvs] [9757b4efb12] master: OBJ: improve new importer file parsing performance on windows

Aras Pranckevicius noreply at git.blender.org
Thu May 12 12:50:02 CEST 2022


Commit: 9757b4efb12e0fba21735fa7960148e6a7b67bb2
Author: Aras Pranckevicius
Date:   Thu May 12 13:48:55 2022 +0300
Branches: master
https://developer.blender.org/rB9757b4efb12e0fba21735fa7960148e6a7b67bb2

OBJ: improve new importer file parsing performance on windows

The OBJ parser was primarily using StringRef for convenience, with
functions like "skip whitespace" or "parse a number" taking an input
stringref, representing an input line, and returning a new stringref,
representing the remainder of the line. This is convenient, but does
more work than strictly needed -- while parsing, only the "beginning"
of the line ever changes by moving forward; the end of the line
always stays the same. We can change the code to take a pair of
pointers (begin of line, end of line) as input, and make the
functions return the new begin of line pointer. This makes the return
value neatly fit into a processor register, which StringRef did not.

On Windows, this does result in non-trivial speedups in the actual
OBJ file parsing part, due to Windows calling convention where return
values larger than 64 bits are returned via memory. Does not
measurably affect performance on Mac/Linux, because the calling
convention there uses a pair of 64-bit registers to return a
StringRef.

End-to-end times of importing several test files, on Windows
(VS2022 build, Ryzen 5950X):

- Monkey subdivided to level 6, no normals (220MB file): 1.25s -> 0.85s
- Rungholt minecraft level (270MB file): 7.0s -> 5.8s
- Blender 3 splash scene (2.4GB file): 49.1s -> 45.5s

The full import process has a lot of other overhead besides actual
OBJ file parsing (mostly creating actual blender objects out of
parsed data). In pure parsing, in the monkey test scene above, the
parsing part goes 1.0s -> 0.6s.

Reviewed By: Howard Trickey
Differential Revision: https://developer.blender.org/D14936

===================================================================

M	source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
M	source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc
M	source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh
M	source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc

===================================================================

diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
index fa89b49b605..c7990028312 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
@@ -66,40 +66,43 @@ static Geometry *create_geometry(Geometry *const prev_geometry,
 }
 
 static void geom_add_vertex(Geometry *geom,
-                            const StringRef line,
+                            const char *p,
+                            const char *end,
                             GlobalVertices &r_global_vertices)
 {
   float3 vert;
-  parse_floats(line, 0.0f, vert, 3);
+  parse_floats(p, end, 0.0f, vert, 3);
   r_global_vertices.vertices.append(vert);
   geom->vertex_count_++;
 }
 
 static void geom_add_vertex_normal(Geometry *geom,
-                                   const StringRef line,
+                                   const char *p,
+                                   const char *end,
                                    GlobalVertices &r_global_vertices)
 {
   float3 normal;
-  parse_floats(line, 0.0f, normal, 3);
+  parse_floats(p, end, 0.0f, normal, 3);
   r_global_vertices.vertex_normals.append(normal);
   geom->has_vertex_normals_ = true;
 }
 
-static void geom_add_uv_vertex(const StringRef line, GlobalVertices &r_global_vertices)
+static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices)
 {
   float2 uv;
-  parse_floats(line, 0.0f, uv, 2);
+  parse_floats(p, end, 0.0f, uv, 2);
   r_global_vertices.uv_vertices.append(uv);
 }
 
 static void geom_add_edge(Geometry *geom,
-                          StringRef line,
+                          const char *p,
+                          const char *end,
                           const VertexIndexOffset &offsets,
                           GlobalVertices &r_global_vertices)
 {
   int edge_v1, edge_v2;
-  line = parse_int(line, -1, edge_v1);
-  line = parse_int(line, -1, edge_v2);
+  p = parse_int(p, end, -1, edge_v1);
+  p = parse_int(p, end, -1, edge_v2);
   /* Always keep stored indices non-negative and zero-based. */
   edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
   edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
@@ -108,7 +111,8 @@ static void geom_add_edge(Geometry *geom,
 }
 
 static void geom_add_polygon(Geometry *geom,
-                             StringRef line,
+                             const char *p,
+                             const char *end,
                              const GlobalVertices &global_vertices,
                              const VertexIndexOffset &offsets,
                              const int material_index,
@@ -127,24 +131,24 @@ static void geom_add_polygon(Geometry *geom,
   curr_face.start_index_ = orig_corners_size;
 
   bool face_valid = true;
-  line = drop_whitespace(line);
-  while (!line.is_empty() && face_valid) {
+  p = drop_whitespace(p, end);
+  while (p < end && face_valid) {
     PolyCorner corner;
     bool got_uv = false, got_normal = false;
     /* Parse vertex index. */
-    line = parse_int(line, INT32_MAX, corner.vert_index, false);
+    p = parse_int(p, end, INT32_MAX, corner.vert_index, false);
     face_valid &= corner.vert_index != INT32_MAX;
-    if (!line.is_empty() && line[0] == '/') {
+    if (p < end && *p == '/') {
       /* Parse UV index. */
-      line = line.drop_prefix(1);
-      if (!line.is_empty() && line[0] != '/') {
-        line = parse_int(line, INT32_MAX, corner.uv_vert_index, false);
+      ++p;
+      if (p < end && *p != '/') {
+        p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false);
         got_uv = corner.uv_vert_index != INT32_MAX;
       }
       /* Parse normal index. */
-      if (!line.is_empty() && line[0] == '/') {
-        line = line.drop_prefix(1);
-        line = parse_int(line, INT32_MAX, corner.vertex_normal_index, false);
+      if (p < end && *p == '/') {
+        ++p;
+        p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false);
         got_normal = corner.uv_vert_index != INT32_MAX;
       }
     }
@@ -185,7 +189,7 @@ static void geom_add_polygon(Geometry *geom,
     curr_face.corner_count_++;
 
     /* Skip whitespace to get to the next face corner. */
-    line = drop_whitespace(line);
+    p = drop_whitespace(p, end);
   }
 
   if (face_valid) {
@@ -200,14 +204,16 @@ static void geom_add_polygon(Geometry *geom,
 }
 
 static Geometry *geom_set_curve_type(Geometry *geom,
-                                     const StringRef rest_line,
+                                     const char *p,
+                                     const char *end,
                                      const GlobalVertices &global_vertices,
                                      const StringRef group_name,
                                      VertexIndexOffset &r_offsets,
                                      Vector<std::unique_ptr<Geometry>> &r_all_geometries)
 {
-  if (rest_line.find("bspline") == StringRef::not_found) {
-    std::cerr << "Curve type not supported:'" << rest_line << "'" << std::endl;
+  p = drop_whitespace(p, end);
+  if (!StringRef(p, end).startswith("bspline")) {
+    std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl;
     return geom;
   }
   geom = create_geometry(
@@ -216,22 +222,23 @@ static Geometry *geom_set_curve_type(Geometry *geom,
   return geom;
 }
 
-static void geom_set_curve_degree(Geometry *geom, const StringRef line)
+static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end)
 {
-  parse_int(line, 3, geom->nurbs_element_.degree);
+  parse_int(p, end, 3, geom->nurbs_element_.degree);
 }
 
 static void geom_add_curve_vertex_indices(Geometry *geom,
-                                          StringRef line,
+                                          const char *p,
+                                          const char *end,
                                           const GlobalVertices &global_vertices)
 {
   /* Curve lines always have "0.0" and "1.0", skip over them. */
   float dummy[2];
-  line = parse_floats(line, 0, dummy, 2);
+  p = parse_floats(p, end, 0, dummy, 2);
   /* Parse indices. */
-  while (!line.is_empty()) {
+  while (p < end) {
     int index;
-    line = parse_int(line, INT32_MAX, index);
+    p = parse_int(p, end, INT32_MAX, index);
     if (index == INT32_MAX) {
       return;
     }
@@ -241,22 +248,22 @@ static void geom_add_curve_vertex_indices(Geometry *geom,
   }
 }
 
-static void geom_add_curve_parameters(Geometry *geom, StringRef line)
+static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end)
 {
-  line = drop_whitespace(line);
-  if (line.is_empty()) {
-    std::cerr << "Invalid OBJ curve parm line: '" << line << "'" << std::endl;
+  p = drop_whitespace(p, end);
+  if (p == end) {
+    std::cerr << "Invalid OBJ curve parm line" << std::endl;
     return;
   }
-  if (line[0] != 'u') {
-    std::cerr << "OBJ curve surfaces are not supported: '" << line[0] << "'" << std::endl;
+  if (*p != 'u') {
+    std::cerr << "OBJ curve surfaces are not supported: '" << *p << "'" << std::endl;
     return;
   }
-  line = line.drop_prefix(1);
+  ++p;
 
-  while (!line.is_empty()) {
+  while (p < end) {
     float val;
-    line = parse_float(line, FLT_MAX, val);
+    p = parse_float(p, end, FLT_MAX, val);
     if (val != FLT_MAX) {
       geom->nurbs_element_.parm.append(val);
     }
@@ -269,7 +276,6 @@ static void geom_add_curve_parameters(Geometry *geom, StringRef line)
 
 static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
 {
-
   if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
       rest_line.find("default") != string::npos) {
     /* Set group for future elements like faces or curves to empty. */
@@ -279,17 +285,18 @@ static void geom_update_group(const StringRef rest_line, std::string &r_group_na
   r_group_name = rest_line;
 }
 
-static void geom_update_smooth_group(StringRef line, bool &r_state_shaded_smooth)
+static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth)
 {
-  line = drop_whitespace(line);
+  p = drop_whitespace(p, end);
   /* Some implementations use "0" and "null" too, in addition to "off". */
+  const StringRef line = StringRef(p, end);
   if (line == "0" || line.startswith("off") || line.startswith("null")) {
     r_state_shaded_smooth = false;
     return;
   }
 
   int smooth = 0;
-  parse_int(line, 0, smooth);
+  parse_int(p, end, 0, smooth);
   r_state_shaded_smooth = smooth != 0;
 }
 
@@ -311,21 +318,21 @@ OBJParser::~OBJParser()
 }
 
 /* If line starts with keyword followed by whitespace, returns true and drops it from the line. */
-static bool parse_keyword(StringRef &line, StringRef keyword)
+static bool parse_keyword(const char *&p, const char *end, StringRef keyword)
 {
   const size_t keyword_len = keyword.size();
-  if (line.size() < keyword_len + 1) {
+  if (end - p < keyword_len + 1) {
     return false;
   }
-  if (!line.startswith(keyword)) {
+  if (memcmp(p, keyword.data(), keyword_len) != 0) {
     return false;
   }
   /* Treat any ASCII control character as white-space;
    * don't use `isspace()` for performance reasons. */
-  if (line[keyword_len] > ' ') {
+  if (p[keyword_len] > ' ') {
     return false;
   }
-  line = line.drop_prefix(keyword_len + 1);
+  p += keyword_len + 1;
   return true;
 }
 
@@ -399,27 +406,29 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
     StringRef buffer_str{buffer.data(), (int64_t)last_nl};
     while (!buffer_str.is_empty()) {
       StringRef line = read_next_line(buffer_str);
-      line = drop_whitespace(line);
+      const char *p = line.begin(), *end = line.end();
+      p = drop_whitespace(p, end);
       ++line_number;
-      if (line.is_empty()) {
+      if (p == end) {
         continue;
       }
       /* Most common things that start with 'v': vertices, normals, UVs. */
-      if (line[0] == 'v') {
-        if (parse_keyword(line, "v

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list