python / cpython

The Python programming language
https://www.python.org
Other
62.94k stars 30.14k forks source link

Nonblocking serial io using Arduino and Ubuntu 14.10 (Python 3.4.2) performance slowdown #67513

Closed e1603987-21b7-458c-9ad4-39cd23e76f1f closed 5 years ago

e1603987-21b7-458c-9ad4-39cd23e76f1f commented 9 years ago
BPO 23324
Nosy @vstinner, @willingc
Files
  • 3: pyprof2calltree file running "polling_digital_analog_io.py" on python 3.4.2
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields: ```python assignee = None closed_at = created_at = labels = ['interpreter-core', 'expert-IO', 'performance'] title = 'Nonblocking serial io using Arduino and Ubuntu 14.10 (Python 3.4.2) performance slowdown' updated_at = user = 'https://bugs.python.org/MrYsLab' ``` bugs.python.org fields: ```python activity = actor = 'willingc' assignee = 'none' closed = True closed_date = closer = 'willingc' components = ['Interpreter Core', 'IO'] creation = creator = 'MrYsLab' dependencies = [] files = ['37880'] hgrepos = [] issue_num = 23324 keywords = [] message_count = 7.0 messages = ['234774', '234793', '234821', '234822', '344865', '344868', '344870'] nosy_count = 3.0 nosy_names = ['vstinner', 'willingc', 'MrYsLab'] pr_nums = [] priority = 'normal' resolution = 'out of date' stage = 'resolved' status = 'closed' superseder = None type = 'performance' url = 'https://bugs.python.org/issue23324' versions = ['Python 3.4'] ```

    e1603987-21b7-458c-9ad4-39cd23e76f1f commented 9 years ago

    Folks, I am not trying to waste anyone's time. If this is not the correct mailing list to get this problem resolved, please point me to the correct one.

    To summarize my problem, if I run the configuration below with python 3.4.2 on Linux, the program reacts to user input (changing a potentiometer) extremely slowly. If I run the exact same code on Windows, there is no slow down. Also, if I run the exact same setup using pypy 3.2.4, there is no slowdown.

    I also tried running the software on pypy 3.2.4 on Linux and there is no slowdown. The only problem I encounter is using Python 3.4.2 on Linux. Python 2.7.8 works without issue on both Linux and Windows.

    The setup to reproduce the problem is not complicated but requires PyMata to be installed on an Arduino Uno or Leonardo attached to the computer.

    Prerequisites: OS: Ubuntu Linux 14.10. Python: 3.4.2 PyMata 2.02 or can be installed from PyPi Requires PySerial 2.7 Arduino Uno or Leonardo microcontroller with StandardFirmata installed.

    Program to be executed: located in the PyMata/examples/digital_analog_io directory. The file is polling_digital_analog_io.py.

    How the problem exhibits itself: When adjusting the potentiometer to set the intensity level of the LED, the intensity level greatly lags the pot adjustment. On Python 2.7 on Linux and Windows, on Python 3.4.2 on Windows, and on pypy3.2.4 on Linux, there is no lag and the program behaves as expected.

    The only variable is the Linux version of Python 3.4.2, so it is my belief that python 3.4.2 for linux has issues. I will be happy to provide any additional information to help resolve this. Again if this is the wrong mailing list, please point me to the correct one.

    willingc commented 9 years ago

    Alan, Thanks for reporting your issue. I have spent some time looking at it, and I have triaged it as best as I am able for the other developers and you. I am editing the issue title for clarity.

    The issue is related to nonblocking i/o between a 'linux' (Ubuntu 14.10) system and an Arduino and the performance difference of the i/o channel noticed by the user on Python 3.4.2 and 2.7.9.

    Third party packages (PyMata and pySerial) as well as threading module of CPython are used by the application that was reported in the original issue. Due to hardware constraints, I have not duplicated the issue.

    I have done some research on the sources used by the application.

    At the lowest level, i/o between the Arduino and the system is dependent on operating system implementation differences. There are differences between how i/o is implemented by pySerial for different operating systems. Comments in the pySerial source and docs state that nonblocking io and read differ and some functions (such as the nonblocking() method are highlighted as not portable.) There are also comments in the pySerial source code that not all systems support polling properly (presumably at their lower level os code).

    Though perhaps not directly related (but at least tangentially), this blog post explains some nonblocking differences between Python 3 and Python 2 (the last few paragraphs are most relevant). http://ballingt.com/2014/03/01/nonblocking-stdin-in-python-3.html

    --------------------------------------------------- Notes from a reverse walkthrough of the source code ---------------------------------------------------

    In PyMata/examples/digital_analog_io/polling_digital_analog_io.py, analog = board.analog_read(POTENTIOMETER)

    In PyMata/pymata.py, Python's threading module is imported, and the PyMata class contains the API: data_lock = threading.Lock() is set analog_read(self, pin) method uses the data_lock and returns data value from pymata's command_handler's analog_response_table

    Pymata's command_handler PyMata/pymata_command_handler.py has the following information in its docstring: There is no blocking in either communications direction.

    There is blocking when accessing the data tables through the _data_lock

    The get_analog_response_table method uses the data_lock.

    PyMata's pymataserial.py uses pySerial's class serial.Serial to open and initialize a serial port. The following lines are in the \_init__ for PyMataSerial class:

            # without this, running python 3.4 is extremely sluggish
            if sys.platform == 'linux':
                # noinspection PyUnresolvedReferences
                self.arduino.nonblocking()

    It should be noted that programs using pySerial's nonblocking() method are not portable.

    The following is the run method for PyMataSerial:

    def run(self):
            """
            This method continually runs. If an incoming character is available on the serial port
            it is read and placed on the _command_deque
            @return: Never Returns
            """
            while not self.is_stopped():
                # we can get an OSError: [Errno9] Bad file descriptor when shutting down
                # just ignore it
                try:
                    if self.arduino.inWaiting():
                        c = self.arduino.read()
                        self.command_deque.append(ord(c))
                except OSError:
                    pass
                except IOError:
                    self.stop()
            self.close()

    The pySerial inWaiting() method is used to determine whether there are bytes to be read by the read() method. The source for pySerial http://svn.code.sf.net/p/pyserial/code/trunk/pyserial/serial/serialutil.py points out variations between Python 2 and 3 for string vs byte reading.

    Interestingly, pySerial's Serial class can be assembled in two ways: 1) using pySerial's file-like emulation or 2) using io.RawIOBase. From http://svn.code.sf.net/p/pyserial/code/trunk/pyserial/serial/serialposix.py, # assemble Serial class with the platform specific implementation and the base # for file-like behavior. for Python 2.6 and newer, that provide the new I/O # library, derive from io.RawIOBase try: import io except ImportError: # classic version with our own file-like emulation class Serial(PosixSerial, FileLike): pass else: # io library present class Serial(PosixSerial, io.RawIOBase): pass

    class PosixPollSerial(Serial):
        """\
        Poll based read implementation. Not all systems support poll properly.
        However this one has better handling of errors, such as a device
        disconnecting while it's in use (e.g. USB-serial unplugged).
        """
    e1603987-21b7-458c-9ad4-39cd23e76f1f commented 9 years ago

    Additional information: When using another example from PyMata, examples/digital_analog_io/callback_digital_analog_io.py, by adding the line: if sys.platform == 'linux': # noinspection PyUnresolvedReferences self.arduino.nonblocking() Allowed the program to run without much noticeable lag. This program is a lot less io intensive.

    Also, setting the pyserial init option, writeTimeout, did not seem to affect performance when running either test program.

    I have run cProfile and ran the output through pyprof2calltree for the polling_digital_analog_io.py example.

    I am attaching the result of pyprof2calltree to a file called 3. I will provide a similar file for the run with python 2.7.8 in an addtional comment (I can't attach more than 1 file per comment).

    e1603987-21b7-458c-9ad4-39cd23e76f1f commented 9 years ago

    I don't see the file I attached in the previous comment, so I have uploaded 4 files to google drive at:

    https://drive.google.com/folderview?id=0B0adDMMjxksDRGtiWFowVUh0RlE&usp=sharing

    These files are the result of running a cProfile for polling_digital_analog_io.py through pyprof2calltree. The file "2" is for python 2.7.8 and "3" is for python 3.4.2

    They can be viewed using kcachegrind.

    I have also included "2c" and "3c" for the results of running callback_digital_analog_io.py

    willingc commented 5 years ago

    @MrYsLab, Sorry that this issue wasn't resolved in the past.

    I would like to close this since we are currently working on a 3.8 release. I'm going to do that now. You may wish to take a look at MicroPython and CircuitPython if you haven't already seen them. They are pretty interesting.

    Thanks for using Python.

    e1603987-21b7-458c-9ad4-39cd23e76f1f commented 5 years ago

    Hi Carol,     Thanks for the update. I retested on Python 3.7 and the problem seems to have been resolved some where along the way. BTW, I am using MicroPython on an ESP8266 remotely controlled from a Python program on a PC. You can read about it here: https://mryslab.github.io/python_banyan/#gpio_intro/.

    Looking forward to 3.8!

    Thanks again, Alan

    On 6/6/19 3:56 PM, Carol Willing wrote:

    Change by Carol Willing \willingc@gmail.com\:

    ---------- status: open -> closed


    Python tracker \report@bugs.python.org\ \https://bugs.python.org/issue23324\


    willingc commented 5 years ago

    @MrYsLab Well, I'm glad it resolved itself along the way.

    Your project looks cool and the docs are very nice too. Thanks for the link.