1

I'm running a python script from C++. I first set up the python script (load module and instantiate class), then call one of the methods of the class N (~1000000) times and measure how much time each method call took using std::chrono::high_resolution_clock and std::chrono::duration_cast. However, interestingly, the duration of the method call seems to be either roughly 50 microseconds OR roughly 100 microseconds (seen in Fig 1. below). Moreover, the durations seems to periodic (seen in Fig 2. below, only a part of data pictured).

What could be the cause of this? The method I call from my python code looks like this:

def data_received(self, data, time_us, first_sample_of_experiment):
    self.eeg_data_index += 1

    c3 = data[4]
    others = [data[20], data[22], data[24], data[26]]
    filtered = self.average(c3, others)

    self.data.append(filtered)
    if len(self.data) > 20:
        self.data.pop(0)

    signal = self.peak_detection.thresholding_algo(c3)
    if signal == 0 and not self.peak_over:
        self.peak_over = True

    peak = signal != 0

    if peak and self.peak_over:
        self.peak_over = False
        self.peak_at = self.eeg_data_index
        self.peaks_detected += 1

    return [charge_event, charge_event]

Where peak_detection is a peak detection algorithm adapted from here and looks like this:

import numpy as np


class RealtimePeakDetection:
    def __init__(self, array, lag, threshold, influence):
        self.y = list(array)
        self.length = len(self.y)
        self.lag = lag
        self.threshold = threshold
        self.influence = influence

        self.signals = [0] * len(self.y)
        self.filteredY = np.array(self.y).tolist()
        self.avgFilter = [0] * len(self.y)
        self.stdFilter = [0] * len(self.y)
        self.avgFilter[self.lag - 1] = np.mean(self.y[0:self.lag]).tolist()
        self.stdFilter[self.lag - 1] = np.std(self.y[0:self.lag]).tolist()

    def thresholding_algo(self, new_value):
        i = len(self.y) - 1
        self.y.append(new_value)

        self.signals += [0]
        self.filteredY += [0]
        self.avgFilter += [0]
        self.stdFilter += [0]

        if len(self.y) > self.length:
            self.y.pop(0)
        if len(self.signals) > self.length:
            self.signals.pop(0)
        if len(self.filteredY) > self.length:
            self.filteredY.pop(0)
        if len(self.avgFilter) > self.length:
            self.avgFilter.pop(0)
        if len(self.stdFilter) > self.length:
            self.stdFilter.pop(0)

        if abs(self.y[i] - self.avgFilter[i - 1]) > (self.threshold * self.stdFilter[i - 1]):

            if self.y[i] > self.avgFilter[i - 1]:
                self.signals[i] = 1
            else:
                self.signals[i] = -1

            self.filteredY[i] = self.influence * self.y[i] + (1 - self.influence) * self.filteredY[i - 1]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])
        else:
            self.signals[i] = 0
            self.filteredY[i] = self.y[i]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])

        return self.signals[i]

I wonder what could cause this kind of periodic durations of the method calls? I'm using Linux PREEMPT_RT kernel 5.15.55-rt48 and my C++ program has RTPRIO -98.

My C++ program is a ROS node with the following CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(data_processor)

if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    add_compile_options(-Wall -Wextra -Wpedantic)
endif ()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(mtms_interfaces REQUIRED)
find_package(fpga_interfaces REQUIRED)

set(MATLAB_FIND_DEBUG true)
# MATLAB
find_package(Matlab)
if (Matlab_FOUND)
    # Following 5 lines taken from https://stackoverflow.com/questions/8880802/cmake-linking-shared-library
    # Fixes runtime error "error while loading shared libraries: libMatlabDataArray.so: cannot open shared object file: No such file or directory"
    SET(CMAKE_SKIP_BUILD_RPATH FALSE)
    SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
    SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64")
    SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
    SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib64")

    set(LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:${Matlab_ROOT_DIR}/extern/bin/glnxa64:${Matlab_ROOT_DIR}/sys/os/glnxa64)
    include_directories(${Matlab_ROOT_DIR}/extern/include/)
    link_directories(${Matlab_ROOT_DIR}/extern/bin/glnxa64)
else ()
    message(STATUS "MATLAB NOT FOUND")
endif ()

add_executable(
        data_processor
        src/data_processor.cpp
        src/processor.cpp
        src/headers/processor.h
        src/python_processor.cpp
        src/matlab_processor.cpp
        src/compiled_matlab_processor.cpp
        src/matlab_processor_interface.cpp
        src/headers/matlab_processor.h
        src/headers/python_processor.h
        src/headers/compiled_matlab_processor.h
        src/headers/scheduling_utils.h
        src/headers/scheduling_utils.cpp
        src/headers/matlab_processor_interface.h
        src/headers/fpga_event.h
        src/headers/data_processor.h
        src/headers/matlab_helpers.h
        src/matlab_helpers.cpp)

target_include_directories(data_processor
        PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/lib>
        $<INSTALL_INTERFACE:lib>)

ament_target_dependencies(data_processor rclcpp std_msgs mtms_interfaces fpga_interfaces)

if (Matlab_FOUND)
    # MATLAB, Linker to libMatlabEngine in link_directories
    target_link_libraries(data_processor MatlabDataArray)
    target_link_libraries(data_processor MatlabEngine)
endif ()

# Python
find_package(PythonLibs)
if (PYTHONLIBS_FOUND)
    message(STATUS "Python found")
    include_directories(${PYTHON_INCLUDE_DIRS})
    target_link_libraries(data_processor ${PYTHON_LIBRARIES})
else ()
    message(STATUS "Python not found")
endif ()

install(TARGETS
        data_processor
        DESTINATION lib/${PROJECT_NAME}
        )

install(
        DIRECTORY launch
        DESTINATION share/${PROJECT_NAME}
)

if (BUILD_TESTING)
    find_package(ament_lint_auto REQUIRED)
    ament_lint_auto_find_test_dependencies()
endif ()

ament_package()

I simply use colcon build --packages-select <ros package> --cmake-args -DCMAKE_BUILD_TYPE=Release to build the project. The compiler used is GNU 9.4.0.

Figure 1. Figure 1.

Figure 2. Figure 2.

Alqio
  • 452
  • 1
  • 5
  • 15
  • 1
    At a guess that's the scheduling interval for your os, how are you launching this code? Note that `steady_clock` should be used for timing – Alan Birtles Sep 20 '22 at 07:45
  • 2
    Any question regarding the speed of a C++ application or module requires that you specify the compiler used, version of the compiler that is used, and the optimization settings you used when building the C++ module. If you are timing an unoptimized or "debug" build, the timings are meaningless. – PaulMcKenzie Sep 20 '22 at 07:45
  • I'm using ROS2, I added my CMakeLists and additional information to the original post. – Alqio Sep 20 '22 at 07:50
  • @PaulMcKenzie the compiler used is GNU 9.4.0 – Alqio Sep 20 '22 at 08:00
  • @AlanBirtles I changed to steady_clock but it had no effect on the periodic nature of the durations. But I think you are on the right track in that its related to the scheduling of the cpu. I just wonder why as I'm specifically setting the priority to maximum and according to htop it's correctly set. I'm using round robin but have also tested fifo scheduling policy, but the same effect can be seen on both. This kind of periodic duration can only be seen when running python scripts from c++. – Alqio Sep 20 '22 at 08:25

0 Answers0