COMPMID-3069: Improve compilation time by removing regex from test framework headers
authorMatthew Bentham <matthew.bentham@arm.com>
Mon, 9 Mar 2020 10:55:40 +0000 (10:55 +0000)
committerMichele Di Giorgio <michele.digiorgio@arm.com>
Thu, 12 Mar 2020 13:52:15 +0000 (13:52 +0000)
Regex is used as an implementation detail by TestFilter and libnpy,
is an expensive header to parse, and also instantiates static objects.

Move TestFilter out of Framework.h by using a partial definition and
a unique_ptr instead of storing the TestFilter by value.

Move npy.h out of AssetsLibrary.h by moving part of a template
definition into AssetsLibrary.cpp

Knocks about 15% off compilation time of small test cases (for me,
knocked .7s off 5s compilation of HogDetector.cpp)

Signed-off-by: Matthew Bentham <matthew.bentham@arm.com>
Change-Id: I1dce18855d0752ec25b2165fddbc6861a4c55a76
Reviewed-on: https://eu-gerrit-1.euhpc.arm.com/c/VisualCompute/ComputeLibrary/+/229181
Reviewed-by: Georgios Pinitas <georgios.pinitas@arm.com>
Tested-by: bsgcomp <bsgcomp@arm.com>
Reviewed-on: https://review.mlplatform.org/c/ml/ComputeLibrary/+/2856
Tested-by: Arm Jenkins <bsgcomp@arm.com>
Comments-Addressed: Arm Jenkins <bsgcomp@arm.com>
Reviewed-by: Michele Di Giorgio <michele.digiorgio@arm.com>
tests/AssetsLibrary.cpp
tests/AssetsLibrary.h
tests/framework/Framework.cpp
tests/framework/Framework.h

index c6d86d1c1ae42ebf9bbaa3b2fbd98cf3b2da1bdc..eafa6314b1e4e7fa3ef78c494951651a6914732d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2018 ARM Limited.
+ * Copyright (c) 2017-2020 ARM Limited.
  *
  * SPDX-License-Identifier: MIT
  *
 
 #include "arm_compute/core/ITensor.h"
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#include "libnpy/npy.hpp"
+#pragma GCC diagnostic pop
+
 #include <cctype>
 #include <fstream>
 #include <limits>
@@ -511,5 +516,42 @@ RawTensor AssetsLibrary::get(const std::string &name, Format format, Channel cha
 {
     return RawTensor(find_or_create_raw_tensor(name, format, channel));
 }
+
+namespace detail
+{
+inline void validate_npy_header(std::ifstream &stream, const std::string &expect_typestr, const TensorShape &expect_shape)
+{
+    ARM_COMPUTE_UNUSED(expect_typestr);
+    ARM_COMPUTE_UNUSED(expect_shape);
+
+    std::string header = npy::read_header(stream);
+
+    // Parse header
+    std::vector<unsigned long> shape;
+    bool                       fortran_order = false;
+    std::string                typestr;
+    npy::parse_header(header, typestr, fortran_order, shape);
+
+    // Check if the typestring matches the given one
+    ARM_COMPUTE_ERROR_ON_MSG(typestr != expect_typestr, "Typestrings mismatch");
+
+    // Validate tensor shape
+    ARM_COMPUTE_ERROR_ON_MSG(shape.size() != expect_shape.num_dimensions(), "Tensor ranks mismatch");
+    if(fortran_order)
+    {
+        for(size_t i = 0; i < shape.size(); ++i)
+        {
+            ARM_COMPUTE_ERROR_ON_MSG(expect_shape[i] != shape[i], "Tensor dimensions mismatch");
+        }
+    }
+    else
+    {
+        for(size_t i = 0; i < shape.size(); ++i)
+        {
+            ARM_COMPUTE_ERROR_ON_MSG(expect_shape[i] != shape[shape.size() - i - 1], "Tensor dimensions mismatch");
+        }
+    }
+}
+} // namespace detail
 } // namespace test
 } // namespace arm_compute
index e625c37505c075447645c2212335d6241f311876..84653ed089b4469fc7fb5b9208d49ea40ea20509 100644 (file)
 #include "arm_compute/core/Types.h"
 #include "arm_compute/core/Window.h"
 #include "arm_compute/core/utils/misc/Random.h"
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-parameter"
-#include "libnpy/npy.hpp"
-#pragma GCC diagnostic pop
 #include "tests/RawTensor.h"
 #include "tests/TensorCache.h"
 #include "tests/Utils.h"
@@ -469,6 +465,16 @@ inline std::vector<std::pair<T, T>> convert_range_pair(const std::vector<AssetsL
     });
     return converted;
 }
+
+/* Read npy header and check the payload is suitable for the specified type and shape
+ *
+ * @param[in] stream         ifstream of the npy file
+ * @param[in] expect_typestr Expected typestr
+ * @param[in] expect_shape   Shape of tensor expected to receive the data
+ *
+ * @note Advances stream to the beginning of the data payload
+ */
+void validate_npy_header(std::ifstream &stream, const std::string &expect_typestr, const TensorShape &expect_shape);
 } // namespace detail
 
 template <typename T, typename D>
@@ -959,41 +965,14 @@ void AssetsLibrary::fill_layer_data(T &&tensor, std::string name) const
 #endif /* _WIN32 */
     const std::string path = _library_path + path_separator + name;
 
-    std::vector<unsigned long> shape;
-
     // Open file
     std::ifstream stream(path, std::ios::in | std::ios::binary);
     if(!stream.good())
     {
         throw framework::FileNotFound("Could not load npy file: " + path);
     }
-    std::string header = npy::read_header(stream);
-
-    // Parse header
-    bool        fortran_order = false;
-    std::string typestr;
-    npy::parse_header(header, typestr, fortran_order, shape);
 
-    // Check if the typestring matches the given one
-    std::string expect_typestr = get_typestring(tensor.data_type());
-    ARM_COMPUTE_ERROR_ON_MSG(typestr != expect_typestr, "Typestrings mismatch");
-
-    // Validate tensor shape
-    ARM_COMPUTE_ERROR_ON_MSG(shape.size() != tensor.shape().num_dimensions(), "Tensor ranks mismatch");
-    if(fortran_order)
-    {
-        for(size_t i = 0; i < shape.size(); ++i)
-        {
-            ARM_COMPUTE_ERROR_ON_MSG(tensor.shape()[i] != shape[i], "Tensor dimensions mismatch");
-        }
-    }
-    else
-    {
-        for(size_t i = 0; i < shape.size(); ++i)
-        {
-            ARM_COMPUTE_ERROR_ON_MSG(tensor.shape()[i] != shape[shape.size() - i - 1], "Tensor dimensions mismatch");
-        }
-    }
+    validate_npy_header(stream, tensor.data_type(), tensor.shape());
 
     // Read data
     if(tensor.padding().empty())
index c6aaad0586dabb8701479dfea9e07cf2f92b3a89..dff280dc8cebeb5c8110fd3e2ff9b840efdea035 100644 (file)
@@ -26,6 +26,7 @@
 #include "arm_compute/runtime/Scheduler.h"
 #include "support/MemorySupport.h"
 #include "tests/framework/ParametersLibrary.h"
+#include "tests/framework/TestFilter.h"
 
 #ifdef ARM_COMPUTE_CL
 #include "arm_compute/runtime/CL/CLRuntimeContext.h"
@@ -49,6 +50,7 @@ namespace framework
 std::unique_ptr<InstrumentsInfo> instruments_info;
 
 Framework::Framework()
+    : _test_filter(nullptr)
 {
     _available_instruments.emplace(std::pair<InstrumentType, ScaleFactor>(InstrumentType::WALL_CLOCK_TIMESTAMPS, ScaleFactor::NONE), Instrument::make_instrument<WallClockTimestamps, ScaleFactor::NONE>);
     _available_instruments.emplace(std::pair<InstrumentType, ScaleFactor>(InstrumentType::WALL_CLOCK_TIMESTAMPS, ScaleFactor::TIME_MS),
@@ -127,7 +129,7 @@ Framework &Framework::get()
 
 void Framework::init(const FrameworkConfig &config)
 {
-    _test_filter    = TestFilter(config.mode, config.name_filter, config.id_filter);
+    _test_filter.reset(new TestFilter(config.mode, config.name_filter, config.id_filter));
     _num_iterations = config.num_iterations;
     _log_level      = config.log_level;
     _cooldown_sec   = config.cooldown_sec;
@@ -558,7 +560,7 @@ bool Framework::run()
         const std::string test_case_name = test_factory->name();
         const TestInfo    test_info{ id, test_case_name, test_factory->mode(), test_factory->status() };
 
-        if(_test_filter.is_selected(test_info))
+        if(_test_filter->is_selected(test_info))
         {
 #ifdef ARM_COMPUTE_CL
             // Every 100 tests, reset the OpenCL context to release the allocated memory
@@ -678,7 +680,7 @@ std::vector<TestInfo> Framework::test_infos() const
     {
         TestInfo test_info{ id, factory->name(), factory->mode(), factory->status() };
 
-        if(_test_filter.is_selected(test_info))
+        if(_test_filter->is_selected(test_info))
         {
             ids.emplace_back(std::move(test_info));
         }
index fb52fd8ffe46575474d65235272b7b6827a0d210..11dedfe89f8e63226cb8345bdf1cc61e39cf8825 100644 (file)
@@ -29,7 +29,6 @@
 #include "Profiler.h"
 #include "TestCase.h"
 #include "TestCaseFactory.h"
-#include "TestFilter.h"
 #include "TestResult.h"
 #include "Utils.h"
 #include "instruments/Instruments.h"
 #include <memory>
 #include <numeric>
 #include <ostream>
-#include <regex>
 #include <set>
 #include <sstream>
 #include <string>
-#include <tuple>
 #include <vector>
 
 namespace arm_compute
@@ -54,6 +51,8 @@ namespace test
 {
 namespace framework
 {
+class TestFilter;
+
 /** Framework configuration structure */
 struct FrameworkConfig
 {
@@ -346,7 +345,7 @@ private:
     std::map<InstrumentsDescription, create_function *> _available_instruments{};
 
     std::set<framework::InstrumentsDescription> _instruments{ std::pair<InstrumentType, ScaleFactor>(InstrumentType::NONE, ScaleFactor::NONE) };
-    TestFilter                                  _test_filter{};
+    std::unique_ptr<TestFilter>                 _test_filter;
     LogLevel                                    _log_level{ LogLevel::ALL };
     const TestInfo                             *_current_test_info{ nullptr };
     TestResult                                 *_current_test_result{ nullptr };